ADDED ABOUT Index: ABOUT ================================================================== --- ABOUT +++ ABOUT @@ -0,0 +1,6 @@ +web2py is an open source full-stack framework for agile development +of secure database-driven web-based applications, written and programmable in +Python. + +Created by Massimo Di Pierro + ADDED LICENSE Index: LICENSE ================================================================== --- LICENSE +++ LICENSE @@ -0,0 +1,137 @@ +## Web2py License + +Web2py is Licensed under the LGPL license version 3 +(http://www.gnu.org/licenses/lgpl.html) + +Copyrighted (c) by Massimo Di Pierro (2007-2011) + +### On Commercial Redistribution + +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 +But you must: +- make clear in the documentation that your application uses web2py +- release any modification of the web2py libraries under the LGPLv3 license + +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 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 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) + +### Licenses for third party contributed software + +web2py contains third party software under the gluon/contrib/ folder. +Each file/module in contrib is distributed with web2py under its original license. +Here we list some of them. + +#### gluon.contrib.simplejson LICENSE + +Copyright (c) 2006 Bob Ippolito - Permission is hereby granted, free of charge, +to any person obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, distribute, +sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +#### gluon.contrib.rss2.py (originally PyRSS2Gen) LICENSE + +This is copyright (c) by Dalke Scientific Software, LLC and released under the +BSD license. See the file LICENSE in the distribution or + for details. + +#### gluon.contrib.markdown (markdown2) LICENSE + +MIT License from from + +#### gluon.contrib.feedparser LICENSE + +Copyright (c) 2002-2005, Mark Pilgrim + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +* Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +#### gluon.wsgiserver.py LICENSE (borrowed from cherrypy) + +Copyright (c) 2004, CherryPy Team (team@cherrypy.org) +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the CherryPy Team nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#### gluon.contrib.pam LICENSE + +Copyright (C) 2007-2009 Chris AtLee Licensed under the MIT license + +#### gluon.contrib.shell LICENSE + +Copyright (C) by Google inc. Apache 2.0 Lincense + +#### The javascript licenses are in the code itself + ADDED Makefile Index: Makefile ================================================================== --- Makefile +++ Makefile @@ -0,0 +1,114 @@ +clean: + rm -f httpserver.log + rm -f parameters*.py + rm -f -r applications/*/compiled + find ./ -name '*~' -exec rm -f {} \; + find ./ -name '#*' -exec rm -f {} \; + find ./ -name 'Thumbs.db' -exec rm -f {} \; + find ./gluon/ -name '.*' -exec rm -f {} \; + find ./gluon/ -name '*class' -exec rm -f {} \; + find ./applications/admin/ -name '.*' -exec rm -f {} \; + find ./applications/examples/ -name '.*' -exec rm -f {} \; + find ./applications/welcome/ -name '.*' -exec rm -f {} \; + find ./ -name '*.pyc' -exec rm -f {} \; +all: + echo "The Makefile is used to build the distribution." + echo "In order to run web2py you do not need to make anything." + echo "just run web2py.py" +epydoc: + ### build epydoc + rm -f -r applications/examples/static/epydoc/ + epydoc --config epydoc.conf + cp applications/examples/static/title.png applications/examples/static/epydoc +tests: + cd gluon/tests; ./test.sh 1>tests.log 2>&1 +src: + echo 'Version 1.98.2 ('`date +%Y-%m-%d\ %H:%M:%S`')' > VERSION + ### rm -f all junk files + make clean + ### clean up baisc apps + rm -f routes.py + rm -f applications/*/sessions/* + rm -f applications/*/errors/* | echo 'too many files' + rm -f applications/*/cache/* + rm -f applications/admin/databases/* + rm -f applications/welcome/databases/* + rm -f applications/examples/databases/* + rm -f applications/admin/uploads/* + rm -f applications/welcome/uploads/* + rm -f applications/examples/uploads/* + ### make admin layout and appadmin the default + cp applications/admin/views/appadmin.html applications/welcome/views + cp applications/admin/views/appadmin.html applications/examples/views + cp applications/admin/controllers/appadmin.py applications/welcome/controllers + cp applications/admin/controllers/appadmin.py applications/examples/controllers + ### update the license + cp ABOUT applications/admin/ + cp ABOUT applications/examples/ + cp LICENSE applications/admin/ + cp LICENSE applications/examples/ + ### build web2py_src.zip + echo '' > NEWINSTALL + mv web2py_src.zip web2py_src_old.zip | echo 'no old' + cd ..; zip -r web2py/web2py_src.zip web2py/gluon/*.py web2py/gluon/contrib/* web2py/splashlogo.gif web2py/*.py web2py/ABOUT web2py/LICENSE web2py/README web2py/NEWINSTALL web2py/VERSION web2py/Makefile web2py/epydoc.css web2py/epydoc.conf web2py/app.example.yaml web2py/logging.example.conf web2py_exe.conf web2py/queue.example.yaml MANIFEST.in mkweb2pyenv startweb2py web2py/scripts/*.sh web2py/scripts/*.py web2py/applications/admin web2py/applications/examples/ web2py/applications/welcome web2py/applications/__init__.py web2py/site-packages/__init__.py web2py/gluon/tests/*.sh web2py/gluon/tests/*.py + +mdp: + make epydoc + make src + make app + make win +app: + echo 'did you uncomment import_all in gluon/main.py?' + python2.5 -c 'import compileall; compileall.compile_dir("gluon/")' + #python web2py.py -S welcome -R __exit__.py + find gluon -path '*.pyc' -exec cp {} ../web2py_osx/site-packages/{} \; + cd ../web2py_osx/site-packages/; zip -r ../site-packages.zip * + mv ../web2py_osx/site-packages.zip ../web2py_osx/web2py/web2py.app/Contents/Resources/lib/python2.5 + cp ABOUT ../web2py_osx/web2py/web2py.app/Contents/Resources + cp NEWINSTALL ../web2py_osx/web2py/web2py.app/Contents/Resources + cp LICENSE ../web2py_osx/web2py/web2py.app/Contents/Resources + cp VERSION ../web2py_osx/web2py/web2py.app/Contents/Resources + cp README ../web2py_osx/web2py/web2py.app/Contents/Resources + cp splashlogo.gif ../web2py_osx/web2py/web2py.app/Contents/Resources + cp options_std.py ../web2py_osx/web2py/web2py.app/Contents/Resources + cp routes.example.py ../web2py_osx/web2py/web2py.app/Contents/Resources + cp router.example.py ../web2py_osx/web2py/web2py.app/Contents/Resources + cp app.example.yaml ../web2py_osx/web2py/web2py.app/Contents/Resources + cp queue.example.yaml ../web2py_osx/web2py/web2py.app/Contents/Resources + cp -r applications/admin ../web2py_osx/web2py/web2py.app/Contents/Resources/applications + cp -r applications/welcome ../web2py_osx/web2py/web2py.app/Contents/Resources/applications + cp -r applications/examples ../web2py_osx/web2py/web2py.app/Contents/Resources/applications + cp applications/__init__.py ../web2py_osx/web2py/web2py.app/Contents/Resources/applications + cd ../web2py_osx; zip -r web2py_osx.zip web2py + mv ../web2py_osx/web2py_osx.zip . +win: + echo 'did you uncomment import_all in gluon/main.py?' + python2.5 -c 'import compileall; compileall.compile_dir("gluon/")' + find gluon -path '*.pyc' -exec cp {} ../web2py_win/library/{} \; + cd ../web2py_win/library/; zip -r ../library.zip * + mv ../web2py_win/library.zip ../web2py_win/web2py + cp ABOUT ../web2py_win/web2py/ + cp NEWINSTALL ../web2py_win/web2py/ + cp LICENSE ../web2py_win/web2py/ + cp VERSION ../web2py_win/web2py/ + cp README ../web2py_win/web2py/ + cp splashlogo.gif ../web2py_win/web2py/ + cp options_std.py ../web2py_win/web2py/ + cp routes.example.py ../web2py_win/web2py/ + cp router.example.py ../web2py_win/web2py/ + cp app.example.yaml ../web2py_win/web2py/ + cp queue.example.yaml ../web2py_win/web2py/ + cp -r applications/admin ../web2py_win/web2py/applications + cp -r applications/welcome ../web2py_win/web2py/applications + cp -r applications/examples ../web2py_win/web2py/applications + cp applications/__init__.py ../web2py_win/web2py/applications + cd ../web2py_win; zip -r web2py_win.zip web2py + mv ../web2py_win/web2py_win.zip . +run: + python2.5 web2py.py -a hello +push: + make src + echo '' > NEWINSTALL + hg push + bzr push bzr+ssh://mdipierro@bazaar.launchpad.net/~mdipierro/web2py/devel --use-existing-dir + ADDED README Index: README ================================================================== --- README +++ README @@ -0,0 +1,1144 @@ +## INSTALLATION INSTRUCTION - IMPORTANT + +To start web2py there is NO NEED to install it. Just unzip and do: + +> python web2py.py + +Thats is it!!! + +## web2py file structure + +start web2py with: + + python web2py.py + +`` + \project + README + LICENSE + TODO + Makefile ## make all and make app + web2py.py ## the startup script (*) + parameters.py ## created at startup + admin.tar ## the admin app (*) + examples.tar ## examples and documentation app (*) + welcome.tar ## the welcome app (entry point) (*) + \gluon ## the core libraries (*) + \deposit ## used for zip and install apps + setup_app.py ## used by py2app to make OSX executable + setup_exe.py ## used by py2app to make Winows executble + wsgihandler.py ## to use Gluon with mod_wsgi + \dist ## used by py2app, py2exe + \build ## used by py2app, py2exe + \tests ## under development stuff + \docs ## in progress documentation + \applications ## are the apps + \welcome ## application welcome, for example + \models + \views + \controllers + \sessions + \errors + \cache + \static + \uploads + \modules +`` + +## EWF v1.5 -> v1.6 +- load and save .py in ascii, avoids problem with LF+CR on windows +- added path.join in compileapp, fixed problem with Windows compileapp + +## EWF v1.6 -> v1.7 +- in paths replace '\' with '/' to fix problem with windows paths +- using limitby in database administration +- replaced mime/miltupart with multipart/form-data to fix a windows problem + +## EWF v1.7 -> Gluon v1.0 +- Name change +- Improved layout.html + +## Gluon v1.0 -> v1.1 +- bug in sqlhtml with JOINS queries + +## Gluon v1.1 -> v1.2 +- fixed some typos in examples +- IS_IN_SET now supports labels +- cleanup in sql.py does not cleanup, just checks valid field and table names + +## Gluon v1.3 +- added IS_IN_DB, IS_NOT_IN_DB and updated examples accordingly + +## Gluon v1.4 +- fixed problem with IS_INT_IN_RANGE and IS_FLOAT_IN_RANGE. Now an error in a validator is reported as a ticket. Good validators should not raise Exceptions. +- IS_IN_DB displays "label (id)" +- it can upload files without extension +- migration is now optional (define_table has migrate=False option) + +## Gluon v1.5 +-
-> in errors.html +- replace('//','////') in sub in template.py + +## Gluon v1.8 +- no more chdir (thread unsafe) +- no more sys.stdout (thread unsafe) +- response.body is StringIO() +- admin/default/site informs about upgrade +- response.locker (optional) + +## Gluon v1.9 +- allow "count(*)" in select +- db.execute() +- fixed problem with continue and return in template +- removed try: ... except in sql.py +- fixed '\t' + +## Gluon v1.10 +- fixed concurrency problems with SQLDB._instances and SQLDB._folders, now use lock +- now, by default, edit SQLFORMs retain uploaded files + +## Gluon v1.11 +- appadmin allows to keep or delete uploaded files + +## Gluon v1.12 +- in sql.py +- handles NULL values properly +- unicode support (data always stored in utf-8) +- 'date' -> datetime.date ,'time' -> datetime.time, 'datetime' -> datetime.datetime, 'boolean' -> True/False +- most types have default validators +- SQLField(...,required=True) option. +- SQLRows has __str__ that serializes in CSV and xml() that serializes in HTML +- SQLTable has import_from_csv_file(...) +- gluon.simplejson for AJAX +- in validators.py +- IS_IN_DB(db,..) - db can be an SQLSet or an SQLDB +- better error messages +- in admin +- new import/export in csv, update and delete interface. +- in appadmin +- edit form allows to keep stored encrypted password +- in main.py +- http://host not defaults to http://host/init/default/index +- New third party modules +- gluon.simplejson(.dumps, .loads) +- gluon.pyrtf(.dumps) +- gluon.rss2(.dumps) + +## Gluon v1.13 +- (this is one of the biggest revisions ever) +- Improved sql.py has support MySQL, cxOracle (experimental), extract, like and better testing +- SQLDB.tables and SQLTable.fields are now SQLCalableList objects +- Fixed bug with editing integer fields storing zero +- Admin interface now says "insert new [tablename]" and display insert, select or update properly in the title. +- Added a cache mechamism. Works for data, controllers, views and SQLRows. +- main.py now uses a request.folder absolute path when not os.name in ['nt','posix']. Seems to work on windowsce devices, except no file locking has consequences. +- Now you can put modules in applications/[anyapp]/modules and import them with +- import applications.[anyapp].modules.[module] as [module] +- Fixed problem with init +- New applications/examples/controller/global.py controller for docs. + +## Gluon v1.14 +- Fixed a bug fix in URLs + +## Gluon v1.15 +- New try:... except. in gluon/main.py for when sessions cannot be saved +- Now validator/formatter method allows IS_DATE('%d/%m/%Y') + +## web2py v1.16 +- yes we changed the name! Turns out Gluon was trademarked by somebody else. +- Although we are not infringing the trademark since this is a non-commercial +- product we could have run into some issues. So we have been professional +- and changed the name to web2py. +- Now SQLFORMs and FORM can have a formname and multiple forms are allowed +- per page. +- A new examples/default/index page. +- web2py.py instead of runme.py +- mysql sets utf8 encoding. +- input integer field values are automatically converted int(). + +## web2py v1.17 +- I posted v1.16 too soon. v1.17 was released after 1h to fix some bugs. + +## web2py v1.18 +- removed vulnerability in accept_languages and session_id +- Minor bug fixes. Typos and cleanup cache. Textarea now clears. +- Support for PyAMF. +- T returns a class, not a string +- new template parser (faster?) +- got rid of sintaxhighlighter in favor of server side CODE +- fix problem with cacheondisk locking +- fix 'None' instead of NULL in IS_NOT_IN_DB (I think) +- gluon.contrib.markdown +- notnull and unique in SQLField now supported (tested on sqlite mysql and postgresql) +- Storage now has __getstate__ and __setstate__ needed for pickling. +- session files are now locked to make it work better with asynchronous requests +- cxoracle should work, apart for limitby +- .../examples is now mapped to .../examples/default/index etc. +- .../init is now mapped to .../welcome if init is not present + +## web2py 1.19 +- minor typos + +## web2py 1.20 +- new IFRAME, LABEL, FIELDSET validators +- P(..cr2br=True) option +- FORM and SQLFORM have hidden=dict(...) option for REST +- testing framework. +- improved examples pages + +## web2py 1.21 +- replaced paste.httpserver with cherrypy.wsgi server +- temporary sessions are no longer saved +- widget has [stop] button and graph +- logging is done by main by appfactory +- fixed a bug in sql belongs + +## web2py 1.22-1.25 +- fixed minor bugs, added IS_NULL_OR + +## web2py 1.26 +- added shell.py (thanks Limodou!) +- added memcache support + +## web2py 1.27 +- IS_NULL_OR now works will all fields +- admin creates paths to static files +- wsgiserver options are passed to HttpServer +- faking limitby for oracle to make appadmin work +- all objects inherit from object +- fixed bug in app names with . +- fixed bug in created RestrictedError object on windows +- shell is now in gluon and accessible via web2py.py + +## web2py 1.28 +- fixed bug with belongs, faster sql.py +- included jquery.js +- minor aestetical fixes +- sortable.js is gone + +## web2py 1.29 +- Now selet mutliple works with get, so does is IS_LENGTH +- Added IS_LIST_OF +- fixed problem with admin from windows and localhost + +## web2py 1.30 +- added flv to contenttype +- added support for appengine + +## web2py 1.31-1.41 +- some bug fixes, mostly better appengine support +- mssql support +- firebird support +- widgets support +- connection pools + +## 1.42 +- fixed security issue by removing slash escape in mysql +- removed random everywhere +- use uuid for session and tickets +- use http_x_forward_for to figure out the client causing a ticket +- use longtext and longblob for mysql +- main now really catches all exceptions +- no more warnings on GAE + +## 1.43-1.48 +- html.py rewrite (better support for custom forms) (Bill Ferrett) +- new stickers in examples (thanks Mateusz) +- on windows can run in taskbar (Mark Larsen) +- in admin|edit page link to edit|controller (Nathan Freeze) +- better error codes and routes_onerror (Timothy Farrell) +- DAL support for groupy and having +- DAL support for expressions instead of values +- DAL has experimental Informix support +- fixed bug with non-printable chars in DAL +- 'text' fields limited to 2**16 (to avoid mysql problems) +- widget has -quiet and -debug (Attila Csipa) +- web2py_session uses BLOB instead of TEXT +- improved IS_URL +- Runs with python 2.6 (Tim) +- On GAE uses GAE for static files (Robin) + + +## 1.49 +- fixed a bug with taskbar widget, thanks Mark +- fixed a bug with form.latest +- made many DIV methods private (_) + + +## 1.50 +- Fixed some bugs introduced in 1.49 + +## 1.51 +- Fixed more bugs introduced in 1.49 (sql _extra and html select) +- support for sqlite:memory: + +## 1.52 +- Fixed a minor bug with _extra[key] and key not str. +- check for upgrade via ajax + +## 1.53 +- On GAE upload data goes automatically in datastore (blob created automatically) +- New appadmin runs on GAE (most of it, not all) +- Martin Hufsky patch allow slicing of fields in DAL expressions + +## 1.54 +- fixed minor bugs + +## 1.55? +- rowcount +- fixed bug when IS_IN_DB involved multiple fields on GAE +- T.set_current_languages +- better unittests +- response.custom_commit and response.custom_rollback +- you can next cache calls (like cache a controller that caches a select). Thanks Iceberg +- db(....id==None).select() no longer returns an error but an empty SQLRows on GAE +- db(...).delete(delete_uploads=True) and SQLFORM.accepts(....delete_uploads=True) will delete all referenced uploaded files +- DIV.element and DIV.update +- sqlrows.json() +- SQLFORM.widgets +- URL(r=request,args=0) +- IS_IN_DB(...,multiple=True) for Many2Many (sort of) +- In URL(...,f) f is url encoded +- In routes_in=[['a/b/c/','a/b/c/?var=value']] +- simplejson 2.0.7 + + +## 1.56 +- Consider the following table: + +- db.define_table('cirlce', +- db.Field('radius','double'), +- db.Field('area','double'), +- db.Field('modified_on','datetime')) + +- now you can do: + +## add a comment in the forms +- db.circle.area.comment="(this is a comment)" + +## do not show area in create/edit forms +- db.circle.area.writable=False + +- ## do not show now in display forms +- db.circle.modified_on.readable=False + +## automatically timestamp when record cretaed +- db.circle.modified_on.default=request.now + +## automatically timestamp when record is modified +- db.circle.modified_on.update=request.now + +## make the radius appear in bold in display and table +- db.circle.radius.represent=lambda value: B(value) + +## make a form that automatically computes area +- pi=3.1415 +- form=SQLFOM(db.circle) +- if form.accepts(request.vars, +- onvalidation=lambda form: form.vars.area=pi*form.vars.radius**2): ... + +## make a create form in two possible ways: +- form=SQLFORM(db.circle) +- form=SQLFORM(db.circle,0) + +## make an update form in two possible ways: +- form=SQLFORM(db.circle,record) +- form=SQLFORM(db.circle,record_id) + +## make a display form in two possible ways: +- form=SQLFORM(db.circle,record,readonly=True) +- form=SQLFORM(db.circle,record_id,readonly=True) + +## so now you can do... + +- form=SQLFORM(db.circle,request.args[-1]) + +- and you get a create form if the URL ends in /0, you get an update +- form if the URL ends in /[valid_record_id] + +## you can also define once for all + +- timestamp=SQLTable(None,'timestamp', +- SQLField('created_on','datetime', +- writable=False, +- default=request.now), +- SQLField('modified_on','datetime', +- writable=False, +- default=request.now,update=request.now)) + +## and use it in all your tables + +- db.define_table('mytable',db.Field('somefield'),timestamp) + +## ## ## + +- One more feature in trunk.... + +- db.define_table('image',SQLField('file','upload')) + +- db.image.file.authorize=lambda row: True or False + +- then controller +- def download(): return response.download(request,db) +- id' is now a hidden field sqlform +- gql references converted to long +- admin login has autofocus +- new notation proposed by Robin, db.table[id] +- new UploadWidget shows images +- new generic.html shows request, response, session +- new LEGEND helper (thanks Marcus) +- fixed doctests in sql (thanks Robin) +- new notation for DB + +- record=db.table[id] +- db.table[id]=dict(...) +- del db.table[id] + +- request.env.web2py_version +- new class gluon.storage.Settings has lock_keys, lock_values +- jquery 1.3.1 +- PEP8 compliance +- new examples application +- runs on jython (no database drivers yet, thanks Phyo) +- fixed bugs in tests +- passes all unittest but test_rewite (not sure it should pass that one) + +- Lots of patches from Fran Boone (about tools) and Dougla Soares de Andarde (Python 2.6 compliance, user use of hashlib instead of md5, new markdown2.py) + +## 1.56.1-1.56.4 +- fixing lots of small bugs with tool and languages +- jquery.1.3.2 + +## 1.57 +- New ajax edit with keepalive (no longer logged out when editing code) +- Fixed conflict resolution page. +- Removed /user/bin/python from models/controllers + +## 1.58 +- Fixed some CRON bugs +- Fixed a bug with new ajax edit +- Experimental DB2 support in DAL +- Customizable font size in admin edit page +- New welcome/models/db.py shows how to memcache sessions on GAE with MEMDB +- More expressive titles in admin +- DB2 support. Thanks Denes! + +## 1.59-1.60 +- fixed lots of small bugs +- routes_in can filter by http_host + +## 1.61 +- fixed some typos +- auth.add_permissions(0,....) 0 indicates group of current user +- crud.update has deletable=True or False +- fixed issue with GAE detection -> gluon.settings.web2py_runtime -> request + +## 1.62 +- SQLFORMS and crud now show readble fields +- Better WingIDE support +- Languages are automatically translated +- T.force and lazyT works better, optional T.lazy=False +- gluon.storage.Messages are now translated without T +- if routes.py then request.env.web2py_original_uri +- db.table.field.isattachment = True +- internationalizaiton of admin by Yair +- admin.py by Alvaro +- new MENU helper +- new w2p file format +- new welcome app with auth, service and crud turned on + +## 1.63-1.63.4 +- no more import gluon. +- support for generic.xxx +- simplejson can handle datetime date and time + +## 1.63.5 +- You can do jQuery.noConflict() without breaking web2py_ajax +- Wigets can have attributes (thanks Hans) +- Lots of internal cleanup and better code reusage (thanks Hans) + +## 1.64 +- Models 2-3 times faster (thanks Alexey) +- Better LDAP support +- Works with Jython (including sqlite and postgresql with zxJDBC): + +- download jython-2.5rc3.jar +- download qlite-jdbc-3.6.14.2.jar +- java -jar jython-xxx.jar +- export CLASSPATH=$CLASSPATH:/Users/mdipierro/jython2.5rc3/sqlite-jdbc-3.6.14.2.jar +- cd web2py +- ../jython2.5rc3/jython web2py.py + +## 1.64.2 +- New IS_COMPLEX validator, thank Mr. Freeze +- Experimental Informix support +- Autologin on registration + +## 1.64.3 +- Some bug fixes + +## 1.64.4 +- Som bug fixes +- Informix Support +- response.render(stream) +- SQLFORM.factory +- SQLFORM.widgets.radio and SQLFORM.widgets.checkboxes + +## 1.65 +- reST docstrings for Sphinx, thanks Hans +- gluon/conrtib/login_methods/gae_google_account.py for google CAS login on GAE, thanks Hans +- fixed problem with Auth and Firebird 'password' issue +- new auth.settings.create_user_groups +- tickets stored on datastore on GAE and also logged, thanks Hans +- imporved IS_LENGTH with max and min, thanks Mateusz +- improved IS_EMAIL with filters, thanks Mateusz +- new IS_IMAGE checks for format and size, thanks Mateusz +- new IS_IPV4, thanks Mateusz + +## 1.65.1 +- spreadsheet +- shell history, thanks sherdim +- crontab editor, thanks Angelo +- gluon/contrib/login_methods/cas_auth.py (thanks Hans) +- DAL(...) instead of SQLDB(...) +- DAL('gae') instead of GQLDB() +- Field instead of SQLField +- (the old syntax still works) + +## 1.65.2 +- Fixed some small auth bugs +- Field.store(...) + +## 1.65.3-10 +- Fixed some small bugs and typos in the docstrings +- Fixed AMF3 support + +## 1.65.11 +- Fixed a sqlhtml bug with image upload + +## 1.65.12 +- lables in auth auto-translate (thanks Alvaro) +- better ldap_auth (thanks Fran) +- auth chacks locally for blocked accounts even for alternate login methods (thanks Fran) + +## 1.65.13 +- request.url (thanks Jonathan) +- restored uploadfield_newfilename +- new examples layout nad logo (thanks Mateusz) + +## 1.66 +- new doctypes +- form.vars.newfilename +- new HTML and XHTML helpers +- better IS_LENGTH + +## 1.67.0 +- Python 2.4 support (again) +- New layout for welcome +- changed defauld field sizes to 512 +- Field(uploadfolder="...") +- appadmin works on GAE (again, somehting got broken at some point) +- new wsgiserver 3.2.0 should fix recurrent broken download problems + +## 1.67.1 +- Bux fixed + +## 1.67.2 +- Security fix in markdown + +## 1.68.1 +- New official markdown with security fix +- rows.first() +- rows.last() +- New cron +- New hindi and spanish translation +- cached uploads allow for progress bars (thanks AndCycle) +- ingres support (thanks Chris) +- legacy database support for db2, mssql with non-int primary keys (thanks Denes) +- default setting of content-type (this may cause strange behavior in old apps when downloading images) +- IS_UPPER and IS_LOWER works with unicode +- CLENUP not takes regex of allowed/now allowed chartares +- New rewrite.py allows dynamic routes +- Better error messages for IS_INT_* and IS_FLOAT_* + +## 1.68.2 +- Fixing bug with admin and missing crontab +- Fixing bug with rewrite.load on GAE (thanks Willian Wang) + +## 1.69.1 +- Fixed a bug introduced in 1.68 about inserting unicode in DAL +- Fixed other small bugs +- Better support for legacy databases (thank Denes) +- response.meta replaces response.author, response.keywords, response.description +- response.files stets dependes in plugins +- better admin for packing/unpacking plugins +- reference fiels nor evaluate to DALRef with lazy evaluation (cool, thanks Mr Freeze) +- can insert a record in place of a reference +- record[e] instead of record._extra[e] (tentatively!) +- record.update_record() with no args +- rows.find() (thanks Mr Freeze) +- rows.exclude() +- rows.sort() +- rows[:] + +## 1.70.1 +- Fixed bug with Rows.as_list and DALRef +- Added Rows.as_dict (thanks Mr Freeze and Thedeus) +- Added request.wsgi (thanks hcvst) allows running wsgi apps under web2py and applying wegi middleware to regular web2py actions that return strings. +- Experimental distributed transactions between postgresql, mysql and firebird +- Finally local_import is here! + +## 1.71.1 +- Complete rewrite of Rows +- renamed DALStorage->Rows, DALRef->Reference +- Experimental serializarion of Row and Rows (get serialized to dict and list of dict) +- DAL(...,folder) and template.render(content=, context=) make it more modular + +## 1.72.1 - 1.72.3 +- Better support for legacy databases + +## 1.73.1 +- Fixed problem with storage and comparison of Row objects +- Fixed problem with mail on GAE +- Fixed problem with T in IS_DATE(TIME) error_message and format +- Rows[i].delete_record() +- Even better support for legacy databases +- Experimantal support for non UTF8 encoding in DB +- Better IPV4 (thanks Thandeus) +- T.current_languages default to 'en' and new T.set_current_languages(...) (thanks Yarko) +- INPUT(...,hideerror=False) used to fix rare chechbox widget problem +- Admin allows change of admin password +- New gluon/contrib/populate.py +- Size of input/textarea set by CSS no more by jQuery (thanks Iceberg) +- Customizable CSV (thanks Thandeus) +- More bug fixed (thanks Thandeus) +- Better regex for template fixed Jython problem (thank Jonathan) + +## 1.74.1 +- Moved to mercurial +- Default validators use the new define_table(....,format='...') +- New get_vars and post_vars compatible in 2.5 and 2.6 (thanks Tim) +- Major rewrite of gql.py extends DAL syntax on GAE +- No more *.w2p, welcome.w2p is create automatically, base apps are always upgraded +- export_to_csv(delimiter = ',', quotechar = '"', quoting = csv.QUOTE_MINIMAL), thanks Thadeus + +## 1.74.2-4 +- Fix bugs including including unicode in emails and blobs on GAE + +## 1.74.5 +- bug fixes +- restored python 2.4 support,thanks ont.rif +- support for native types on Google App Engine +- cache.ram usage statictics, thanks Thadus +- no more auth manu in scaffolding +- no more spash screen with -Q +- fixed doctest in html.py, thanks Anand Vaidya +- export_to_csv_file has represent, thanks Thadeus + +## 1.74.6 +- bug fixes +- IS_IN_DB(...,_and=IS_NOT_IN_DB) +- Smaller populate, thanks Piotr +- better slicing of fields, thanks Michael Fig +- Cache stats, thanks Thadeus +- Better gql.py +- IS_IN_DB and IS_IN_SET default to zero='', no longer zero=None + +## 1.74.7 +- request_password_reset and password reset verification +- python web2py.py -S app -M -R script.py -A arg1 arg2 arg3 +- T("%(a)s") % dict(a="hello") + +## 1.74.8 +- IS_SLUG, thanks Gustavo and Jonathan +- web2py.py -nogui, thanks Jeff Bauer +- solved a problem with jython, thanks Tim Farrel +- login has "remember be option", thanks Piotr Banasziewicz +- fixed problem with keepvalue in update forms, thanks Miguel Lopez + +## 1.74.9 +- IS_IN_SET(((0,'label0'),(1,'label1'))), thanks Falko Krause +- SQLFORM(...).accpets stores True or False in boolean types no None, thanks Frederik Wagner +- SQLFORM.factory(...,table_name='no_table'), thanks Thedeus +- jQuery 1.4.1 +- Fixed major problem with internationalization of multiple languages. +- Fixed a serius security issue with login +- Possibly fixed some issues with cron + +## 1.75.1 +- better cron +- better fetch +- logging of email failures +- new web2py.fedora.sh +- new setup-web2py-ubuntu.sh +- experimental autocomplete +- menus work on IE6 + +## 1.75.2 +- fetch supports cache +- curd.update(....,onaccept=crud.archive) magic +- new UUID mechnism fixes session conflicts with cloned machine in cloud +- allow to upload app and overwrite existing old ones, thanks Jonathan +- print gluon.tools.prettydate(request.now,T), thanks Richard + +## 1.75.3 +- added support for PAM authentican for apps and for admin +- INTRODUCED MAJOR BUG IN BEAUTIFY (upgrade to 1.75.4) IMMEDIATELY + +## 1.75.4 +- customizable BEAUTIFY, thanks John + +## 1.75.5 +- fixed behaviour with languages.py, thanks Iceberg +- added chinese (thanks Iceberg) and Hungarian (thanks Gyuris) +- fixed problem with GAE deleted by id (thanks what_ho) +- fixed bug in LOAD with custom views, thanks vhang +- improved IS_IN_SET takes iterator, dict, list and list of tuples, thanks Iceberg +- Auth(...,controller='default') +- Fixed major bug in parsing repeated request.vars, thanks Ben +- IS_DATE and IS_DATETIME can now handle any 00).select(),headers='fieldname:capitalize') +- Oracle limitby improved (thanks Sergey) +- fixed migrations in Firebird, thanks Jose Jachuf +- gluon/contrib/login_methods/linkedin_account.py (to be tested) + +## 1.76.5 +- Fixed a typo in auth that created some registration problems + +## 1.77.1 +- Replaced CherryPy with Rocket web server, thanks Tim +- CacheOnDisk allows to specify a folder +- IS_DATE/DATETIME can handle any year since 0 +- SQLTABLE(...,headers='fieldname:capitalize') +- Field().with_alias, thanks Nathan and Mengu +- has_membership(group=...,role=...), thank Jonathan +- db.define_table(username=True), thanks Jonathan +- gluon.tools.prettydate +- can specify hostname in routes_out (same syntax as routes in), thanks Martin +- db.table.bulk_insert([...records...]) now works on GAE, thanks Jon +- IS_EMAIL validates on 'localhost', thanks Jonathan +- welcome/views/layout.html uses ez.css, thanks Yarko +- mail attachments support utf8, thanks szimszon +- works with PyPy, thanks Joe +- better Firebird support, thanks Jose +- better Oracle support, thanks Gabriele +- cron supports days of week +- SQLFORM(...,formstyle="table3cols") or "table2cols" or "divs" or "ul" +- crud.settings.formstyle +- web2py.py -f folder allows to specify locations of applications, thanks Iceberg +- better/faster regex in template works in Jython +- fixed lots of small bugs + +## 1.77.2 +- fixed x-index in calendar +## 1.77.3 +- some cleanup of code in compileapp + +## 1.78.1 +- new template system allows {{block name}}{{end}}, thanks Thadeus +- fixed mime headers in emails, included PGP in emails, thanks Gyuris +- automatic database retry connect when pooling and lost connections +- OPTGROUP helper, thanks Iceberg +- web2py_ajax_trap captures all form submissions, thank you Skiros +- multicolumn checkwidget and arbitrary chars in multiple is_in_set, thanks hy +- Québécois for welcome, thanks Chris +- crud.search(), thanks Mr Freeze +- DAL(...migrate,fake_migrate), thanks Thadeus + +## 1.78.3 +- reverted temporarily to old template system because of backward compatibility issues + +## 1.79.1 +- x509 emails, thanks Gyuris +- attachment and html in Mail on GAE, thanks PanosJee +- fixed docstring in SQLTABLE, thanks aabelyakov +- TAG(html) parese html into helpers (experimental, still some problems with unicode, , thanks RobertVa for unicode help) +- DIV.elements(find=re.compile(....)) +- DIV.flatten() +- DIV.elements('....') supports jQuery syntax in '....' +- better it-it.py and it.py, thanks Marcello Della Longa +- Many Bug fixes: +- improved support for DAL and joins in postgresql/oracle, thanks Nico de Groot +- bux fixex in html.py, thanks Ian +- fixed an issue with registration_key==None, thanks Jay Kelkar +- fixed bug in gql.py, thanks NoNoNo +- fixed problem with multiple and checkboxes, thanks MIchael Howden +- fixed bug in gae, thanks NoNoNo +- restored 2.4 compatibility, thanks Paolo Gasparello +- auth.update() when pictures in profile +- formstyle can be a function, thanks Howden +- escape in sanitizer, thanks Howes +- add missing settings, thanks Hamdy +- find and exclude return empty Rows instead of [], thanks iceberg +- simplejson 2.1.1 should fix compatibility problems +- added sms_utils and Authorize.net in contrib + +## 1.79.2 +- solved simplejson imcompatibility problem + +## 1.80.1 +- MARKMIN helper (no backward compatibility promise yet) +- self._last_reference, thanks Dave (no backward compatibility promise yet) +- IS_EQUAL_TO +- zh-tw and better internationalization page, thanks Daniel Lin and Iceberg +- better crud.search, thanks MrFreeze +- Rocket interfaces, thanks Nik Klever +- db.table.field.uploadseparate=True, thanks Gyuris +- SCOPE_IDENITY for MSSQL, thanks Jose +- fixed email attachment issue, thanks Bob_in_Comox +- fixed problem with groupby and IS_IN_DB +- other bug fixes +- new implementation for local_import +- ajax(..,...,null) +- fixed Chrome bug in calendar.js, thanks Iceberg +- experimental scrips/web2py-setup-fedora.sh +- generic.load, thanks Iceberg + +## 1.81.1 +- rpx (janrain) support out of the box, allows login with Facebook, MySpace, etc. Thanks Mr Freeze +- Increased security (escape single and double quotes, thanks Craig" +- Fixed a bug with db.table.field.uploadseparate=True and autodelete +- New welcome app with superfish and jQuery 1.4.2 +- Deleted openwysiwyg from admin +- In XML and xmlescape quote defaults to True. Both ' and " are escaped. Thanks Craig Younkins + +## 1.81.2 +- fixed bug in Auth + +## 1.81.3 +- fixed bug in label names in formstyle +- fixed id names in admin test.html page + +## 1.81.4 +- Fixed gluon.tools to work work with load and base.css to nowrap labels + +## 1.81.5 +- Fixed a few bugs. The most important bugs we fixed are in memcache (thanks Scott) and in a process starvation issue with Rocket (thanks Mike Ellis and Tim). + +## 1.82.1 +- request.ajax to detect if action is called via ajax, tahnks Jonathan and David Mako +- more captcha options, thanks Vidul +- openid and oauth2 thanks Michele and Keith +- better PluginManager and load components +- new template system, thanks Thadeus +- new db.table(id,[field=value]) and db.table(query) syntax +- URL('index') (no more r=request), thanks Thadeus +- mail.send(message='...', ....) +- DAL([....]) for load balancing +- @service.soap(...) with mysimplesoap, thanks Mariano +- hideerror + +## 1.83.1 +- New error reporting mechanism (thanks Mariano) +- New routing system with app level routing (thanks Jonathan) +- Integrated GAE appstat and GAE precompilation (thanks Scott) +- New Field types "list:string", "list:integer", "list:reference" +- request.cid, request.ajax, A(cid=request.cid), response.js + +## 1.83.2 +- mostly cleanup + +## 1.84.1-4 +- flash now stays put in the top right corner +- improved behavior for URL and T objects +- new app level logging with logging.conf (thanks Jonathan) +- improved OpenID (thanks Michele) +- web2py_ajax handles prepend, append, hide (thanks Juris) +- web2py_ajax also handels pre-validation of decimal fields +- ru-ru translation (thanks Michele) +- sk-sk translation (thanks Julius) +- migrations save .table only if changed and after each ALTER TABLE (no more mysql inconsistencies) +- fixed bugs in SQLCustomField, Field(default=...), IS_IMAGE, IS_DECIMAL_IN_RANGE and a few more. +- Better validators (IS_DECIMAL_IN_RANGE, IS_INT_IN_RANGE, etc) thanks Jonatham +- Polymmodel support on GAE +- Experimental ListWidget +- moved DAL and routes to thread.local (thanks Jonathan, again) +- scripts/extract_mysql_models.py, thanks Falko Krause and Ron McOuat +- scripts/dbsessions2trash.py, thanks Scott + +## 1.85.1-3 +- fixed some bugs +- added pyfpdf, thank Mariano + +## 1.86.1-1.86.3 +- markmin2latex +- markmin2pdf +- fixed some bugs +- Storage getfirst, getlast, getall by Kevin and Nathan +- db(db.table), db(db.table.id) both suported and equivalent to db(db.table.id>0) +- postresql ssl support +- less un-necessary logging and warnings on GAE +- IS_DECIMAL_IN_RANGE and IS_FLOAT_IN_RANGE support dot="," (dot="." is default) +- on_failed_authorization can be a function, thanks Niphold +- gluon/contrib/login_methods/cas_auth.py for integration between CAS and Auth. + +## 1.86.3 +- Error reporting on save, thanks Mariano +recalled + +## 1.87.1-2 +- new layout for examples, thanks Bruno and Martin +- admin allow ``DEMO_MODE=True`` and ``FILTER_APPS=['welcome']`` +- fixed a possible problem with CRON and paths + + +## 1.87.3 +- fixed a major bug introduced in 1.87.1 that prevents appadmin from working for new apps created with 1.87.1-2. +- upgraded to clockpick 1.28, thanks villas + +## 1.88.1-2 +- better list: string support, thanks Bob +- jquery 1.4.3 +- scripts/autoroutes.py +- new admin wizard +- added retrieve_username to navbar (if username) +- internal rewrite for arbitrary paths (abspath), thanks Jonathan +- populate support for list: and decimal, thanks Chirstian +- markmin2latex has extra attribute +- better mercual admin allows list of files, versions and retrieve +- new error report system, thanks Thadeus and Selecta +- SQLFORM.accepts(detect_record_change).record_changed +- fixed cron for bytecode compiled apps, thanks Álvaro J. Iradier Muro +- other bugs fixes and pep8 compliant fixes + +## 1.89.1-.5 +- new admin layout (thanks Branko Vukelic) +- new admin search +- new admin language selector (thanks Yair) +- new Welcome app (thanks Martin Mulone) +- better wizard +- admin support for DEMO_MODE=True +- admin exposes GAE deployment button (always) +- MENU support None links (thanks Michael Wolfe) +- web2py.py -J for running cron (thanks Jonathan Lundell) +- fixed ~db.table.id on GAE (thanks MicLee) +- service.jsonrpc supports service.JsonRpcException (thanks Matt) +- bug fixes + +## 1.90.1 +- new DAL (complete rewrite of the web2py DAL is more modular) +- rewrite has fail safe reload, thanks Jonathan +- better CAS with v2 support, thanks Olivier ROCH VILATO +- better markmin2latex +- session.connect(separate=True) to handle many session files, thanks huaiyu wang +- changed bahvior of impersonate (more secure, can generate form or used as API) +- new rocket, thanks Tim +- new pyfpdf +- no more old style classes +- experimental couchdb support in new dal (only insert, select, update by id) +- mysql support out of the box via pymysql +- SQLITABLE(...,headers='labels') thanks Bruno +- optional: digitally signed URLs, thanks Brian Meredyk +- minor bug fixes + +## 1.90.2-4 +- pymysql no longer requires ssl (if not used) +- fixed bug with virtualfields +- fixed bug in truncate (new dal) +- fixed bug in select with alternate primary key (new dal) +- fixed bug with IS_IN_DB and self refences (also new dal) + +## 1.90.5 +- set poll = False in rocket because of poll python thread bug often unfixed, thanks Jonathan +- fixes issue with crud and reCaptcha + +## 1.90.6 +- fix issue with pickling new dal Row and Rows. + +## 1.91.1 +- LICENSE CHANGE FROM GPLv2 to LGPLv3 +- URL(...,hash_vars=...) allows to specify which vars need to be signed +- fixed bug with aliasing in new DAL + +## 1.91.2-1.91.5 +- fixed a problem with deplyment on GAE +- other new dal bug fixes + +## 1.91.6 +- web2py comet via gluon/contrib/comet_messaging.py (html5 websockets) experimental +- fixed problem with services (broken in 1.91.5), thanks Vollrath +- customizable uploadwidget, thanks Fran +- fixed problem with mail unicode support, thanks Richard +- fixed problem with linkto=None and references fields in SQLTABLE, thanks villas +- no more upgrade button on windows since does not work +- better remember-me login, thanks Martin Weissenboeck +- support for recatcha options +- support for GAE namespaces via DAL('gae://namespace') +- new rocket (1.2.2), thanks Tim +- many other bug fixes and improvements (thanks Jonathan) + +## 1.92.1 +- much improved routing (thanks Jonathan) +- Expression.__mod__ (thanks Denes) +- admin has MULTI_USER_MODE (admin/models/0.py) +- support for count(distinct=...) +- has_permissions(...,group_id) +- IS_MATCH(...,strict=True) +- URL(...,scheme=,host=,port=), thanks Jonathan +- admin in Afrikaans, thanks Caleb +- auth.signature (experimental) +- many other bug fixes + +## 1.93.1-2 +- support for multiple interfaces, thanks Jonathan +- jquery 1.5.1 +- simplejson 2.1.3 +- customizable simplejson +- leaner app.yaml +- css3 buttons in welcome +- android support (experimental) +- Field(':hidden'), Field('.readonly'), Field('name=value') +- combined expressions print db.data.body.len().sum() +- wizard can download plugins +- better json serilization (object.custom_json) +- better xml serialization (object.custom_xml) +- better formstyle support +- better comet_messaging.py (needs more testing) +- many bug fixes + +## 1.94.1 +- moderniz 1.17 +- web2py no longer saves session if no change, this makes it up up to 10x faster for simple actions +- experimental REST API +- better support for MSSQL NOT NULL +- small bug fixes + +## 1.94.2 +- reverted wrong behavior of auth.requires(condition) in 1.94.1 + +## 1.94.3 +- fixed major bug in auth redirection + +## 1.94.4 +- removed debug print statement that caused problems on GAE and mod_wsgi + +## 1.94.5 +- fixed a major bug with session introdued in 1.94.1 + +## 1.94.6 +- fixed a number of minor bugs including adding some missing files +- better session handling on session._unlock(..), thanks Jonathan +- added experimental pip support, thanks Lifeeth +- added experimental SAP DB support + +## 1.95.1 +- Google MySQL support (experimental) +- pip support, thanks lifeeth +- better setup_exe.py, thanks meredyk +- importved pyfpdf +- domain check in email_auth.py, thanks Gyuris +- added change_password_onvalidation and change_password_onaccept +- DAL(...,migrate_enabled=True) +- login_methods/loginza.py, thanks Vladimir +- bpython shell support, thanks Arun +- request.uuid and response.uuid (for a future toolbar) +- db._timings contains database query timing info +- efficient db(...).isempty() +- setup-web2py-nginx-uwsgi-ubuntu.sh +- Many bug fixes, thanks Jonathan + +## 1.96.1 + +- "from gluon import *" imports in every python module a web2py environment (A, DIV,..SQLFORM, DAL, Field,...) including current.request, current.response, current.session, current.T, current.cache, thanks Jonathan. +- conditional models in + models//a.py and models///a.py +- from mymodule import *, looks for mymodule in applications/thisapp/modules first and then in sys.path. No more need for local_import. Thanks Pierre. +- usage of generic.* views is - by default - restricted to localhost for security. This can be changed in a granular way with: response.generic_patterns=['*']. This is a slight change of behavior for new app but a major security fix. + +- all applications have cas 2.0 provider at http://.../user/cas/login +- all applications can delegate to login to external provider Auth(...,cas_provider='http://.../other_app/default/user/cas') +- A(...,callback=URL(...),larget='id') does Ajax +- URL(...,user_signature=True), LOAD(...,user_signature=True) can sign urls and @auth.requires_signature() will check the signature for any decorated action. + +- DAL(...,migrate_enabled=False) to disable all migrations +- DAL(...,fake_migrate_all=True) to rebuild all corrupted metadata +- new DAL metadata format (databases/*.table) +- DAL(...,adapter_arg={}) allows support for alternate drivers +- DAL now allows circular table defintions +- DAL(..,auto_import=True) automatically imports tables from metadata without need to db.define_table(...)s. +- new alterante syntax for inner joins: db(...).select(join=...) +- experimental cubrid database support +- DAL 'request_tenant' fields are special, the altomatically filer all records based on their default value. +- db._common_fields.append(Field('owner')) allows to add fields to ALL tables +- DAL ignores repeated fields with same names + +- web2py_ajax.html is more modular, thanks Anthony +- request.is_local +- request.is_http +- new sessions2trash.py thanks Jim Karsten +- corrupted cache files are automatically deleted +- new simpler API gluon.contrib.AuthorizeNet.procss(...) +- fixed recaptcha (as they released new API) +- messages in validators have default internationalization +- No more Auth(globals(),db), just Auth(db). Same for Crud and Service. +- scripts/access.wsgi allows apache+mod_wsgi to delegate authentication of any URL to any web2py app +- json now supports T(...) +- scripts/setup-web2py-nginx-uwsgi-ubuntu.sh +- web2py HTTP responses now set: "X-Powered-By: web2py", thanks Bruno +- mostly fixed generic.pdf. You can view any page in PDF if you have pdflatex installed or if your html follows the pyfpdf convention. +- auth.settings.extra_fields['auth_user'].append(Field('country')) allows to extend auth_* tables without need of definiting a custom auth_* table. Must be placed before auth.define_tables() +- {{=response.toolbar()}} to help you debug applications +- web based shell now supports object modifications (but no redefinitions of non-serializable types) +- jQuery 1.6.1 +- Lots of bug fixes + +# 1.96.2-1.96.4 +- bug fixes + +# 1.97.1 +- validate_and_update, thanks Bruno +- fixed problem with new custom import, thanks Mart +- fixed pyamf 0.6, thanks Alexei and Nickd +- fixed "+ =" bug in wizard +- fixed problem with allowed_patterns +- fixed problems with LOAD and vars and ajax +- closed lots of google code tickets +- checkboxes should now work with list:string +- web2py works on Android, thanks Corne Dickens +- new cpdb.py, thanks Mart +- improved translation (frech in particuler), thanks Pierre +- improved cas_auth.py, thanks Sergio +- IS_DATE and IS_DATETIME validators now work with native types +- better description of --shell, thanks Anthony +- extra SQLTABLE columns, thanks Martin +- fixed toolbar conflics, thanks Simon +- GAE password shows with **** + +# 1.98.1 +- fixed some problems with LOAD(ajax=False), thanks Anthony +- jquery 1.6.2 +- gevent.pywsgi adds ssl support, thanks Vasile +- import/export of blobs are base64 encoded +- max number of login attemts in admin, thanks Ross +- fixed joins with alias tables +- new field.custom_delete attribute +- removed resctions on large 'text fields, thanks Martin +- field.represent = lambda value,record: .... (record is optional) +- FORM.validate() and FORM.process(), thanks Bruno +- faster visrtualfields, thanks Howsec +- mail has ssl support separate from tls, thanks Eric +- TAG objects are now pickable +- new CAT tag for no tags +- request.user_agent(), thanks Ross +- fixed fawps support +- SQLFORM(...,separator=': ') now customizable +- many small bug fixes ADDED VERSION Index: VERSION ================================================================== --- VERSION +++ VERSION @@ -0,0 +1,1 @@ +Version 1.98.2 (2011-08-04 00:47:09) ADDED __init__.py Index: __init__.py ================================================================== --- __init__.py +++ __init__.py @@ -0,0 +1,1 @@ + ADDED anyserver.py Index: anyserver.py ================================================================== --- anyserver.py +++ anyserver.py @@ -0,0 +1,193 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +This file is part of the web2py Web Framework +Copyrighted by Massimo Di Pierro +License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) + +This file is based, althought a rewrite, on MIT code from the Bottle web framework. +""" + +import os, sys, optparse +path = os.path.dirname(os.path.abspath(__file__)) +os.chdir(path) +sys.path = [path]+[p for p in sys.path if not p==path] +import gluon.main +from gluon.fileutils import read_file, write_file + +class Servers: + @staticmethod + def cgi(app, address=None, **options): + from wsgiref.handlers import CGIHandler + CGIHandler().run(app) # Just ignore host and port here + + @staticmethod + def flup(app,address, **options): + import flup.server.fcgi + flup.server.fcgi.WSGIServer(app, bindAddress=address).run() + + @staticmethod + def wsgiref(app,address,**options): # pragma: no cover + from wsgiref.simple_server import make_server, WSGIRequestHandler + class QuietHandler(WSGIRequestHandler): + def log_request(*args, **kw): pass + options['handler_class'] = QuietHandler + srv = make_server(address[0],address[1],app,**options) + srv.serve_forever() + + @staticmethod + def cherrypy(app,address, **options): + from cherrypy import wsgiserver + server = wsgiserver.CherryPyWSGIServer(address, app) + server.start() + + @staticmethod + def rocket(app,address, **options): + from gluon.rocket import CherryPyWSGIServer + server = CherryPyWSGIServer(address, app) + server.start() + + @staticmethod + def rocket_with_repoze_profiler(app,address, **options): + from gluon.rocket import CherryPyWSGIServer + from repoze.profile.profiler import AccumulatingProfileMiddleware + from gluon.settings import global_settings + global_settings.web2py_crontype = 'none' + wrapped = AccumulatingProfileMiddleware( + app, + log_filename='wsgi.prof', + discard_first_request=True, + flush_at_shutdown=True, + path = '/__profile__' + ) + server = CherryPyWSGIServer(address, wrapped) + server.start() + + @staticmethod + def paste(app,address,**options): + from paste import httpserver + from paste.translogger import TransLogger + httpserver.serve(app, host=address[0], port=address[1], **options) + + @staticmethod + def fapws(app,address, **options): + import fapws._evwsgi as evwsgi + from fapws import base + evwsgi.start(address[0],str(address[1])) + evwsgi.set_base_module(base) + def app(environ, start_response): + environ['wsgi.multiprocess'] = False + return app(environ, start_response) + evwsgi.wsgi_cb(('',app)) + evwsgi.run() + + + @staticmethod + def gevent(app,address, **options): + from gevent import monkey; monkey.patch_all() + from gevent import pywsgi + from gevent.pool import Pool + pywsgi.WSGIServer(address, app, spawn = 'workers' in options and Pool(int(option.workers)) or 'default').serve_forever() + + @staticmethod + def bjoern(app,address, **options): + import bjoern + bjoern.run(app, *address) + + @staticmethod + def tornado(app,address, **options): + import tornado.wsgi + import tornado.httpserver + import tornado.ioloop + container = tornado.wsgi.WSGIContainer(app) + server = tornado.httpserver.HTTPServer(container) + server.listen(address=address[0], port=address[1]) + tornado.ioloop.IOLoop.instance().start() + + @staticmethod + def twisted(app,address, **options): + from twisted.web import server, wsgi + from twisted.python.threadpool import ThreadPool + from twisted.internet import reactor + thread_pool = ThreadPool() + thread_pool.start() + reactor.addSystemEventTrigger('after', 'shutdown', thread_pool.stop) + factory = server.Site(wsgi.WSGIResource(reactor, thread_pool, app)) + reactor.listenTCP(address[1], factory, interface=address[0]) + reactor.run() + + @staticmethod + def diesel(app,address, **options): + from diesel.protocols.wsgi import WSGIApplication + app = WSGIApplication(app, port=address[1]) + app.run() + + @staticmethod + def gnuicorn(app,address, **options): + import gunicorn.arbiter + gunicorn.arbiter.Arbiter(address, 4, app).run() + + @staticmethod + def eventlet(app,address, **options): + from eventlet import wsgi, listen + wsgi.server(listen(address), app) + + +def run(servername,ip,port,softcron=True,logging=False,profiler=None): + if logging: + application = gluon.main.appfactory(wsgiapp=gluon.main.wsgibase, + logfilename='httpserver.log', + profilerfilename=profiler) + else: + application = gluon.main.wsgibase + if softcron: + from gluon.settings import global_settings + global_settings.web2py_crontype = 'soft' + getattr(Servers,servername)(application,(ip,int(port))) + +def main(): + usage = "python anyserver.py -s tornado -i 127.0.0.1 -p 8000 -l -P" + try: + version = read_file('VERSION') + except IOError: + version = '' + parser = optparse.OptionParser(usage, None, optparse.Option, version) + parser.add_option('-l', + '--logging', + action='store_true', + default=False, + dest='logging', + help='log into httpserver.log') + parser.add_option('-P', + '--profiler', + default=False, + dest='profiler', + help='profiler filename') + servers = ', '.join(x for x in dir(Servers) if not x[0]=='_') + parser.add_option('-s', + '--server', + default='rocket', + dest='server', + help='server name (%s)' % servers) + parser.add_option('-i', + '--ip', + default='127.0.0.1', + dest='ip', + help='ip address') + parser.add_option('-p', + '--port', + default='8000', + dest='port', + help='port number') + parser.add_option('-w', + '--workers', + default='', + dest='workers', + help='number of workers number') + (options, args) = parser.parse_args() + print 'starting %s on %s:%s...' % (options.server,options.ip,options.port) + run(options.server,options.ip,options.port,logging=options.logging,profiler=options.profiler) + +if __name__=='__main__': + main() ADDED app.example.yaml Index: app.example.yaml ================================================================== --- app.example.yaml +++ app.example.yaml @@ -0,0 +1,88 @@ +# For Google App Engine deployment, copy this file to app.yaml +# and edit as required +# See http://code.google.com/appengine/docs/python/config/appconfig.html +# and http://web2py.com/book/default/chapter/11?search=app.yaml + +application: web2py +version: 1 +api_version: 1 +runtime: python + +default_expiration: "24h" + +derived_file_type: +- python_precompiled + +handlers: + +- url: /_ah/stats.* + script: $PYTHON_LIB/google/appengine/ext/appstats/ui.py + login: admin + +- url: /(?P.+?)/static/(?P.+) + static_files: applications/\1/static/\2 + upload: applications/(.+?)/static/(.+) + secure: optional + +- url: /favicon.ico + static_files: applications/welcome/static/favicon.ico + upload: applications/welcome/static/favicon.ico + +- url: /robots.txt + static_files: applications/welcome/static/robots.txt + upload: applications/welcome/static/robots.txt + +- url: /_ah/admin/.* + script: $PYTHON_LIB/google/appengine/ext/admin + login: admin + +- url: /_ah/queue/default + script: gaehandler.py + login: admin + +- url: .* + script: gaehandler.py + secure: optional + +admin_console: + pages: + - name: Appstats + url: /_ah/stats + +skip_files: | + ^(.*/)?( + (app\.yaml)| + (app\.yml)| + (index\.yaml)| + (index\.yml)| + (#.*#)| + (.*~)| + (.*\.py[co])| + (.*/RCS/.*)| + (\..*)| + (applications/(admin|examples)/.*)| + ((admin|examples|welcome)\.(w2p|tar))| + (applications/.*?/(cron|databases|errors|cache|sessions)/.*)| + ((logs|scripts)/.*)| + (anyserver\.py)| + (web2py\.py)| + ((cgi|fcgi|modpython|wsgi)handler\.py)| + (epydoc\.(conf|css))| + (httpserver\.log)| + (logging\.example\.conf)| + (route[rs]\.example\.py)| + (setup_(app|exe)\.py)| + (splashlogo\.gif)| + (parameters_\d+\.py)| + (options_std.py)| + (gluon/tests/.*)| + (gluon/(rocket|winservice)\.py)| + (contrib/(gateways|markdown|memcache|pymysql)/.*)| + (contrib/(populate|taskbar_widget)\.py)| + (google_appengine/.*)| + (.*\.(bak|orig))| + )$ + +builtins: +- remote_api: on +- datastore_admin: on ADDED appengine_config.py Index: appengine_config.py ================================================================== --- appengine_config.py +++ appengine_config.py @@ -0,0 +1,4 @@ +def webapp_add_wsgi_middleware(app): + from google.appengine.ext.appstats import recording + app = recording.appstats_wsgi_middleware(app) + return app ADDED applications/__init__.py Index: applications/__init__.py ================================================================== --- applications/__init__.py +++ applications/__init__.py ADDED applications/admin/ABOUT Index: applications/admin/ABOUT ================================================================== --- applications/admin/ABOUT +++ applications/admin/ABOUT @@ -0,0 +1,6 @@ +web2py is an open source full-stack framework for agile development +of secure database-driven web-based applications, written and programmable in +Python. + +Created by Massimo Di Pierro + ADDED applications/admin/LICENSE Index: applications/admin/LICENSE ================================================================== --- applications/admin/LICENSE +++ applications/admin/LICENSE @@ -0,0 +1,137 @@ +## Web2py License + +Web2py is Licensed under the LGPL license version 3 +(http://www.gnu.org/licenses/lgpl.html) + +Copyrighted (c) by Massimo Di Pierro (2007-2011) + +### On Commercial Redistribution + +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 +But you must: +- make clear in the documentation that your application uses web2py +- release any modification of the web2py libraries under the LGPLv3 license + +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 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 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) + +### Licenses for third party contributed software + +web2py contains third party software under the gluon/contrib/ folder. +Each file/module in contrib is distributed with web2py under its original license. +Here we list some of them. + +#### gluon.contrib.simplejson LICENSE + +Copyright (c) 2006 Bob Ippolito - Permission is hereby granted, free of charge, +to any person obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, distribute, +sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +#### gluon.contrib.rss2.py (originally PyRSS2Gen) LICENSE + +This is copyright (c) by Dalke Scientific Software, LLC and released under the +BSD license. See the file LICENSE in the distribution or + for details. + +#### gluon.contrib.markdown (markdown2) LICENSE + +MIT License from from + +#### gluon.contrib.feedparser LICENSE + +Copyright (c) 2002-2005, Mark Pilgrim + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +* Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +#### gluon.wsgiserver.py LICENSE (borrowed from cherrypy) + +Copyright (c) 2004, CherryPy Team (team@cherrypy.org) +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the CherryPy Team nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#### gluon.contrib.pam LICENSE + +Copyright (C) 2007-2009 Chris AtLee Licensed under the MIT license + +#### gluon.contrib.shell LICENSE + +Copyright (C) by Google inc. Apache 2.0 Lincense + +#### The javascript licenses are in the code itself + ADDED applications/admin/__init__.py Index: applications/admin/__init__.py ================================================================== --- applications/admin/__init__.py +++ applications/admin/__init__.py ADDED applications/admin/controllers/appadmin.py Index: applications/admin/controllers/appadmin.py ================================================================== --- applications/admin/controllers/appadmin.py +++ applications/admin/controllers/appadmin.py @@ -0,0 +1,408 @@ +# -*- coding: utf-8 -*- + +# ########################################################## +# ## make sure administrator is on localhost +# ########################################################### + +import os +import socket +import datetime +import copy +import gluon.contenttype +import gluon.fileutils + +# ## critical --- make a copy of the environment + +global_env = copy.copy(globals()) +global_env['datetime'] = datetime + +http_host = request.env.http_host.split(':')[0] +remote_addr = request.env.remote_addr +try: + hosts = (http_host, socket.gethostname(), + socket.gethostbyname(http_host), + '::1','127.0.0.1','::ffff:127.0.0.1') +except: + hosts = (http_host, ) + +if request.env.http_x_forwarded_for or request.env.wsgi_url_scheme\ + in ['https', 'HTTPS']: + session.secure() +elif (remote_addr not in hosts) and (remote_addr != "127.0.0.1"): + raise HTTP(200, T('appadmin is disabled because insecure channel')) + +if (request.application=='admin' and not session.authorized) or \ + (request.application!='admin' and not gluon.fileutils.check_credentials(request)): + redirect(URL('admin', 'default', 'index')) + +ignore_rw = True +response.view = 'appadmin.html' +response.menu = [[T('design'), False, URL('admin', 'default', 'design', + args=[request.application])], [T('db'), False, + URL('index')], [T('state'), False, + URL('state')], [T('cache'), False, + URL('ccache')]] + +# ########################################################## +# ## auxiliary functions +# ########################################################### + + +def get_databases(request): + dbs = {} + for (key, value) in global_env.items(): + cond = False + try: + cond = isinstance(value, GQLDB) + except: + cond = isinstance(value, SQLDB) + if cond: + dbs[key] = value + return dbs + + +databases = get_databases(None) + + +def eval_in_global_env(text): + exec ('_ret=%s' % text, {}, global_env) + return global_env['_ret'] + + +def get_database(request): + if request.args and request.args[0] in databases: + return eval_in_global_env(request.args[0]) + else: + session.flash = T('invalid request') + redirect(URL('index')) + + +def get_table(request): + db = get_database(request) + if len(request.args) > 1 and request.args[1] in db.tables: + return (db, request.args[1]) + else: + session.flash = T('invalid request') + redirect(URL('index')) + + +def get_query(request): + try: + return eval_in_global_env(request.vars.query) + except Exception: + return None + + +def query_by_table_type(tablename,db,request=request): + keyed = hasattr(db[tablename],'_primarykey') + if keyed: + firstkey = db[tablename][db[tablename]._primarykey[0]] + cond = '>0' + if firstkey.type in ['string', 'text']: + cond = '!=""' + qry = '%s.%s.%s%s' % (request.args[0], request.args[1], firstkey.name, cond) + else: + qry = '%s.%s.id>0' % tuple(request.args[:2]) + return qry + + + +# ########################################################## +# ## list all databases and tables +# ########################################################### + + +def index(): + return dict(databases=databases) + + +# ########################################################## +# ## insert a new record +# ########################################################### + + +def insert(): + (db, table) = get_table(request) + form = SQLFORM(db[table], ignore_rw=ignore_rw) + if form.accepts(request.vars, session): + response.flash = T('new record inserted') + return dict(form=form,table=db[table]) + + +# ########################################################## +# ## list all records in table and insert new record +# ########################################################### + + +def download(): + import os + db = get_database(request) + return response.download(request,db) + +def csv(): + import gluon.contenttype + response.headers['Content-Type'] = \ + gluon.contenttype.contenttype('.csv') + db = get_database(request) + query = get_query(request) + if not query: + return None + response.headers['Content-disposition'] = 'attachment; filename=%s_%s.csv'\ + % tuple(request.vars.query.split('.')[:2]) + return str(db(query).select()) + + +def import_csv(table, file): + table.import_from_csv_file(file) + +def select(): + import re + db = get_database(request) + dbname = request.args[0] + regex = re.compile('(?P\w+)\.(?P\w+)=(?P\d+)') + if len(request.args)>1 and hasattr(db[request.args[1]],'_primarykey'): + regex = re.compile('(?P
\w+)\.(?P\w+)=(?P.+)') + if request.vars.query: + match = regex.match(request.vars.query) + if match: + request.vars.query = '%s.%s.%s==%s' % (request.args[0], + match.group('table'), match.group('field'), + match.group('value')) + else: + request.vars.query = session.last_query + query = get_query(request) + if request.vars.start: + start = int(request.vars.start) + else: + start = 0 + nrows = 0 + stop = start + 100 + table = None + rows = [] + orderby = request.vars.orderby + if orderby: + orderby = dbname + '.' + orderby + if orderby == session.last_orderby: + if orderby[0] == '~': + orderby = orderby[1:] + else: + orderby = '~' + orderby + session.last_orderby = orderby + session.last_query = request.vars.query + form = FORM(TABLE(TR(T('Query:'), '', INPUT(_style='width:400px', + _name='query', _value=request.vars.query or '', + requires=IS_NOT_EMPTY(error_message=T("Cannot be empty")))), TR(T('Update:'), + INPUT(_name='update_check', _type='checkbox', + value=False), INPUT(_style='width:400px', + _name='update_fields', _value=request.vars.update_fields + or '')), TR(T('Delete:'), INPUT(_name='delete_check', + _class='delete', _type='checkbox', value=False), ''), + TR('', '', INPUT(_type='submit', _value='submit'))), + _action=URL(r=request,args=request.args)) + if request.vars.csvfile != None: + try: + import_csv(db[request.vars.table], + request.vars.csvfile.file) + response.flash = T('data uploaded') + except Exception, e: + response.flash = DIV(T('unable to parse csv file'),PRE(str(e))) + if form.accepts(request.vars, formname=None): +# regex = re.compile(request.args[0] + '\.(?P
\w+)\.id\>0') + regex = re.compile(request.args[0] + '\.(?P
\w+)\..+') + + match = regex.match(form.vars.query.strip()) + if match: + table = match.group('table') + try: + nrows = db(query).count() + if form.vars.update_check and form.vars.update_fields: + db(query).update(**eval_in_global_env('dict(%s)' + % form.vars.update_fields)) + response.flash = T('%s rows updated', nrows) + elif form.vars.delete_check: + db(query).delete() + response.flash = T('%s rows deleted', nrows) + nrows = db(query).count() + if orderby: + rows = db(query).select(limitby=(start, stop), + orderby=eval_in_global_env(orderby)) + else: + rows = db(query).select(limitby=(start, stop)) + except Exception, e: + (rows, nrows) = ([], 0) + response.flash = DIV(T('Invalid Query'),PRE(str(e))) + return dict( + form=form, + table=table, + start=start, + stop=stop, + nrows=nrows, + rows=rows, + query=request.vars.query, + ) + + +# ########################################################## +# ## edit delete one record +# ########################################################### + + +def update(): + (db, table) = get_table(request) + keyed = hasattr(db[table],'_primarykey') + record = None + if keyed: + key = [f for f in request.vars if f in db[table]._primarykey] + if key: + record = db(db[table][key[0]] == request.vars[key[0]]).select().first() + else: + record = db(db[table].id == request.args(2)).select().first() + + if not record: + qry = query_by_table_type(table, db) + session.flash = T('record does not exist') + redirect(URL('select', args=request.args[:1], + vars=dict(query=qry))) + + if keyed: + for k in db[table]._primarykey: + db[table][k].writable=False + + form = SQLFORM(db[table], record, deletable=True, delete_label=T('Check to delete'), + ignore_rw=ignore_rw and not keyed, + linkto=URL('select', + args=request.args[:1]), upload=URL(r=request, + f='download', args=request.args[:1])) + + if form.accepts(request.vars, session): + session.flash = T('done!') + qry = query_by_table_type(table, db) + redirect(URL('select', args=request.args[:1], + vars=dict(query=qry))) + return dict(form=form,table=db[table]) + + +# ########################################################## +# ## get global variables +# ########################################################### + + +def state(): + return dict() + +def ccache(): + form = FORM( + P(TAG.BUTTON("Clear CACHE?", _type="submit", _name="yes", _value="yes")), + P(TAG.BUTTON("Clear RAM", _type="submit", _name="ram", _value="ram")), + P(TAG.BUTTON("Clear DISK", _type="submit", _name="disk", _value="disk")), + ) + + if form.accepts(request.vars, session): + clear_ram = False + clear_disk = False + session.flash = "" + if request.vars.yes: + clear_ram = clear_disk = True + if request.vars.ram: + clear_ram = True + if request.vars.disk: + clear_disk = True + + if clear_ram: + cache.ram.clear() + session.flash += "Ram Cleared " + if clear_disk: + cache.disk.clear() + session.flash += "Disk Cleared" + + redirect(URL(r=request)) + + try: + from guppy import hpy; hp=hpy() + except ImportError: + hp = False + + import shelve, os, copy, time, math + from gluon import portalocker + + ram = { + 'bytes': 0, + 'objects': 0, + 'hits': 0, + 'misses': 0, + 'ratio': 0, + 'oldest': time.time() + } + disk = copy.copy(ram) + total = copy.copy(ram) + + for key, value in cache.ram.storage.items(): + if isinstance(value, dict): + ram['hits'] = value['hit_total'] - value['misses'] + ram['misses'] = value['misses'] + try: + ram['ratio'] = ram['hits'] * 100 / value['hit_total'] + except (KeyError, ZeroDivisionError): + ram['ratio'] = 0 + else: + if hp: + ram['bytes'] += hp.iso(value[1]).size + ram['objects'] += hp.iso(value[1]).count + + if value[0] < ram['oldest']: + ram['oldest'] = value[0] + + locker = open(os.path.join(request.folder, + 'cache/cache.lock'), 'a') + portalocker.lock(locker, portalocker.LOCK_EX) + disk_storage = shelve.open(os.path.join(request.folder, 'cache/cache.shelve')) + try: + for key, value in disk_storage.items(): + if isinstance(value, dict): + disk['hits'] = value['hit_total'] - value['misses'] + disk['misses'] = value['misses'] + try: + disk['ratio'] = disk['hits'] * 100 / value['hit_total'] + except (KeyError, ZeroDivisionError): + disk['ratio'] = 0 + else: + if hp: + disk['bytes'] += hp.iso(value[1]).size + disk['objects'] += hp.iso(value[1]).count + if value[0] < disk['oldest']: + disk['oldest'] = value[0] + finally: + portalocker.unlock(locker) + locker.close() + disk_storage.close() + + total['bytes'] = ram['bytes'] + disk['bytes'] + total['objects'] = ram['objects'] + disk['objects'] + total['hits'] = ram['hits'] + disk['hits'] + total['misses'] = ram['misses'] + disk['misses'] + try: + total['ratio'] = total['hits'] * 100 / (total['hits'] + total['misses']) + except (KeyError, ZeroDivisionError): + total['ratio'] = 0 + + if disk['oldest'] < ram['oldest']: + total['oldest'] = disk['oldest'] + else: + total['oldest'] = ram['oldest'] + + def GetInHMS(seconds): + hours = math.floor(seconds / 3600) + seconds -= hours * 3600 + minutes = math.floor(seconds / 60) + seconds -= minutes * 60 + seconds = math.floor(seconds) + + return (hours, minutes, seconds) + + ram['oldest'] = GetInHMS(time.time() - ram['oldest']) + disk['oldest'] = GetInHMS(time.time() - disk['oldest']) + total['oldest'] = GetInHMS(time.time() - total['oldest']) + + return dict(form=form, total=total, + ram=ram, disk=disk) + ADDED applications/admin/controllers/default.py Index: applications/admin/controllers/default.py ================================================================== --- applications/admin/controllers/default.py +++ applications/admin/controllers/default.py @@ -0,0 +1,1222 @@ +# coding: utf8 + +from gluon.admin import * +from gluon.fileutils import abspath, read_file, write_file +from glob import glob +import shutil +import platform + +if DEMO_MODE and request.function in ['change_password','pack','pack_plugin','upgrade_web2py','uninstall','cleanup','compile_app','remove_compiled_app','delete','delete_plugin','create_file','upload_file','update_languages','reload_routes']: + session.flash = T('disabled in demo mode') + redirect(URL('site')) + +if not is_manager() and request.function in ['change_password','upgrade_web2py']: + session.flash = T('disabled in multi user mode') + redirect(URL('site')) + +if FILTER_APPS and request.args(0) and not request.args(0) in FILTER_APPS: + session.flash = T('disabled in demo mode') + redirect(URL('site')) + +def safe_open(a,b): + if DEMO_MODE and 'w' in b: + class tmp: + def write(self,data): pass + return tmp() + return open(a,b) + +def safe_read(a, b='r'): + safe_file = safe_open(a, b) + try: + return safe_file.read() + finally: + safe_file.close() + +def safe_write(a, value, b='w'): + safe_file = safe_open(a, b) + try: + safe_file.write(value) + finally: + safe_file.close() + +def get_app(name=None): + app = name or request.args(0) + if app and (not MULTI_USER_MODE or db(db.app.name==app)(db.app.owner==auth.user.id).count()): + return app + session.flash = 'App does not exist or your are not authorized' + redirect(URL('site')) + +def index(): + """ Index handler """ + + send = request.vars.send + if DEMO_MODE: + session.authorized = True + session.last_time = t0 + if not send: + send = URL('site') + if session.authorized: + redirect(send) + elif request.vars.password: + if verify_password(request.vars.password): + session.authorized = True + login_record(True) + + if CHECK_VERSION: + session.check_version = True + else: + session.check_version = False + + session.last_time = t0 + if isinstance(send, list): # ## why does this happen? + send = str(send[0]) + + redirect(send) + else: + times_denied = login_record(False) + if times_denied >= allowed_number_of_attempts: + response.flash = \ + T('admin disabled because too many invalid login attempts') + elif times_denied == allowed_number_of_attempts - 1: + response.flash = \ + T('You have one more login attempt before you are locked out') + else: + response.flash = T('invalid password.') + return dict(send=send) + + +def check_version(): + """ Checks if web2py is up to date """ + + session.forget() + session._unlock(response) + + new_version, version_number = check_new_version(request.env.web2py_version, + WEB2PY_VERSION_URL) + + if new_version == -1: + return A(T('Unable to check for upgrades'), _href=WEB2PY_URL) + elif new_version != True: + return A(T('web2py is up to date'), _href=WEB2PY_URL) + elif platform.system().lower() in ('windows','win32','win64') and os.path.exists("web2py.exe"): + return SPAN('You should upgrade to version %s' % version_number) + else: + return sp_button(URL('upgrade_web2py'), T('upgrade now')) \ + + XML(' %s' % version_number) + + +def logout(): + """ Logout handler """ + session.authorized = None + if MULTI_USER_MODE: + redirect(URL('user/logout')) + redirect(URL('index')) + + +def change_password(): + + if session.pam_user: + session.flash = T('PAM authenticated user, cannot change password here') + redirect(URL('site')) + form=SQLFORM.factory(Field('current_admin_password','password'), + Field('new_admin_password','password',requires=IS_STRONG()), + Field('new_admin_password_again','password')) + if form.accepts(request.vars): + if not verify_password(request.vars.current_admin_password): + form.errors.current_admin_password = T('invalid password') + elif form.vars.new_admin_password != form.vars.new_admin_password_again: + form.errors.new_admin_password_again = T('no match') + else: + path = abspath('parameters_%s.py' % request.env.server_port) + safe_write(path, 'password="%s"' % CRYPT()(request.vars.new_admin_password)[0]) + session.flash = T('password changed') + redirect(URL('site')) + return dict(form=form) + +def site(): + """ Site handler """ + + myversion = request.env.web2py_version + + # Shortcut to make the elif statements more legible + file_or_appurl = 'file' in request.vars or 'appurl' in request.vars + + if DEMO_MODE: + pass + + elif request.vars.filename and not 'file' in request.vars: + # create a new application + appname = cleanpath(request.vars.filename).replace('.', '_') + if app_create(appname, request): + if MULTI_USER_MODE: + db.app.insert(name=appname,owner=auth.user.id) + session.flash = T('new application "%s" created', appname) + redirect(URL('design',args=appname)) + else: + session.flash = \ + T('unable to create application "%s" (it may exist already)', request.vars.filename) + redirect(URL(r=request)) + + elif file_or_appurl and not request.vars.filename: + # can't do anything without an app name + msg = 'you must specify a name for the uploaded application' + response.flash = T(msg) + + elif file_or_appurl and request.vars.filename: + # fetch an application via URL or file upload + f = None + if request.vars.appurl is not '': + try: + f = urllib.urlopen(request.vars.appurl) + except Exception, e: + session.flash = DIV(T('Unable to download app because:'),PRE(str(e))) + redirect(URL(r=request)) + fname = request.vars.appurl + elif request.vars.file is not '': + f = request.vars.file.file + fname = request.vars.file.filename + + if f: + appname = cleanpath(request.vars.filename).replace('.', '_') + installed = app_install(appname, f, request, fname, + overwrite=request.vars.overwrite_check) + if f and installed: + msg = 'application %(appname)s installed with md5sum: %(digest)s' + session.flash = T(msg, dict(appname=appname, + digest=md5_hash(installed))) + elif f and request.vars.overwrite_check: + msg = 'unable to install application "%(appname)s"' + session.flash = T(msg, dict(appname=request.vars.filename)) + + else: + msg = 'unable to install application "%(appname)s"' + session.flash = T(msg, dict(appname=request.vars.filename)) + + redirect(URL(r=request)) + + regex = re.compile('^\w+$') + + if is_manager(): + apps = [f for f in os.listdir(apath(r=request)) if regex.match(f)] + else: + apps = [f.name for f in db(db.app.owner==auth.user_id).select()] + + if FILTER_APPS: + apps = [f for f in apps if f in FILTER_APPS] + + apps = sorted(apps,lambda a,b:cmp(a.upper(),b.upper())) + + return dict(app=None, apps=apps, myversion=myversion) + + +def pack(): + app = get_app() + + if len(request.args) == 1: + fname = 'web2py.app.%s.w2p' % app + filename = app_pack(app, request) + else: + fname = 'web2py.app.%s.compiled.w2p' % app + filename = app_pack_compiled(app, request) + + if filename: + response.headers['Content-Type'] = 'application/w2p' + disposition = 'attachment; filename=%s' % fname + response.headers['Content-Disposition'] = disposition + return safe_read(filename, 'rb') + else: + session.flash = T('internal error') + redirect(URL('site')) + +def pack_plugin(): + app = get_app() + if len(request.args) == 2: + fname = 'web2py.plugin.%s.w2p' % request.args[1] + filename = plugin_pack(app, request.args[1], request) + if filename: + response.headers['Content-Type'] = 'application/w2p' + disposition = 'attachment; filename=%s' % fname + response.headers['Content-Disposition'] = disposition + return safe_read(filename, 'rb') + else: + session.flash = T('internal error') + redirect(URL('plugin',args=request.args)) + +def upgrade_web2py(): + if 'upgrade' in request.vars: + (success, error) = upgrade(request) + if success: + session.flash = T('web2py upgraded; please restart it') + else: + session.flash = T('unable to upgrade because "%s"', error) + redirect(URL('site')) + elif 'noupgrade' in request.vars: + redirect(URL('site')) + return dict() + +def uninstall(): + app = get_app() + if 'delete' in request.vars: + if MULTI_USER_MODE: + if is_manager() and db(db.app.name==app).delete(): + pass + elif db(db.app.name==app)(db.app.owner==auth.user.id).delete(): + pass + else: + session.flash = T('no permission to uninstall "%s"', app) + redirect(URL('site')) + if app_uninstall(app, request): + session.flash = T('application "%s" uninstalled', app) + else: + session.flash = T('unable to uninstall "%s"', app) + redirect(URL('site')) + elif 'nodelete' in request.vars: + redirect(URL('site')) + return dict(app=app) + + +def cleanup(): + app = get_app() + clean = app_cleanup(app, request) + if not clean: + session.flash = T("some files could not be removed") + else: + session.flash = T('cache, errors and sessions cleaned') + + redirect(URL('site')) + + +def compile_app(): + app = get_app() + c = app_compile(app, request) + if not c: + session.flash = T('application compiled') + else: + session.flash = DIV(T('Cannot compile: there are errors in your app:'), + CODE(c)) + redirect(URL('site')) + + +def remove_compiled_app(): + """ Remove the compiled application """ + app = get_app() + remove_compiled_application(apath(app, r=request)) + session.flash = T('compiled application removed') + redirect(URL('site')) + +def delete(): + """ Object delete handler """ + app = get_app() + filename = '/'.join(request.args) + sender = request.vars.sender + + if isinstance(sender, list): # ## fix a problem with Vista + sender = sender[0] + + if 'nodelete' in request.vars: + redirect(URL(sender)) + elif 'delete' in request.vars: + try: + os.unlink(apath(filename, r=request)) + session.flash = T('file "%(filename)s" deleted', + dict(filename=filename)) + except Exception: + session.flash = T('unable to delete file "%(filename)s"', + dict(filename=filename)) + redirect(URL(sender)) + return dict(filename=filename, sender=sender) + +def peek(): + """ Visualize object code """ + app = get_app() + filename = '/'.join(request.args) + try: + data = safe_read(apath(filename, r=request)).replace('\r','') + except IOError: + session.flash = T('file does not exist') + redirect(URL('site')) + + extension = filename[filename.rfind('.') + 1:].lower() + + return dict(app=request.args[0], + filename=filename, + data=data, + extension=extension) + + +def test(): + """ Execute controller tests """ + app = get_app() + if len(request.args) > 1: + file = request.args[1] + else: + file = '.*\.py' + + controllers = listdir(apath('%s/controllers/' % app, r=request), file + '$') + + return dict(app=app, controllers=controllers) + +def keepalive(): + return '' + +def search(): + keywords=request.vars.keywords or '' + app = get_app() + def match(filename,keywords): + filename=os.path.join(apath(app, r=request),filename) + if keywords in read_file(filename,'rb'): + return True + return False + path = apath(request.args[0], r=request) + files1 = glob(os.path.join(path,'*/*.py')) + files2 = glob(os.path.join(path,'*/*.html')) + files3 = glob(os.path.join(path,'*/*/*.html')) + files=[x[len(path)+1:].replace('\\','/') for x in files1+files2+files3 if match(x,keywords)] + return response.json({'files':files}) + +def edit(): + """ File edit handler """ + # Load json only if it is ajax edited... + app = get_app() + filename = '/'.join(request.args) + # Try to discover the file type + if filename[-3:] == '.py': + filetype = 'python' + elif filename[-5:] == '.html': + filetype = 'html' + elif filename[-5:] == '.load': + filetype = 'html' + elif filename[-4:] == '.css': + filetype = 'css' + elif filename[-3:] == '.js': + filetype = 'js' + else: + filetype = 'html' + + # ## check if file is not there + + path = apath(filename, r=request) + + if request.vars.revert and os.path.exists(path + '.bak'): + try: + data = safe_read(path + '.bak') + data1 = safe_read(path) + except IOError: + session.flash = T('Invalid action') + if 'from_ajax' in request.vars: + return response.json({'error': str(T('Invalid action'))}) + else: + redirect(URL('site')) + + safe_write(path, data) + file_hash = md5_hash(data) + saved_on = time.ctime(os.stat(path)[stat.ST_MTIME]) + safe_write(path + '.bak', data1) + response.flash = T('file "%s" of %s restored', (filename, saved_on)) + else: + try: + data = safe_read(path) + except IOError: + session.flash = T('Invalid action') + if 'from_ajax' in request.vars: + return response.json({'error': str(T('Invalid action'))}) + else: + redirect(URL('site')) + + file_hash = md5_hash(data) + saved_on = time.ctime(os.stat(path)[stat.ST_MTIME]) + + if request.vars.file_hash and request.vars.file_hash != file_hash: + session.flash = T('file changed on disk') + data = request.vars.data.replace('\r\n', '\n').strip() + '\n' + safe_write(path + '.1', data) + if 'from_ajax' in request.vars: + return response.json({'error': str(T('file changed on disk')), + 'redirect': URL('resolve', + args=request.args)}) + else: + redirect(URL('resolve', args=request.args)) + elif request.vars.data: + safe_write(path + '.bak', data) + data = request.vars.data.replace('\r\n', '\n').strip() + '\n' + safe_write(path, data) + file_hash = md5_hash(data) + saved_on = time.ctime(os.stat(path)[stat.ST_MTIME]) + response.flash = T('file saved on %s', saved_on) + + data_or_revert = (request.vars.data or request.vars.revert) + + # Check compile errors + highlight = None + if filetype == 'python' and request.vars.data: + import _ast + try: + code = request.vars.data.rstrip().replace('\r\n','\n')+'\n' + compile(code, path, "exec", _ast.PyCF_ONLY_AST) + except Exception, e: + start = sum([len(line)+1 for l, line + in enumerate(request.vars.data.split("\n")) + if l < e.lineno-1]) + if e.text and e.offset: + offset = e.offset - (len(e.text) - len(e.text.splitlines()[-1])) + else: + offset = 0 + highlight = {'start': start, 'end': start + offset + 1} + try: + ex_name = e.__class__.__name__ + except: + ex_name = 'unknown exception!' + response.flash = DIV(T('failed to compile file because:'), BR(), + B(ex_name), T(' at line %s') % e.lineno, + offset and T(' at char %s') % offset or '', + PRE(str(e))) + + if data_or_revert and request.args[1] == 'modules': + # Lets try to reload the modules + try: + mopath = '.'.join(request.args[2:])[:-3] + exec 'import applications.%s.modules.%s' % (request.args[0], mopath) + reload(sys.modules['applications.%s.modules.%s' + % (request.args[0], mopath)]) + except Exception, e: + response.flash = DIV(T('failed to reload module because:'),PRE(str(e))) + + edit_controller = None + editviewlinks = None + view_link = None + if filetype == 'html' and len(request.args) >= 3: + cfilename = os.path.join(request.args[0], 'controllers', + request.args[2] + '.py') + if os.path.exists(apath(cfilename, r=request)): + edit_controller = URL('edit', args=[cfilename]) + view = request.args[3].replace('.html','') + view_link = URL(request.args[0],request.args[2],view) + elif filetype == 'python' and request.args[1] == 'controllers': + ## it's a controller file. + ## Create links to all of the associated view files. + app = get_app() + viewname = os.path.splitext(request.args[2])[0] + viewpath = os.path.join(app,'views',viewname) + aviewpath = apath(viewpath, r=request) + viewlist = [] + if os.path.exists(aviewpath): + if os.path.isdir(aviewpath): + viewlist = glob(os.path.join(aviewpath,'*.html')) + elif os.path.exists(aviewpath+'.html'): + viewlist.append(aviewpath+'.html') + if len(viewlist): + editviewlinks = [] + for v in viewlist: + vf = os.path.split(v)[-1] + vargs = "/".join([viewpath.replace(os.sep,"/"),vf]) + editviewlinks.append(A(T(vf.split(".")[0]),\ + _href=URL('edit',args=[vargs]))) + + if len(request.args) > 2 and request.args[1] == 'controllers': + controller = (request.args[2])[:-3] + functions = regex_expose.findall(data) + else: + (controller, functions) = (None, None) + + if 'from_ajax' in request.vars: + return response.json({'file_hash': file_hash, 'saved_on': saved_on, 'functions':functions, 'controller': controller, 'application': request.args[0], 'highlight': highlight }) + else: + + editarea_preferences = {} + editarea_preferences['FONT_SIZE'] = '10' + editarea_preferences['FULL_SCREEN'] = 'false' + editarea_preferences['ALLOW_TOGGLE'] = 'true' + editarea_preferences['REPLACE_TAB_BY_SPACES'] = '4' + editarea_preferences['DISPLAY'] = 'onload' + for key in editarea_preferences: + if globals().has_key(key): + editarea_preferences[key]=globals()[key] + return dict(app=request.args[0], + filename=filename, + filetype=filetype, + data=data, + edit_controller=edit_controller, + file_hash=file_hash, + saved_on=saved_on, + controller=controller, + functions=functions, + view_link=view_link, + editarea_preferences=editarea_preferences, + editviewlinks=editviewlinks) + +def resolve(): + """ + """ + + filename = '/'.join(request.args) + # ## check if file is not there + path = apath(filename, r=request) + a = safe_read(path).split('\n') + try: + b = safe_read(path + '.1').split('\n') + except IOError: + session.flash = 'Other file, no longer there' + redirect(URL('edit', args=request.args)) + + d = difflib.ndiff(a, b) + + def leading(line): + """ """ + + # TODO: we really need to comment this + z = '' + for (k, c) in enumerate(line): + if c == ' ': + z += ' ' + elif c == ' \t': + z += ' ' + elif k == 0 and c == '?': + pass + else: + break + + return XML(z) + + def getclass(item): + """ Determine item class """ + + if item[0] == ' ': + return 'normal' + if item[0] == '+': + return 'plus' + if item[0] == '-': + return 'minus' + + if request.vars: + c = ''.join([item[2:] for (i, item) in enumerate(d) if item[0] \ + == ' ' or 'line%i' % i in request.vars]) + safe_write(path, c) + session.flash = 'files merged' + redirect(URL('edit', args=request.args)) + else: + # Making the short circuit compatible with <= python2.4 + gen_data = lambda index,item: not item[:1] in ['+','-'] and "" \ + or INPUT(_type='checkbox', + _name='line%i' % index, + value=item[0] == '+') + + diff = TABLE(*[TR(TD(gen_data(i,item)), + TD(item[0]), + TD(leading(item[2:]), + TT(item[2:].rstrip())), _class=getclass(item)) + for (i, item) in enumerate(d) if item[0] != '?']) + + return dict(diff=diff, filename=filename) + + +def edit_language(): + """ Edit language file """ + app = get_app() + filename = '/'.join(request.args) + from gluon.languages import read_dict, write_dict + strings = read_dict(apath(filename, r=request)) + keys = sorted(strings.keys(),lambda x,y: cmp(x.lower(), y.lower())) + rows = [] + rows.append(H2(T('Original/Translation'))) + + for key in keys: + name = md5_hash(key) + if key==strings[key]: + _class='untranslated' + else: + _class='translated' + if len(key) <= 40: + elem = INPUT(_type='text', _name=name,value=strings[key], + _size=70,_class=_class) + else: + elem = TEXTAREA(_name=name, value=strings[key], _cols=70, + _rows=5, _class=_class) + + # Making the short circuit compatible with <= python2.4 + k = (strings[key] != key) and key or B(key) + + rows.append(P(k, BR(), elem, TAG.BUTTON(T('delete'), + _onclick='return delkey("%s")' % name), _id=name)) + + rows.append(INPUT(_type='submit', _value=T('update'))) + form = FORM(*rows) + if form.accepts(request.vars, keepvalues=True): + strs = dict() + for key in keys: + name = md5_hash(key) + if form.vars[name]==chr(127): continue + strs[key] = form.vars[name] + write_dict(apath(filename, r=request), strs) + session.flash = T('file saved on %(time)s', dict(time=time.ctime())) + redirect(URL(r=request,args=request.args)) + return dict(app=request.args[0], filename=filename, form=form) + + +def about(): + """ Read about info """ + app = get_app() + # ## check if file is not there + about = safe_read(apath('%s/ABOUT' % app, r=request)) + license = safe_read(apath('%s/LICENSE' % app, r=request)) + return dict(app=app, about=MARKMIN(about), license=MARKMIN(license)) + + +def design(): + """ Application design handler """ + app = get_app() + + if not response.flash and app == request.application: + msg = T('ATTENTION: you cannot edit the running application!') + response.flash = msg + + if request.vars.pluginfile!=None and not isinstance(request.vars.pluginfile,str): + filename=os.path.basename(request.vars.pluginfile.filename) + if plugin_install(app, request.vars.pluginfile.file, + request, filename): + session.flash = T('new plugin installed') + redirect(URL('design',args=app)) + else: + session.flash = \ + T('unable to create application "%s"', request.vars.filename) + redirect(URL(r=request)) + elif isinstance(request.vars.pluginfile,str): + session.flash = T('plugin not specified') + redirect(URL(r=request)) + + + # If we have only pyc files it means that + # we cannot design + if os.path.exists(apath('%s/compiled' % app, r=request)): + session.flash = \ + T('application is compiled and cannot be designed') + redirect(URL('site')) + + # Get all models + models = listdir(apath('%s/models/' % app, r=request), '.*\.py$') + models=[x.replace('\\','/') for x in models] + defines = {} + for m in models: + data = safe_read(apath('%s/models/%s' % (app, m), r=request)) + defines[m] = regex_tables.findall(data) + defines[m].sort() + + # Get all controllers + controllers = sorted(listdir(apath('%s/controllers/' % app, r=request), '.*\.py$')) + controllers = [x.replace('\\','/') for x in controllers] + functions = {} + for c in controllers: + data = safe_read(apath('%s/controllers/%s' % (app, c), r=request)) + items = regex_expose.findall(data) + functions[c] = items + + # Get all views + views = sorted(listdir(apath('%s/views/' % app, r=request), '[\w/\-]+\.\w+$')) + views = [x.replace('\\','/') for x in views] + extend = {} + include = {} + for c in views: + data = safe_read(apath('%s/views/%s' % (app, c), r=request)) + items = regex_extend.findall(data) + + if items: + extend[c] = items[0][1] + + items = regex_include.findall(data) + include[c] = [i[1] for i in items] + + # Get all modules + modules = listdir(apath('%s/modules/' % app, r=request), '.*\.py$') + modules = modules=[x.replace('\\','/') for x in modules] + modules.sort() + + # Get all static files + statics = listdir(apath('%s/static/' % app, r=request), '[^\.#].*') + statics = [x.replace('\\','/') for x in statics] + statics.sort() + + # Get all languages + languages = listdir(apath('%s/languages/' % app, r=request), '[\w-]*\.py') + + #Get crontab + cronfolder = apath('%s/cron' % app, r=request) + if not os.path.exists(cronfolder): os.mkdir(cronfolder) + crontab = apath('%s/cron/crontab' % app, r=request) + if not os.path.exists(crontab): + safe_write(crontab, '#crontab') + + plugins=[] + def filter_plugins(items,plugins): + plugins+=[item[7:].split('/')[0].split('.')[0] for item in items if item.startswith('plugin_')] + plugins[:]=list(set(plugins)) + plugins.sort() + return [item for item in items if not item.startswith('plugin_')] + + return dict(app=app, + models=filter_plugins(models,plugins), + defines=defines, + controllers=filter_plugins(controllers,plugins), + functions=functions, + views=filter_plugins(views,plugins), + modules=filter_plugins(modules,plugins), + extend=extend, + include=include, + statics=filter_plugins(statics,plugins), + languages=languages, + crontab=crontab, + plugins=plugins) + +def delete_plugin(): + """ Object delete handler """ + app=request.args(0) + plugin = request.args(1) + plugin_name='plugin_'+plugin + if 'nodelete' in request.vars: + redirect(URL('design',args=app)) + elif 'delete' in request.vars: + try: + for folder in ['models','views','controllers','static','modules']: + path=os.path.join(apath(app,r=request),folder) + for item in os.listdir(path): + if item.startswith(plugin_name): + filename=os.path.join(path,item) + if os.path.isdir(filename): + shutil.rmtree(filename) + else: + os.unlink(filename) + session.flash = T('plugin "%(plugin)s" deleted', + dict(plugin=plugin)) + except Exception: + session.flash = T('unable to delete file plugin "%(plugin)s"', + dict(plugin=plugin)) + redirect(URL('design',args=request.args(0))) + return dict(plugin=plugin) + +def plugin(): + """ Application design handler """ + app = get_app() + plugin = request.args(1) + + if not response.flash and app == request.application: + msg = T('ATTENTION: you cannot edit the running application!') + response.flash = msg + + # If we have only pyc files it means that + # we cannot design + if os.path.exists(apath('%s/compiled' % app, r=request)): + session.flash = \ + T('application is compiled and cannot be designed') + redirect(URL('site')) + + # Get all models + models = listdir(apath('%s/models/' % app, r=request), '.*\.py$') + models=[x.replace('\\','/') for x in models] + defines = {} + for m in models: + data = safe_read(apath('%s/models/%s' % (app, m), r=request)) + defines[m] = regex_tables.findall(data) + defines[m].sort() + + # Get all controllers + controllers = sorted(listdir(apath('%s/controllers/' % app, r=request), '.*\.py$')) + controllers = [x.replace('\\','/') for x in controllers] + functions = {} + for c in controllers: + data = safe_read(apath('%s/controllers/%s' % (app, c), r=request)) + items = regex_expose.findall(data) + functions[c] = items + + # Get all views + views = sorted(listdir(apath('%s/views/' % app, r=request), '[\w/\-]+\.\w+$')) + views = [x.replace('\\','/') for x in views] + extend = {} + include = {} + for c in views: + data = safe_read(apath('%s/views/%s' % (app, c), r=request)) + items = regex_extend.findall(data) + if items: + extend[c] = items[0][1] + + items = regex_include.findall(data) + include[c] = [i[1] for i in items] + + # Get all modules + modules = listdir(apath('%s/modules/' % app, r=request), '.*\.py$') + modules = modules=[x.replace('\\','/') for x in modules] + modules.sort() + + # Get all static files + statics = listdir(apath('%s/static/' % app, r=request), '[^\.#].*') + statics = [x.replace('\\','/') for x in statics] + statics.sort() + + # Get all languages + languages = listdir(apath('%s/languages/' % app, r=request), '[\w-]*\.py') + + #Get crontab + crontab = apath('%s/cron/crontab' % app, r=request) + if not os.path.exists(crontab): + safe_write(crontab, '#crontab') + + def filter_plugins(items): + regex=re.compile('^plugin_'+plugin+'(/.*|\..*)?$') + return [item for item in items if regex.match(item)] + + return dict(app=app, + models=filter_plugins(models), + defines=defines, + controllers=filter_plugins(controllers), + functions=functions, + views=filter_plugins(views), + modules=filter_plugins(modules), + extend=extend, + include=include, + statics=filter_plugins(statics), + languages=languages, + crontab=crontab) + + +def create_file(): + """ Create files handler """ + try: + app = get_app(name=request.vars.location.split('/')[0]) + path = apath(request.vars.location, r=request) + filename = re.sub('[^\w./-]+', '_', request.vars.filename) + + if path[-11:] == '/languages/': + # Handle language files + if len(filename) == 0: + raise SyntaxError + if not filename[-3:] == '.py': + filename += '.py' + app = path.split('/')[-3] + path=os.path.join(apath(app, r=request),'languages',filename) + if not os.path.exists(path): + safe_write(path, '') + findT(apath(app, r=request), filename[:-3]) + session.flash = T('language file "%(filename)s" created/updated', + dict(filename=filename)) + redirect(request.vars.sender) + + elif path[-8:] == '/models/': + # Handle python models + if not filename[-3:] == '.py': + filename += '.py' + + if len(filename) == 3: + raise SyntaxError + + text = '# coding: utf8\n' + + elif path[-13:] == '/controllers/': + # Handle python controllers + if not filename[-3:] == '.py': + filename += '.py' + + if len(filename) == 3: + raise SyntaxError + + text = '# coding: utf8\n# %s\ndef index(): return dict(message="hello from %s")' + text = text % (T('try something like'), filename) + + elif path[-7:] == '/views/': + if request.vars.plugin and not filename.startswith('plugin_%s/' % request.vars.plugin): + filename = 'plugin_%s/%s' % (request.vars.plugin, filename) + # Handle template (html) views + if filename.find('.')<0: + filename += '.html' + extension = filename.split('.')[-1].lower() + + if len(filename) == 5: + raise SyntaxError + + msg = T('This is the %(filename)s template', + dict(filename=filename)) + if extension == 'html': + text = dedent(""" + {{extend 'layout.html'}} +

%s

+ {{=BEAUTIFY(response._vars)}}""" % msg) + else: + generic = os.path.join(path,'generic.'+extension) + if os.path.exists(generic): + text = read_file(generic) + else: + text = '' + + elif path[-9:] == '/modules/': + if request.vars.plugin and not filename.startswith('plugin_%s/' % request.vars.plugin): + filename = 'plugin_%s/%s' % (request.vars.plugin, filename) + # Handle python module files + if not filename[-3:] == '.py': + filename += '.py' + + if len(filename) == 3: + raise SyntaxError + + text = dedent(""" + #!/usr/bin/env python + # coding: utf8 + from gluon import *\n""") + + elif path[-8:] == '/static/': + if request.vars.plugin and not filename.startswith('plugin_%s/' % request.vars.plugin): + filename = 'plugin_%s/%s' % (request.vars.plugin, filename) + text = '' + else: + redirect(request.vars.sender) + + full_filename = os.path.join(path, filename) + dirpath = os.path.dirname(full_filename) + + if not os.path.exists(dirpath): + os.makedirs(dirpath) + + if os.path.exists(full_filename): + raise SyntaxError + + safe_write(full_filename, text) + session.flash = T('file "%(filename)s" created', + dict(filename=full_filename[len(path):])) + redirect(URL('edit', + args=[os.path.join(request.vars.location, filename)])) + except Exception, e: + if not isinstance(e,HTTP): + session.flash = T('cannot create file') + + redirect(request.vars.sender) + + +def upload_file(): + """ File uploading handler """ + + try: + filename = None + app = get_app(name=request.vars.location.split('/')[0]) + path = apath(request.vars.location, r=request) + + if request.vars.filename: + filename = re.sub('[^\w\./]+', '_', request.vars.filename) + else: + filename = os.path.split(request.vars.file.filename)[-1] + + if path[-8:] == '/models/' and not filename[-3:] == '.py': + filename += '.py' + + if path[-9:] == '/modules/' and not filename[-3:] == '.py': + filename += '.py' + + if path[-13:] == '/controllers/' and not filename[-3:] == '.py': + filename += '.py' + + if path[-7:] == '/views/' and not filename[-5:] == '.html': + filename += '.html' + + if path[-11:] == '/languages/' and not filename[-3:] == '.py': + filename += '.py' + + filename = os.path.join(path, filename) + dirpath = os.path.dirname(filename) + + if not os.path.exists(dirpath): + os.makedirs(dirpath) + + safe_write(filename, request.vars.file.file.read(), 'wb') + session.flash = T('file "%(filename)s" uploaded', + dict(filename=filename[len(path):])) + except Exception: + if filename: + d = dict(filename = filename[len(path):]) + else: + d = dict(filename = 'unkown') + session.flash = T('cannot upload file "%(filename)s"', d) + + redirect(request.vars.sender) + + +def errors(): + """ Error handler """ + import operator + import os + import pickle + import hashlib + + app = get_app() + + method = request.args(1) or 'new' + + + if method == 'new': + errors_path = apath('%s/errors' % app, r=request) + + delete_hashes = [] + for item in request.vars: + if item[:7] == 'delete_': + delete_hashes.append(item[7:]) + + hash2error = dict() + + for fn in listdir(errors_path, '^\w.*'): + fullpath = os.path.join(errors_path, fn) + if not os.path.isfile(fullpath): continue + try: + fullpath_file = open(fullpath, 'r') + try: + error = pickle.load(fullpath_file) + finally: + fullpath_file.close() + except IOError: + continue + + hash = hashlib.md5(error['traceback']).hexdigest() + + if hash in delete_hashes: + os.unlink(fullpath) + else: + try: + hash2error[hash]['count'] += 1 + except KeyError: + error_lines = error['traceback'].split("\n") + last_line = error_lines[-2] + error_causer = os.path.split(error['layer'])[1] + hash2error[hash] = dict(count=1, pickel=error, + causer=error_causer, + last_line=last_line, + hash=hash,ticket=fn) + + decorated = [(x['count'], x) for x in hash2error.values()] + decorated.sort(key=operator.itemgetter(0), reverse=True) + + return dict(errors = [x[1] for x in decorated], app=app, method=method) + else: + for item in request.vars: + if item[:7] == 'delete_': + os.unlink(apath('%s/errors/%s' % (app, item[7:]), r=request)) + func = lambda p: os.stat(apath('%s/errors/%s' % \ + (app, p), r=request)).st_mtime + tickets = sorted(listdir(apath('%s/errors/' % app, r=request), '^\w.*'), + key=func, + reverse=True) + + return dict(app=app, tickets=tickets, method=method) + + +def make_link(path): + """ Create a link from a path """ + tryFile = path.replace('\\', '/') + + if os.path.isabs(tryFile) and os.path.isfile(tryFile): + (folder, filename) = os.path.split(tryFile) + (base, ext) = os.path.splitext(filename) + app = get_app() + + editable = {'controllers': '.py', 'models': '.py', 'views': '.html'} + for key in editable.keys(): + check_extension = folder.endswith("%s/%s" % (app,key)) + if ext.lower() == editable[key] and check_extension: + return A('"' + tryFile + '"', + _href=URL(r=request, + f='edit/%s/%s/%s' % (app, key, filename))).xml() + return '' + + +def make_links(traceback): + """ Make links using the given traceback """ + + lwords = traceback.split('"') + + # Making the short circuit compatible with <= python2.4 + result = (len(lwords) != 0) and lwords[0] or '' + + i = 1 + + while i < len(lwords): + link = make_link(lwords[i]) + + if link == '': + result += '"' + lwords[i] + else: + result += link + + if i + 1 < len(lwords): + result += lwords[i + 1] + i = i + 1 + + i = i + 1 + + return result + + +class TRACEBACK(object): + """ Generate the traceback """ + + def __init__(self, text): + """ TRACEBACK constructor """ + + self.s = make_links(CODE(text).xml()) + + def xml(self): + """ Returns the xml """ + + return self.s + + +def ticket(): + """ Ticket handler """ + + if len(request.args) != 2: + session.flash = T('invalid ticket') + redirect(URL('site')) + + app = get_app() + myversion = request.env.web2py_version + ticket = request.args[1] + e = RestrictedError() + e.load(request, app, ticket) + + return dict(app=app, + ticket=ticket, + output=e.output, + traceback=(e.traceback and TRACEBACK(e.traceback)), + snapshot=e.snapshot, + code=e.code, + layer=e.layer, + myversion=myversion) + +def error(): + """ Generate a ticket (for testing) """ + raise RuntimeError('admin ticket generator at your service') + +def update_languages(): + """ Update available languages """ + + app = get_app() + update_all_languages(apath(app, r=request)) + session.flash = T('Language files (static strings) updated') + redirect(URL('design',args=app)) + +def twitter(): + session.forget() + session._unlock(response) + import gluon.tools + import gluon.contrib.simplejson as sj + try: + if TWITTER_HASH: + page = gluon.tools.fetch('http://twitter.com/%s?format=json'%TWITTER_HASH) + return sj.loads(page)['#timeline'] + else: + return 'disabled' + except Exception, e: + return DIV(T('Unable to download because:'),BR(),str(e)) + +def user(): + if MULTI_USER_MODE: + if not db(db.auth_user).count(): + auth.settings.registration_requires_approval = False + return dict(form=auth()) + else: + return dict(form=T("Disabled")) + +def reload_routes(): + """ Reload routes.py """ + gluon.rewrite.load() + redirect(URL('site')) ADDED applications/admin/controllers/gae.py Index: applications/admin/controllers/gae.py ================================================================== --- applications/admin/controllers/gae.py +++ applications/admin/controllers/gae.py @@ -0,0 +1,87 @@ +### this works on linux only + +import re +try: + import fcntl + import subprocess + import signal + import os + import shutil + from gluon.fileutils import read_file, write_file +except: + session.flash='sorry, only on Unix systems' + redirect(URL(request.application,'default','site')) + +forever=10**8 + +def kill(): + p = cache.ram('gae_upload',lambda:None,forever) + if not p or p.poll()!=None: + return 'oops' + os.kill(p.pid, signal.SIGKILL) + cache.ram('gae_upload',lambda:None,-1) + +class EXISTS(object): + def __init__(self, error_message='file not found'): + self.error_message = error_message + def __call__(self, value): + if os.path.exists(value): + return (value,None) + return (value,self.error_message) + +def deploy(): + regex = re.compile('^\w+$') + apps = sorted(file for file in os.listdir(apath(r=request)) if regex.match(file)) + form = SQLFORM.factory( + Field('appcfg',default=GAE_APPCFG,label='Path to appcfg.py', + requires=EXISTS(error_message=T('file not found'))), + Field('google_application_id',requires=IS_ALPHANUMERIC()), + Field('applications','list:string', + requires=IS_IN_SET(apps,multiple=True), + label=T('web2py apps to deploy')), + Field('email',requires=IS_EMAIL(),label=T('GAE Email')), + Field('password','password',requires=IS_NOT_EMPTY(),label=T('GAE Password'))) + cmd = output = errors= "" + if form.accepts(request,session): + try: + kill() + except: + pass + ignore_apps = [item for item in apps \ + if not item in form.vars.applications] + regex = re.compile('\(applications/\(.*') + yaml = apath('../app.yaml', r=request) + if not os.path.exists(yaml): + example = apath('../app.example.yaml', r=request) + shutil.copyfile(example,yaml) + data = read_file(yaml) + data = re.sub('application:.*','application: %s' % form.vars.google_application_id,data) + data = regex.sub('(applications/(%s)/.*)|' % '|'.join(ignore_apps),data) + write_file(yaml, data) + + path = request.env.applications_parent + cmd = '%s --email=%s --passin update %s' % \ + (form.vars.appcfg, form.vars.email, path) + p = cache.ram('gae_upload', + lambda s=subprocess,c=cmd:s.Popen(c, shell=True, + stdin=s.PIPE, + stdout=s.PIPE, + stderr=s.PIPE, close_fds=True),-1) + p.stdin.write(form.vars.password+'\n') + fcntl.fcntl(p.stdout.fileno(), fcntl.F_SETFL, os.O_NONBLOCK) + fcntl.fcntl(p.stderr.fileno(), fcntl.F_SETFL, os.O_NONBLOCK) + return dict(form=form,command=cmd) + +def callback(): + p = cache.ram('gae_upload',lambda:None,forever) + if not p or p.poll()!=None: + return '' + try: + output = p.stdout.read() + except: + output='' + try: + errors = p.stderr.read() + except: + errors='' + return (output+errors).replace('\n','
') ADDED applications/admin/controllers/mercurial.py Index: applications/admin/controllers/mercurial.py ================================================================== --- applications/admin/controllers/mercurial.py +++ applications/admin/controllers/mercurial.py @@ -0,0 +1,82 @@ +from gluon.fileutils import read_file, write_file + +if DEMO_MODE or MULTI_USER_MODE: + session.flash = T('disabled in demo mode') + redirect(URL('default','site')) +if not have_mercurial: + session.flash=T("Sorry, could not find mercurial installed") + redirect(URL('default','design',args=request.args(0))) + +_hgignore_content = """\ +syntax: glob +*~ +*.pyc +*.pyo +*.bak +*.bak2 +cache/* +private/* +uploads/* +databases/* +sessions/* +errors/* +""" + +def hg_repo(path): + import os + uio = ui.ui() + uio.quiet = True + if not os.environ.get('HGUSER') and not uio.config("ui", "username"): + os.environ['HGUSER'] = 'web2py@localhost' + try: + repo = hg.repository(ui=uio, path=path) + except: + repo = hg.repository(ui=uio, path=path, create=True) + hgignore = os.path.join(path, '.hgignore') + if not os.path.exists(hgignore): + write_file(hgignore, _hgignore_content) + return repo + +def commit(): + app = request.args(0) + path = apath(app, r=request) + repo = hg_repo(path) + form = FORM('Comment:',INPUT(_name='comment',requires=IS_NOT_EMPTY()), + INPUT(_type='submit',_value='Commit')) + if form.accepts(request.vars,session): + oldid = repo[repo.lookup('.')] + cmdutil.addremove(repo) + repo.commit(text=form.vars.comment) + if repo[repo.lookup('.')] == oldid: + response.flash = 'no changes' + try: + files = TABLE(*[TR(file) for file in repo[repo.lookup('.')].files()]) + changes = TABLE(TR(TH('revision'),TH('description'))) + for change in repo.changelog: + ctx=repo.changectx(change) + revision, description = ctx.rev(), ctx.description() + changes.append(TR(A(revision,_href=URL('revision', + args=(app,revision))), + description)) + except: + files = [] + changes = [] + return dict(form=form,files=files,changes=changes,repo=repo) + +def revision(): + app = request.args(0) + path = apath(app, r=request) + repo = hg_repo(path) + revision = request.args(1) + ctx=repo.changectx(revision) + form=FORM(INPUT(_type='submit',_value='revert')) + if form.accepts(request.vars): + hg.update(repo, revision) + session.flash = "reverted to revision %s" % ctx.rev() + redirect(URL('default','design',args=app)) + return dict( + files=ctx.files(), + rev=str(ctx.rev()), + desc=ctx.description(), + form=form + ) ADDED applications/admin/controllers/shell.py Index: applications/admin/controllers/shell.py ================================================================== --- applications/admin/controllers/shell.py +++ applications/admin/controllers/shell.py @@ -0,0 +1,45 @@ +import sys +import cStringIO +import gluon.contrib.shell +import code, thread +from gluon.shell import env + +if DEMO_MODE or MULTI_USER_MODE: + session.flash = T('disabled in demo mode') + redirect(URL('default','site')) + +FE=10**9 + +def index(): + app = request.args(0) or 'admin' + reset() + return dict(app=app) + +def callback(): + app = request.args[0] + command = request.vars.statement + escape = command[:1]!='!' + history = session['history:'+app] = session.get('history:'+app,gluon.contrib.shell.History()) + if not escape: + command = command[1:] + if command == '%reset': + reset() + return '*** reset ***' + elif command[0] == '%': + try: + command=session['commands:'+app][int(command[1:])] + except ValueError: + return '' + session['commands:'+app].append(command) + environ=env(app,True) + output = gluon.contrib.shell.run(history,command,environ) + k = len(session['commands:'+app]) - 1 + #output = PRE(output) + #return TABLE(TR('In[%i]:'%k,PRE(command)),TR('Out[%i]:'%k,output)) + return 'In [%i] : %s%s\n' % (k + 1, command, output) + +def reset(): + app = request.args(0) or 'admin' + session['commands:'+app] = [] + session['history:'+app] = gluon.contrib.shell.History() + return 'done' ADDED applications/admin/controllers/toolbar.py Index: applications/admin/controllers/toolbar.py ================================================================== --- applications/admin/controllers/toolbar.py +++ applications/admin/controllers/toolbar.py @@ -0,0 +1,29 @@ +import os +from gluon.settings import global_settings, read_file +# + +def index(): + app = request.args(0) + return dict(app=app) + +def profiler(): + """ + to use the profiler start web2py with -F profiler.log + """ + KEY = 'web2py_profiler_size' + filename = global_settings.cmd_options.profiler_filename + data = 'profiler disabled' + if filename: + if KEY in request.cookies: + size = int(request.cookies[KEY].value) + else: + size = 0 + if os.path.exists(filename): + data = read_file('profiler.log','rb') + if size=m: redirect(URL('step2')) + table=session.app['tables'][n] + form=SQLFORM.factory(Field('field_names','list:string', + default=session.app.get('table_'+table,[]))) + if form.accepts(request.vars) and form.vars.field_names: + fields=listify(form.vars.field_names) + if table=='auth_user': + for field in ['first_name','last_name','username','email','password']: + if not field in fields: + fields.append(field) + session.app['table_'+table]=[t.strip().lower() + for t in listify(form.vars.field_names) + if t.strip()] + try: + tables=sort_tables(session.app['tables']) + except RuntimeError: + response.flash=T('invalid circual reference') + else: + if n=m: redirect(URL('step4')) + page=session.app['pages'][n] + markmin_url='http://web2py.com/examples/static/markmin.html' + form=SQLFORM.factory(Field('content','text', + default=session.app.get('page_'+page,[]), + comment=A('use markmin',_href=markmin_url,_target='_blank')), + formstyle='table2cols') + if form.accepts(request.vars): + session.app['page_'+page]=form.vars.content + if n 0 +FONT_SIZE = 10 + +# Displays the editor in full screen mode. The value must be 'true' or 'false' +FULL_SCREEN = 'false' + +# Display a check box under the editor to allow the user to switch +# between the editor and a simple +# HTML text area. The value must be 'true' or 'false' +ALLOW_TOGGLE = 'true' + +# Replaces tab characters with space characters. +# The value can be 'false' (meaning that tabs are not replaced), +# or an integer > 0 that specifies the number of spaces to replace a tab with. +REPLACE_TAB_BY_SPACES = 4 + +# Toggle on/off the code editor instead of textarea on startup +DISPLAY = "onload" or "later" + +# if demo mode is True then admin works readonly and does not require login +DEMO_MODE = False + +# if visible_apps is not empty only listed apps will be accessible +FILTER_APPS = [] + +# To upload on google app engine this has to point to the proper appengine +# config file +import os +# extract google_appengine_x.x.x.zip to web2py root directory +#GAE_APPCFG = os.path.abspath(os.path.join('appcfg.py')) +# extract google_appengine_x.x.x.zip to applications/admin/private/ +GAE_APPCFG = os.path.abspath(os.path.join('/usr/local/bin/appcfg.py')) + +# To use web2py as a teaching tool, set MULTI_USER_MODE to True +MULTI_USER_MODE = False + +# configurable twitterbox, set to None/False to suppress +TWITTER_HASH = "web2py" + +# parameter for downloading LAYOUTS +LAYOUTS_APP = 'http://web2py.com/layouts' +#LAYOUTS_APP = 'http://127.0.0.1:8000/layouts' + + +# parameter for downloading PLUGINS +PLUGINS_APP = 'http://web2py.com/plugins' +#PLUGINS_APP = 'http://127.0.0.1:8000/plugins' + +# set the language +if 'adminLanguage' in request.cookies and not (request.cookies['adminLanguage'] is None): + T.force(request.cookies['adminLanguage'].value) ADDED applications/admin/models/0_imports.py Index: applications/admin/models/0_imports.py ================================================================== --- applications/admin/models/0_imports.py +++ applications/admin/models/0_imports.py @@ -0,0 +1,26 @@ +import time +import os +import sys +import re +import urllib +import cgi +import difflib +import shutil +import stat +import socket + +from textwrap import dedent + +try: + from mercurial import ui, hg, cmdutil + have_mercurial = True +except ImportError: + have_mercurial = False + +from gluon.utils import md5_hash +from gluon.fileutils import listdir, cleanpath, up +from gluon.fileutils import tar, tar_compiled, untar, fix_newlines +from gluon.languages import findT, update_all_languages +from gluon.myregex import * +from gluon.restricted import * +from gluon.compileapp import compile_application, remove_compiled_application ADDED applications/admin/models/access.py Index: applications/admin/models/access.py ================================================================== --- applications/admin/models/access.py +++ applications/admin/models/access.py @@ -0,0 +1,146 @@ +import os, time +from gluon import portalocker +from gluon.admin import apath +from gluon.fileutils import read_file +# ########################################################### +# ## make sure administrator is on localhost or https +# ########################################################### + +http_host = request.env.http_host.split(':')[0] + +if request.env.web2py_runtime_gae: + session_db = DAL('gae') + session.connect(request, response, db=session_db) + hosts = (http_host, ) + +if request.env.http_x_forwarded_for or request.is_https: + session.secure() +elif not request.is_local and not DEMO_MODE: + raise HTTP(200, T('Admin is disabled because insecure channel')) + +try: + _config = {} + port = int(request.env.server_port or 0) + restricted(read_file(apath('../parameters_%i.py' % port, request)), _config) + + if not 'password' in _config or not _config['password']: + raise HTTP(200, T('admin disabled because no admin password')) +except IOError: + import gluon.fileutils + if request.env.web2py_runtime_gae: + if gluon.fileutils.check_credentials(request): + session.authorized = True + session.last_time = time.time() + else: + raise HTTP(200, + T('admin disabled because not supported on google app engine')) + else: + raise HTTP(200, T('admin disabled because unable to access password file')) + + +def verify_password(password): + session.pam_user = None + if DEMO_MODE: + return True + elif not 'password' in _config: + return False + elif _config['password'].startswith('pam_user:'): + session.pam_user = _config['password'][9:].strip() + import gluon.contrib.pam + return gluon.contrib.pam.authenticate(session.pam_user,password) + else: + return _config['password'] == CRYPT()(password)[0] + + +# ########################################################### +# ## handle brute-force login attacks +# ########################################################### + +deny_file = os.path.join(request.folder, 'private', 'hosts.deny') +allowed_number_of_attempts = 5 +expiration_failed_logins = 3600 + +def read_hosts_deny(): + import datetime + hosts = {} + if os.path.exists(deny_file): + hosts = {} + f = open(deny_file, 'r') + portalocker.lock(f, portalocker.LOCK_SH) + for line in f.readlines(): + if not line.strip() or line.startswith('#'): + continue + fields = line.strip().split() + if len(fields) > 2: + hosts[fields[0].strip()] = ( # ip + int(fields[1].strip()), # n attemps + int(fields[2].strip()) # last attempts + ) + portalocker.unlock(f) + f.close() + return hosts + +def write_hosts_deny(denied_hosts): + f = open(deny_file, 'w') + portalocker.lock(f, portalocker.LOCK_EX) + for key, val in denied_hosts.items(): + if time.time()-val[1] < expiration_failed_logins: + line = '%s %s %s\n' % (key, val[0], val[1]) + f.write(line) + portalocker.unlock(f) + f.close() + +def login_record(success=True): + denied_hosts = read_hosts_deny() + val = (0,0) + if success and request.client in denied_hosts: + del denied_hosts[request.client] + elif not success and not request.is_local: + val = denied_hosts.get(request.client,(0,0)) + if time.time()-val[1]= allowed_number_of_attempts: + return val[0] # locked out + time.sleep(2**val[0]) + val = (val[0]+1,int(time.time())) + denied_hosts[request.client] = val + write_hosts_deny(denied_hosts) + return val[0] + + +# ########################################################### +# ## session expiration +# ########################################################### + +t0 = time.time() +if session.authorized: + + if session.last_time and session.last_time < t0 - EXPIRATION: + session.flash = T('session expired') + session.authorized = False + else: + session.last_time = t0 + +if not session.authorized and not \ + (request.controller == 'default' and \ + request.function in ('index','user')): + + if request.env.query_string: + query_string = '?' + request.env.query_string + else: + query_string = '' + + if request.env.web2py_original_uri: + url = request.env.web2py_original_uri + else: + url = request.env.path_info + query_string + redirect(URL(request.application, 'default', 'index', vars=dict(send=url))) +elif session.authorized and \ + request.controller == 'default' and \ + request.function == 'index': + redirect(URL(request.application, 'default', 'site')) + + +if request.controller=='appadmin' and DEMO_MODE: + session.flash = 'Appadmin disabled in demo mode' + redirect(URL('default','sites')) + ADDED applications/admin/models/buttons.py Index: applications/admin/models/buttons.py ================================================================== --- applications/admin/models/buttons.py +++ applications/admin/models/buttons.py @@ -0,0 +1,15 @@ +# Template helpers + +import os + +def button(href, label): + return A(SPAN(label),_class='button',_href=href) + +def sp_button(href, label): + return A(SPAN(label),_class='button special',_href=href) + +def helpicon(): + return IMG(_src=URL('static', 'images/help.png'), _alt='help') + +def searchbox(elementid): + return TAG[''](LABEL(IMG(_src=URL('static', 'images/search.png'), _alt=T('filter')), _class='icon', _for=elementid), ' ', INPUT(_id=elementid, _type='text', _size=12)) ADDED applications/admin/models/db.py Index: applications/admin/models/db.py ================================================================== --- applications/admin/models/db.py +++ applications/admin/models/db.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# this file is released under public domain and you can use without limitations + +if MULTI_USER_MODE: + db = DAL('sqlite://storage.sqlite') # if not, use SQLite or other DB + from gluon.tools import * + mail = Mail() # mailer + auth = Auth(globals(),db) # authentication/authorization + crud = Crud(globals(),db) # for CRUD helpers using auth + service = Service(globals()) # for json, xml, jsonrpc, xmlrpc, amfrpc + plugins = PluginManager() + + mail.settings.server = 'logging' or 'smtp.gmail.com:587' # your SMTP server + mail.settings.sender = 'you@gmail.com' # your email + mail.settings.login = 'username:password' # your credentials or None + + auth.settings.hmac_key = '' # before define_tables() + auth.define_tables() # creates all needed tables + auth.settings.mailer = mail # for user email verification + auth.settings.registration_requires_verification = False + auth.settings.registration_requires_approval = True + auth.messages.verify_email = 'Click on the link http://'+request.env.http_host+URL('default','user',args=['verify_email'])+'/%(key)s to verify your email' + auth.settings.reset_password_requires_verification = True + auth.messages.reset_password = 'Click on the link http://'+request.env.http_host+URL('default','user',args=['reset_password'])+'/%(key)s to reset your password' + + db.define_table('app',Field('name'),Field('owner',db.auth_user)) + +if not session.authorized and MULTI_USER_MODE: + if auth.user and not request.function=='user': + session.authorized = True + elif not request.function=='user': + redirect(URL('default','user/login')) + +def is_manager(): + if not MULTI_USER_MODE: + return True + elif auth.user and auth.user.id==1: + return True + else: + return False ADDED applications/admin/models/menu.py Index: applications/admin/models/menu.py ================================================================== --- applications/admin/models/menu.py +++ applications/admin/models/menu.py @@ -0,0 +1,32 @@ +# ########################################################### +# ## generate menu +# ########################################################### + +_a = request.application +_c = request.controller +_f = request.function +response.title = '%s %s' % (_f, '/'.join(request.args)) +response.subtitle = 'admin' +response.menu = [(T('site'), _f == 'site', URL(_a,'default','site'))] + +if request.args: + _t = request.args[0] + response.menu.append((T('edit'), _c == 'default' and _f == 'design', + URL(_a,'default','design',args=_t))) + response.menu.append((T('about'), _c == 'default' and _f == 'about', + URL(_a,'default','about',args=_t))) + response.menu.append((T('errors'), _c == 'default' and _f == 'errors', + URL(_a,'default','errors',args=_t))) + + response.menu.append((T('versioning'), + _c == 'mercurial' and _f == 'commit', + URL(_a,'mercurial','commit',args=_t))) + +if not session.authorized: + response.menu = [(T('login'), True, '')] +else: + response.menu.append((T('logout'), False, + URL(_a,'default',f='logout'))) + +response.menu.append((T('help'), False, URL('examples','default','index'))) + ADDED applications/admin/models/plugin_multiselect.py Index: applications/admin/models/plugin_multiselect.py ================================================================== --- applications/admin/models/plugin_multiselect.py +++ applications/admin/models/plugin_multiselect.py @@ -0,0 +1,4 @@ +response.files.append(URL('static','plugin_multiselect/jquery.dimensions.js')) +response.files.append(URL('static','plugin_multiselect/jquery.multiselect.js')) +response.files.append(URL('static','plugin_multiselect/jquery.multiselect.css')) +response.files.append(URL('static','plugin_multiselect/start.js')) ADDED applications/admin/modules/__init__.py Index: applications/admin/modules/__init__.py ================================================================== --- applications/admin/modules/__init__.py +++ applications/admin/modules/__init__.py ADDED applications/admin/static/css/calendar.css Index: applications/admin/static/css/calendar.css ================================================================== --- applications/admin/static/css/calendar.css +++ applications/admin/static/css/calendar.css @@ -0,0 +1,4 @@ +.calendar{z-index:99;position:relative;display:none;border-top:2px solid #fff;border-right:2px solid #000;border-bottom:2px solid #000;border-left:2px solid #fff;font-size:11px;color:#000;cursor:default;background:#d4d0c8;font-family:tahoma,verdana,sans-serif;}.calendar table{border-top:1px solid #000;border-right:1px solid #fff;border-bottom:1px solid #fff;border-left:1px solid #000;font-size:11px;color:#000;cursor:default;background:#d4d0c8;font-family:tahoma,verdana,sans-serif;}.calendar .button{text-align:center;padding:1px;border-top:1px solid #fff;border-right:1px solid #000;border-bottom:1px solid #000;border-left:1px solid #fff;}.calendar .nav{background:transparent}.calendar thead .title{font-weight:bold;padding:1px;border:1px solid #000;background:#848078;color:#fff;text-align:center;}.calendar thead .name{border-bottom:1px solid #000;padding:2px;text-align:center;background:#f4f0e8;}.calendar thead .weekend{color:#f00;}.calendar thead .hilite{border-top:2px solid #fff;border-right:2px solid #000;border-bottom:2px solid #000;border-left:2px solid #fff;padding:0;background-color:#e4e0d8;}.calendar thead .active{padding:2px 0 0 2px;border-top:1px solid #000;border-right:1px solid #fff;border-bottom:1px solid #fff;border-left:1px solid #000;background-color:#c4c0b8;}.calendar tbody .day{width:2em;text-align:right;padding:2px 4px 2px 2px;}.calendar tbody .day.othermonth{font-size:80%;color:#aaa;}.calendar tbody .day.othermonth.oweekend{color:#faa;}.calendar table .wn{padding:2px 3px 2px 2px;border-right:1px solid #000;background:#f4f0e8;}.calendar tbody .rowhilite td{background:#e4e0d8;}.calendar tbody .rowhilite td.wn{background:#d4d0c8;}.calendar tbody td.hilite{padding:1px 3px 1px 1px;border-top:1px solid #fff;border-right:1px solid #000;border-bottom:1px solid #000;border-left:1px solid #fff;}.calendar tbody td.active{padding:2px 2px 0 2px;border-top:1px solid #000;border-right:1px solid #fff;border-bottom:1px solid #fff;border-left:1px solid #000;}.calendar tbody td.selected{font-weight:bold;border-top:1px solid #000;border-right:1px solid #fff;border-bottom:1px solid #fff;border-left:1px solid #000;padding:2px 2px 0 2px;background:#e4e0d8;}.calendar tbody td.weekend{color:#f00;}.calendar tbody td.today{font-weight:bold;color:#00f;}.calendar tbody .disabled{color:#999;}.calendar tbody .emptycell{visibility:hidden;}.calendar tbody .emptyrow{display:none;}.calendar tfoot .ttip{background:#f4f0e8;padding:1px;border:1px solid #000;background:#848078;color:#fff;text-align:center;}.calendar tfoot .hilite{border-top:1px solid #fff;border-right:1px solid #000;border-bottom:1px solid #000;border-left:1px solid #fff;padding:1px;background:#e4e0d8;}.calendar tfoot .active{padding:2px 0 0 2px;border-top:1px solid #000;border-right:1px solid #fff;border-bottom:1px solid #fff;border-left:1px solid #000;}.calendar .combo{position:absolute;display:none;width:4em;top:0;left:0;cursor:default;border-top:1px solid #fff;border-right:1px solid #000;border-bottom:1px solid #000;border-left:1px solid #fff;background:#e4e0d8;font-size:90%;padding:1px;z-index:100;}.calendar .combo .label,.calendar .combo .label-IEfix{text-align:center;padding:1px;}.calendar .combo .label-IEfix{width:4em;}.calendar .combo .active{background:#c4c0b8;padding:0;border-top:1px solid #000;border-right:1px solid #fff;border-bottom:1px solid #fff;border-left:1px solid #000;}.calendar .combo .hilite{background:#048;color:#fea;}.calendar td.time{border-top:1px solid #000;padding:1px 0;text-align:center;background-color:#f4f0e8;}.calendar td.time .hour,.calendar td.time .minute,.calendar td.time .ampm{padding:0 3px 0 4px;border:1px solid #889;font-weight:bold;background-color:#fff;}.calendar td.time .ampm{text-align:center;}.calendar td.time .colon{padding:0 2px 0 3px;font-weight:bold;}.calendar td.time span.hilite{border-color:#000;background-color:#766;color:#fff;}.calendar td.time span.active{border-color:#f00;background-color:#000;color:#0f0;} + +#CP_hourcont {z-index:99;padding: 0px;position:absolute;border:1px dashed #bbbbbb;background-color:#dddddd;display:none;} #CP_minutecont {z-index:99;background-color:#dddddd;padding: 0px;position:absolute;width:45px;border: 1px dashed #cccccc;display:none;} .floatleft {float:left;} .CP_hour {z-index:99;padding:1px;font-family: Arial, Helvetica, sans-serif;font-size:9px;white-space:nowrap;cursor:pointer;width:35px;margin:1px;background-color:#eeeeee;} .CP_minute {z-index:99;padding:1px;background-color:#eeeeee;font-family: Arial, Helvetica, sans-serif;font-size:9px;white-space:nowrap;cursor:pointer;width:auto;margin:1px;} .CP_over {z-index:99;background-color:#ffffff;} + ADDED applications/admin/static/css/jqueryMultiSelect.css Index: applications/admin/static/css/jqueryMultiSelect.css ================================================================== --- applications/admin/static/css/jqueryMultiSelect.css +++ applications/admin/static/css/jqueryMultiSelect.css @@ -0,0 +1,47 @@ +.multiSelect { + width: 200px; + border: solid 1px #BBB; + background: #FFF right center no-repeat; + padding: 2px 4px; + padding-right: 20px; + display: inline; +} + +.multiSelect.hover { + background: right center no-repeat; +} + +.multiSelect.active, +.multiSelect.focus { + border: inset 1px #000; +} + +.multiSelect.active { + background: right center no-repeat; +} + +.multiSelectOptions { + width: 500px; + max-height: 150px; + margin-top: -1px; + overflow: auto; + border: solid 1px #B2B2B2; + background: #FFF; +} + +.multiSelectOptions LABEL { + padding: 2px 5px; + display: block; +} + +.multiSelectOptions LABEL.checked { + background: #E6E6E6; +} + +.multiSelectOptions LABEL.selectAll { + border-bottom: dotted 1px #CCC; +} + +.multiSelectOptions LABEL.hover { + background: #CFCFCF; +} ADDED applications/admin/static/css/styles.css Index: applications/admin/static/css/styles.css ================================================================== --- applications/admin/static/css/styles.css +++ applications/admin/static/css/styles.css @@ -0,0 +1,1065 @@ +/** +* cSans v0.6.3 +* 2009 Copyright A navalla suíza http://anavallasuiza.com +* cSans is released under the GNU Affero GPL version 3 - more information at http://www.fsf.org/licensing/licenses/agpl-3.0.html +*/ + +/* 1. RESET */ +html,body,div,span,object,iframe, +h1,h2,h3,h4,h5,h6,p,blockquote,pre, +a,abbr,acronym,address,code, +del,dfn,em,img,q,dl,dt,dd,ol,ul,li, +fieldset,form,label,legend,textarea, +table,caption,tbody,tfoot,thead,tr,th,td { + margin:0; + padding:0; + border:0; +} +textarea,select,input { font-size:1em; } +html,body { height:100%; } +body { + font-family:Arial,Helvetica,"Liberation Sans",Sans,sans-serif; + font-size: 12pt; + line-height:1.25em; + text-align:center; + background:#fff; + color:#000; +} +.page,.expanded-page { text-align:left; } + +/* 2. DEBUG: Show borders to stress an element */ +.omg-red,.omg-yellow,.omg-blue,.omg-green,.omg-black,.omg-white { + border-top:dotted 3px; + border-bottom:dotted 3px; +} +.omg-red { border-color:red; } +.omg-yellow { border-color:yellow; } +.omg-blue { border-color:blue; } +.omg-green { border-color:green; } +.omg-black { border-color:black; } +.omg-white { border-color:white; } + +/* 3. BASIC STYLES */ +/* 3.1. Titles */ +h1,h2,h3,h4,h5,h6 { + font-weight:bold; + line-height:1em; + margin:1em 0 0.5em 0; +} +h1 { font-size:2em; } +h2 { font-size:1.75em; } +h3 { font-size:1.5em; } +h4 { font-size:1.125em; } +h5 { font-size:1em; } +h6 { font-size:1em; font-weight:normal; } + +/* 3.2. Lists */ +ul,dd { margin-left:1em; } +ol { list-style-type:decimal; margin-left:1.5em; } +dl dt { font-weight:bold; } + +/* 3.3. Tables */ +table { border-collapse:collapse; border-spacing:0; } +caption,th { font-weight:bold; } +th,td { text-align:left; padding:0; border:1px solid #ccc; } +tfoot { font-style:italic; } + +/* 3.4. Images */ +a img { border:none; } +img.right { margin-left:1em; } +img.left { margin-right:1em; } + +/* 3.5. Forms */ +fieldset { border:1px solid #ccc; } +legend { font-weight:bold; font-size:1.2em; } +input { margin:0; } +input.text,input.password { overflow-y:visible; } +textarea { width:400px; height:100px; border:1px solid #ccc; overflow-y: auto; } +select { margin:0; font-size:1em; } +input,textarea,select { font-family:Arial,sans-serif; font-size: 1em; } + +/* 3.6. Monospace elements */ +pre,code,tt { font-family:"Courier New", Courier, monospace; line-height:1.5; } +pre,code { white-space:pre; } +tt { display:block; line-height:1.5; } + +/* 3.7. Block elements */ +p,form,table,address,blockquote,pre,code,tt,ul,ol,dl { margin-bottom:1em; } + +/* 3.8. Inline elements */ +em,dfn { font-style:italic; } + +/* 3.9. HR element */ +hr { + background:none; + visibility:hidden; + clear:both; + float:none; + width:100%; + height:1px; + border:none; + margin:-1px 0; +} + +/* 3.10. Flash objects */ +object { outline:none; } + +/* 4. UTILS */ +/* 4.1. Images with link and without link must overwrite background, width and height properties in your css */ +.image a,.image li,.image { + background-repeat:no-repeat; + background-color:transparent; + margin:0; + padding:0; + outline:none; + font-size:0px !important; + line-height:0em !important; + letter-spacing:-20px; + text-indent: -2px; + display:block; + overflow:hidden; + text-align:left; + border: none; +} +.image a { + display:block; + width:100%; + height:100%; + height:inherit; +} +ol.image,ul.image { + background:none; + width:100%; + height:auto; + float:left; +} +.image li { + float:left; +} +span.image,strong.image,em.image,a.image { + display:inline-block; + vertical-align:bottom; +} +input.image { cursor:pointer; } + +/* 4.2. Simple tabs system. You can overwrite background and text styles */ +.tabs { + list-style:none; + padding:0; + margin:0; + float:left; + width:100%; +} +.tabs li { + float:left; + margin:0 1px 0 0; +} +.tabs li span, +.tabs li a { + float: left; + padding:2px 5px; + white-space:nowrap; + text-align:center; + cursor:pointer; + outline:0; + text-decoration:none; +} +.tabs li span { cursor:default; } +.tabs .select a,.tabs a:hover { + background:#ddd; +} + +/* 4.3. Convert a block element (like a fieldset) into a inline element */ +.inline { + border:none; + margin:0; + padding:0; + display:inline; +} +fieldset.inline { + display:block; + float:left; + width: 100%; +} + +/* 4.4. Inline-block element must content any element inside (div, p, etc) for the correct visualization in FF<3 */ +.inline-block-top,.inline-block-middle,.inline-block-bottom { + float:none !important; + display:inline-block; +} +.inline-block-top { vertical-align:top !important; } +.inline-block-middle { vertical-align:middle !important; } +.inline-block-bottom { vertical-align:bottom !important; } + +/* 4.5. Float elements */ +.left { float:left !important; } +.right,.right-right { float:right !important; } +.right-right { text-align:right !important; } +.right-full { width:100%; text-align:right !important; } + +/* 4.6. Clear */ +.clear,.content,.page,.expanded-page { display:block; } +.clear:after,.tabs:after,.content:after,.page:after,.expanded-page:after { + content:" "; + display:block; + height:0; + clear:both; + visibility:hidden; + font-size:0; +} + +/* 4.7. Destroy margin collapse */ +.no-collapse { padding-top:1px; } + +/* 4.8. Hide an element */ +.hide { display:none; } + +/* 5. LAYOUT */ +/* 5.1. Row */ +.row { + float:left; + width:100%; + margin:0; + padding:0; + border:none; +} + +/* 5.2. Last column in a row */ +.last { margin-right:0 !important; } + +/* 5.3. Content */ +.content { + padding:0; + margin:0; +} + +/* 6. HACKS */ +img { -ms-interpolation-mode:bicubic; } /* IE */ +.clear,.tabs,.content,.page,.expanded-page { + *overflow-y:auto; /* IE7 */ + *overflow-x:hidden; /* IE7 */ + _height:1%; /* IE6 */ + _overflow-y:visible; /* IE6 */ + _overflow-x:visible; /* IE6 */ +} +a.image,span.image,strong.image,em.image,.inline-block-top,.inline-block-middle,.inline-block-bottom { + display:-moz-inline-box; /* FF<3 */ + -moz-box-orient:vertical; /* FF<3 */ + *display:inline; /* IE */ +} + +/** +* cSans Button plugin v0.3.1 +* 2009 Copyright A navalla suíza http://anavallasuiza.com +* cSans is released under the GNU Affero GPL version 3 - more information at http://www.fsf.org/licensing/licenses/agpl-3.0.html +*/ + +/* 1. BASIC STYLES */ + +ul.button { + list-style: none; + padding: 0; + margin: 0; + display:block; + } +ul.button { + overflow-y: auto; + overflow-x: hidden; + } +ul.button li { + float: left; + margin: 0; + } +ul.button li a { + display: block; + z-index: 2; + } +a.button { + display: inline-block; + vertical-align: middle; + } + +/* 1.1. Normal status */ +a.button, +ul.button li a { + background-position: 100% 0; + background-repeat: no-repeat; + white-space: nowrap; + text-align: center; + cursor: pointer; + outline: 0; + } +a.button span, +ul.button li a span { + display: block; + background-position: 0 0; + background-repeat: no-repeat; + } + +/* 1.2. Hover/selected status */ +a.button:hover, +a.button.select, +ul.button li a:hover, +ul.button li.select a { + background-position: 100% -70px !important; + } +a.button:hover span, +a.button.select span, +ul.button li a:hover span, +ul.button li.select a span { + background-position: 0 -70px !important; + } + +/* 2. EDITABLE STYLES */ + +/* 2.1. Image background used */ +a.button, +.button a, +.button span { + background-image: url(../images/button.png); + } + +/* 2.2. Normal status (Example for padding 10px) */ +a.button, +.button a { + padding: 0 10px 0 0; /* Padding-right: 10px */ + margin: 0 1px 0 10px; /* Margin-left: 10px */ + text-decoration: none; + } +.button span { + padding: 4px 0 6px 10px; /*Padding-left: 10px */ + margin: 0 0 0 -10px; /* Margin-left: -10px */ + } + +/* 3. HACKS */ + +ul.button { + _height: 1%; /* IE6 */ + _overflow-y: visible; /* IE6 */ + _overflow-x: visible; /* IE6 */ + } +a.button span { + _float: left; /* Only IE6 */ + _position: relative; /* Only IE6 */ + } +a.button { + display: -moz-inline-box; /* FF<3 */ + display: inline-block; /* FF<3 */ + -moz-box-orient: vertical; /* FF<3 */ + *display: inline; /* IE */ + } + +/** +* cSans Tooltip pluging v0.1 +* 2009 Copyright A navalla suíza http://anavallasuiza.com +* cSans is released under the GNU Affero GPL version 3 - more information at http://www.fsf.org/licensing/licenses/agpl-3.0.html +*/ + +/* BASIC STYLES */ + +.tooltip:hover { + background:transparent; + text-decoration:none; + } +.tooltip span { + display:none; + padding:5px; + margin-left:10px; + width:150px; + } +.tooltip:hover span { + cursor: default; + display:inline; + position:absolute; + } + +/** +* cSans Flexible v0.1 +* 2009 Copyright A navalla suíza http://anavallasuiza.com +* cSans is released under the GNU Affero GPL version 3 - more information at http://www.fsf.org/licensing/licenses/agpl-3.0.html +*/ + +/* 1. BASIC STYLES */ +/* 1.1 Widths */ +.f10 { width:10%; } +.f20 { width:20%; } +.f25 { width:25%; } +.f30 { width:30%; } +.f33 { width:33.33%; } +.f40 { width:40%; } +.f50 { width:50%; } +.f60 { width:60%; } +.f66 { width:66.66%; } +.f70 { width:70%; } +.f75 { width:75%; } +.f80 { width:80%; } +.f90 { width:90%; } +.f100 { width:100%; } + +.f10,.f20,.f25,.f30,.f33,.f40,.f50,.f60,.f66,.f70,.f75,.f80,.f90,.f100 { + float:left; + overflow:hidden; +} + +/* 1.2 Margin-left */ +.fl10 { margin-left:10%; } +.fl20 { margin-left:20%; } +.fl25 { margin-left:25%; } +.fl30 { margin-left:30%; } +.fl33 { margin-left:33.33%; } +.fl40 { margin-left:40%; } +.fl50 { margin-left:50%; } +.fl60 { margin-left:60%; } +.fl66 { margin-left:66.66%; } +.fl70 { margin-left:70%; } +.fl75 { margin-left:75%; } +.fl80 { margin-left:80%; } +.fl90 { margin-left:90%; } +.fl100 { margin-left:100%; } + +/* 1.3 Margin-right */ +.fr10 { margin-right:10%; } +.fr20 { margin-right:20%; } +.fr25 { margin-right:25%; } +.fr30 { margin-right:30%; } +.fr33 { margin-right:33.33%; } +.fr40 { margin-right:40%; } +.fr50 { margin-right:50%; } +.fr60 { margin-right:60%; } +.fr66 { margin-right:66.66%; } +.fr70 { margin-right:70%; } +.fr75 { margin-right:75%; } +.fr80 { margin-right:80%; } +.fr90 { margin-right:90%; } +.fr100 { margin-right:100%; } + +/* 2. HACKS */ +.f10,.f20,.f25,.f30,.f33,.f40,.f50,.f60,.f66,.f70,.f75,.f80,.f90,.f100 { + _display:inline; /* IE5-6 */ + *margin-left:-1px; /* IE5-7 */ +} + +/** +* web2py Nuovo Theme +* ------------------ +**/ + +/* Basics */ + +html, body { + font-size: 13px; + text-align: left; + color: #333; + padding: 0; + margin: 0; + background: #fff url(../images/header_shadow.png) repeat-x left 33px; +} + +a { + color: #e8953c; + text-decoration: none; +} + +label { + color: #777; + font-weight: bold; + font-size: 100%; +} + +img { + vertical-align: baseline; +} + +td, th { + border: none; +} + +.center { + text-align: center; +} + +.centerblock { + margin: 0 auto; +} + +.clear { + clear: both; +} + +.att { + color: #d22; +} + +/* General */ + +h3 { + padding-left: 18px; + background: url(../images/sidebar_bullet.gif) no-repeat; + color: #555; + font-weight: normal; + font-size: 130%; +} + +.formfield { + padding: 0.7em 0.5em; + -moz-border-radius: 0.3em; + border-radius: 0.3em; + margin: 1em 0; + border: 1px solid #ddd; +} + +.buttongroup { + padding: 0.7em 0.5em; +} + +.formfield { + background: #eee; +} + +.flash { + position: fixed; + top: 2em; + right: 2em; + background: #e8953c; + color: #fff; + border: 2px solid #fff; + -moz-border-radius: 0.7em; + border-radius: 0.7em; + padding: 0.5em 1em; +} + +.tooltip span { + background: #9fb364; + color: #eef1d9; + border: 1px solid #eef1d9; + font-style: italic; + width: 20%; + padding: 0.3em; + -moz-border-radius: 0.5em; + border-radius: 0.5em; + font-size: 13px; + text-transform: none; +} + +.help { + width: 60%; + font-size: 1em; + padding: 0.3em; + -moz-border-radius: 0.5em; + border-radius: 0.5em; + background: #eef1d9; + color: #9fb364; + font-style: italic; + text-transform: none; +} + +.help h3 { + color: #9fb364; + font-size: 1.2em; + background: transparent; + font-weight: bold; +} + +.icon img { + vertical-align: middle; + cursor: pointer; +} + +.form td { + padding: 0.2em 1em 0.2em 0; +} + +/* Buttons */ + +.controls a.button, +.controls a.button span { + background-image: url(../images/small_button.png); +} + +.controls a.button span { + padding-top: 2px; +} + +.controls a.button { + color: #333; +} + +.controls a.special, +.controls a.special span { + background-image: url(../images/small_special_button.png); +} + +.controls a.special { + color: #ddd; +} + +/* Header */ + +#header { + background: #292929 url(../images/header_bg.png) repeat-x; + height: 33px; + overflow: hidden; +} + +/* Home button */ + +#start { + position: absolute; + top: 2px; + left: 13px; + margin: 0; +} + +#start a.button { + display: block; +} + +#start a.button, +#start a.button span { + background-image: url(../images/start.png); +} + +#start a { + text-indent: -999px; + overflow: hidden; + padding: 0; + margin: 0; +} + +#start a.button span { + width: 112px; + height: 36px; + padding: 0; + margin: 0; +} + +/* Menu */ + +#menu { + float: right; + margin: 3px 13px 0 0; +} + +#menu li { + float: left; + list-style: none; + margin-right: 0.4em; +} + +#menu a.button, +#menu a.button span { + background-image: url(../images/menu.png); +} + +#menu a.button { + padding-right: 1em; +} + +#menu a.button span { + padding-left: 1em; +} + +#menu a { + color: #333; +} + +/* Main area */ + +#main { + padding: 2em 1em 5em; + position: relative; +} + +#main h2 { + margin-top: 0; + font-weight: normal; + text-transform: uppercase; + border-bottom: 1px dotted #aaa; + padding-left: 18px; + background: transparent url(../images/section_bullet.png) no-repeat left 3px; + color: #aaa; +} + +/* Applist */ + +.applist h3 { + color: #aaa; + font-weight: normal; +} + +.applist ul { + margin: 0; +} + +.applist li { + list-style: none; + padding: 0; +} + +h3.editableapp, +h3.currentapp { + padding: 5px 0 5px 54px; +} + +h3.editableapp { + background: #fff url(../images/folder.png) no-repeat; +} + +h3.currentapp { + background: #fff url(../images/folder_locked.png) no-repeat; +} + +.applist .controls { + margin-left: 1.5em; +} + + +/* Site sidebar */ + +.sidebar_inner { + margin: 0 1em 0; + -moz-border-radius: 0.5em; + border-radius: 0.5em; + border: 1px solid #ddd; + min-width:400px; +} + +.sidebar h4 { + color: #888; +} + +.pwdchange { + padding: 1em; + float: right !important; +} + +.sidebar .box { + clear: right; + margin-top: 2em; + border-top: 1px solid #eee; + padding: 0 1em; +} + +.sidebar .box { + background: url(../images/sidebar_background.jpg) no-repeat; +} + +.sidebar .upgrade_version { + color: #71c837; +} + +/* Tweets */ + +#tweets ol { + margin: 1em 0; +} + +#tweets ol li { + background: #ebe8d0; + list-style: none; + -moz-border-radius: 0.5em; + border-radius: 0.5em; + padding: 0.5em; + margin: 1em 0; + border: 1px solid #aaa; +} + +#tweets .entry-date { + font-weight: bold; + display: block; +} + +/* Design/Plugin page */ + +.component { + cursor: pointer; +} + +.component_contents { + padding-left: 20px; +} + +.component_contents li { + list-style: none; +} + +.component_contents div.comptools { + margin-bottom: 1em; + padding-bottom: 0.5em; +} + +.component_contents div.formfield form { + margin-bottom: 0.2em; + margin-top: 0.2em; +} + +.file { + font-weight: bold; +} + +.folder { + display: block; + padding: 4px 0 4px 40px; + background: url(../images/folder_sm.png) no-repeat; + margin: 0.5em 0; +} + +.folder .file { + font-weight: bold; +} + +.sublist { + margin-left: 0; + border-left: 1px dotted #aaa; + padding-left: 0.5em; + margin-top: 0.5em; + margin-bottom: 0.0em; +} + +/* About */ + +.legalese { + background: #eee url(../images/embossed.png) repeat-y; + padding: 1em 1em 1em 2em; +} + +/* Wizard */ + +.step li { + list-style: none; + margin-left: 1em; + margin-top: 0.5em; +} + +.step #wizard_nav .box { + border-bottom: 1px dotted #aaa; + padding: 0.5em; +} + +.step #wizard_form { + padding: 0.5em 0 2em 2em; +} + +/* Editor */ + +.edit #body { + height: auto; + width: 100%; +} + +.edit .help li { + list-style: none; + padding: 0.5em 0; +} + +.edit .help tt { + font-weight: bold; + font-style: normal; + display: inline; /* Rest cSans base style */ + border: 1px solid #999; + background: #333; + color: #ddd; + padding: 0.3em; + -moz-border-radius: 0.3em; + border-radius: 0.3em; +} + +/* Ticket */ + +ul#snapshot > li { + list-style: none; +} + +.inspect td, +.versions td, +.inspect th, +.versions th { + padding: 0.3em; + // border: 1px solid #aaa; +} + +.inspect th, +.versions th { + background: #ddd; + color: #777; +} + +.ticket h3 { + margin-top: 0.5em; + background: url(../images/ticket_section.png) no-repeat; + padding: 30px; + text-transform: uppercase; +} + + +.ticket .inspect li { + list-style: none; +} + +#frames ul { + margin: 0; +} + +#frames li { + margin: 0.5em 0; + padding: 0; + list-style: none; +} + +/* Errors */ + +.errors table.sortable th { + background: url(../images/header_bg.png) repeat-x; + color: #eee; + // border-right: 1px solid #eee; + padding-top: 0.5em; +} + +.errors table.sortable td { + border-bottom: 1px dotted #ddd; + padding: 0.4em 0.2em; +} + +/* Tests */ + +.test h3.failed { + background-image: url(../images/red_bullet.gif); +} + +.test h3.nodoctests { + background-image: url(../images/dim_bullet.gif); +} + +/* Footer */ + +#footer { + padding: 1em 0 0; + color: #eee; + text-align: center; + background: #292929 url(../images/header_bg.png) repeat-x; + height: 45px; + overflow: hidden; + clear: both; +} + +/* Shell */ + +.shell #wrapper { + margin: 0 auto; +} + +.shell #output { + width: 75%; + height:30em; +} + +.shell #output, +.shell #output pre { + color: #e8953c; + background: white; + border: 1px solid #333; +} + +.shell .prompt, +.shell #output, +.shell pre, +.shell #caret { + font-family: monospace; +} + +.shell .prompt, +.shell #output, +.shell #caret { + font-size: 10pt; + padding: 6px; + padding-right: 0em; +} + +.shell #shellwrapper { + background: white; + border: 1px solid #333; + color: #e8953c; + width: 75%; + // padding: 6px; + // -moz-border-radius: 1em; + // border-radius: 1em; + margin: 1em 0 +} + +.shell #caret { + border: 0; + float: left; +} + +.shell .prompt { + color: #e8953c; + width: 85%; + height: 4em; + border: 0; +} + +.shell .prompt, .shell #output { + overflow: auto; +} + +.shell table, tr, td { + text-align: left; + vertical-align: top; +} + +.shell pre { + border: 0; + padding: 0; + margin: 0; + color: #333333; +} + +.shell .message { + width: 100%; + color: #8AD; + font-weight: bold; + font-style: italic; +} + +.shell .error { + color: #F44; +} + +.shell .username { + font-weight: bold; +} + +.shell dd{ + color: #000033; +} + +.shell dt{ + color: #333333; +} + +.shell #ajax-status { + font-weight: bold; +} + +.shell .processing { + background-image: url('../images/spinner.gif'); +} + +.shell #caret { + width: 2.5em; + margin-right: 0px; + padding-right: 0px; + border-right: 0px; +} + + +/* ie7 hacks */ + +.sublist { + zoom: 1; +} + +.untranslated { background-color: #FFCC00; } +.translated { background-color: white; } +.ui-multiselect { border: 1px solid #ccc; width:400px;} +#editor_area textarea { height: 400px; width: 100% } ADDED applications/admin/static/eamy/bundle_markup.js Index: applications/admin/static/eamy/bundle_markup.js ================================================================== --- applications/admin/static/eamy/bundle_markup.js +++ applications/admin/static/eamy/bundle_markup.js @@ -0,0 +1,389 @@ +/* + * eAmy.Offline - Amy Editor embedded for offline use. + * http://www.april-child.com/amy + * + * Published under MIT License. + * Copyright (c) 2007-2008 Petr Krontorád, April-Child.com + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + * + * + * This file is auto-generated from original Fry Framework and Amy Editor sources.. + */ + + +// Generated from theme definition file. +$class('ac.chap.theme.EAmy < ac.chap.Theme'); + + ac.chap.theme.EAmy.prototype.initDefinition = function() + { +// $call(this, 'ac.chap.Theme.initDefinition');this.cssId = 'black'; +//this.background = '#072240'; +//this.textColor = '#DFEFFF'; +//this.caretColor = 'lime'; +//this.caretRowStyleActive = '#041629'; +//this.selectionStyle = '#86553b'; +//this.colorScheme[ac.chap.TOKEN_MULTIROW_COMMENT] = 'color:#0084FF;font-style:italic'; +//this.colorScheme[ac.chap.TOKEN_SINGLEROW_COMMENT] = 'color:#0084FF;font-style:italic'; +//this.colorScheme[ac.chap.TOKEN_SINGLE_QUOTED] = 'color:#00DF00'; +//this.colorScheme[ac.chap.TOKEN_DOUBLE_QUOTED] = 'color:#00DF00'; +//this.colorScheme[ac.chap.CHUNK_KEYWORD] = 'color:#FF9D00'; +//this.colorScheme[ac.chap.CHUNK_NUMBER] = 'color:#FF5B8C'; +//this.colorScheme[ac.chap.CHUNK_OPERATOR] = 'color:#FF9D00;'; +//this.colorScheme[ac.chap.CHUNK_PARENTHESIS] = 'color:#FFF177'; +//this.colorScheme[ac.chap.CHUNK_KEYWORD_CUSTOM] = 'color:#54FFB8'; +//this.colorScheme[ac.chap.CHUNK_FUNCTION_NAME] = 'color:#FFE000'; +//this.colorScheme[ac.chap.CHUNK_LIBRARY] = 'color:#71E5B6'; +//this.colorScheme[ac.chap.CHUNK_LIBRARY_CUSTOM] = 'color:#FF78E5'; + + $call(this, 'ac.chap.Theme.initDefinition');this.cssId = 'twilight'; +this.background = '#141414'; +this.textColor = '#F8F8F8'; +this.caretColor = '#A7A7A7'; +this.caretRowStyleActive = '#1B1B1B'; +this.selectionStyle = '#3C4043'; +this.colorScheme[ac.chap.TOKEN_MULTIROW_COMMENT] = 'color:#605A60;font-style:italic'; +this.colorScheme[ac.chap.TOKEN_SINGLEROW_COMMENT] = 'color:#605A60;font-style:italic'; +this.colorScheme[ac.chap.TOKEN_SINGLE_QUOTED] = 'color:#8B9F67'; +this.colorScheme[ac.chap.TOKEN_DOUBLE_QUOTED] = 'color:#D4F29E'; +this.colorScheme[ac.chap.CHUNK_KEYWORD] = 'color:#D2A964'; +this.colorScheme[ac.chap.CHUNK_NUMBER] = 'color:#DE6848'; +this.colorScheme[ac.chap.CHUNK_OPERATOR] = 'color:#EFC25A;'; +this.colorScheme[ac.chap.CHUNK_PARENTHESIS] = 'color:#ABC4DC'; +this.colorScheme[ac.chap.CHUNK_KEYWORD_CUSTOM] = 'color:#A0849E'; +this.colorScheme[ac.chap.CHUNK_FUNCTION_NAME] = 'color:#DAD280'; +this.colorScheme[ac.chap.CHUNK_LIBRARY] = 'color:#7286A8'; +this.colorScheme[ac.chap.CHUNK_LIBRARY_CUSTOM] = 'color:#A55C29'; +} + + +// Generated from bundle keymap definition file. +ac.chap.KeyMap.prototype.initDefinition = function() + { + var _ = '\n'; + this.compile + (""+_+ "KEY: 0" ++_+ " insert(character:true)" ++_+ "KEY: -37" ++_+ " caret(move:'left')" ++_+ "KEY: -37+shift" ++_+ " caret(move:'left')" ++_+ " selection(add:true)" ++_+ "KEY: -37+ctrl" ++_+ " caret(move:'prev_regexp', re:'[^|._A-Z ,|(|);]*$')" ++_+ "KEY: -37+alt" ++_+ " caret(move:'prev_word')" ++_+ "KEY: -37+ctrl+shift" ++_+ " caret(move:'prev_regexp', re:'[^|._A-Z ,|(|);]*$')" ++_+ " selection(add:true)" ++_+ "KEY: -37+alt+shift" ++_+ " caret(move:'prev_word')" ++_+ " selection(add:true)" ++_+ "KEY: -37+meta" ++_+ " caret(move:'row_start')" ++_+ "KEY: -37+meta+shift" ++_+ " caret(move:'row_start')" ++_+ " selection(add:true)" ++_+ "KEY: -39" ++_+ " caret(move:'right')" ++_+ "KEY: -39+shift" ++_+ " caret(move:'right')" ++_+ " selection(add:true)" ++_+ "KEY: -39+ctrl" ++_+ " caret(move:'next_regexp', re:'^[^|._A-Z ,|(|);]*')" ++_+ "KEY: -39+alt" ++_+ " caret(move:'next_word')" ++_+ "KEY: -39+ctrl+shift" ++_+ " caret(move:'next_regexp', re:'^[^|._A-Z ,|(|);]*')" ++_+ " selection(add:true)" ++_+ "KEY: -39+alt+shift" ++_+ " caret(move:'next_word')" ++_+ " selection(add:true)" ++_+ "KEY: -39+meta" ++_+ " caret(move:'row_end')" ++_+ "KEY: -39+meta+shift" ++_+ " caret(move:'row_end')" ++_+ " selection(add:true)" ++_+ "KEY: -38" ++_+ " caret(move:'up')" ++_+ "KEY: -38+shift" ++_+ " caret(move:'up')" ++_+ " selection(add:true)" ++_+ "KEY: -40" ++_+ " caret(move:'down')" ++_+ "KEY: -40+shift" ++_+ " caret(move:'down')" ++_+ " selection(add:true)" ++_+ "KEY: -13" ++_+ " insert(row:true)" ++_+ "KEY: -8" ++_+ " delete(character:true)" ++_+ "KEY: -46" ++_+ " delete(character:false)" ++_+ "KEY: 75+ctrl+shift" ++_+ " delete(row:true)" ++_+ "KEY: -27" ++_+ " custom(action:'WordComplete', direction:true)" ++_+ "KEY: -27+shift" ++_+ " custom(action:'WordComplete', direction:false)" ++_+ "KEY: -9" ++_+ " custom(action:'SnippetComplete')" ++_+ "KEY: 123" ++_+ " custom(action:'AutoComplete', use_selection:true, text:'}')" ++_+ "KEY: 34" ++_+ " custom(action:'AutoComplete', use_selection:true, text:'\"')" ++_+ "KEY: 91" ++_+ " custom(action:'AutoComplete', use_selection:true, text:']')" ++_+ "KEY: 40" ++_+ " custom(action:'AutoComplete', use_selection:true, text:')')" ++_+ "KEY: -36" ++_+ " caret(move:'doc_start')" ++_+ "KEY: -36+shift" ++_+ " caret(move:'doc_start')" ++_+ " selection(add:true)" ++_+ "KEY: -35" ++_+ " caret(move:'doc_end')" ++_+ "KEY: -35+shift" ++_+ " caret(move:'doc_end')" ++_+ " selection(add:true)" ++_+ "KEY: -34+meta" ++_+ " caret(move:'page_down')" ++_+ "KEY: -34+meta+shift" ++_+ " caret(move:'page_down')" ++_+ " selection(add:true)" ++_+ "KEY: -33+meta" ++_+ " caret(move:'page_up')" ++_+ "KEY: -33+meta+shift" ++_+ " caret(move:'page_down')" ++_+ " selection(add:true)" ++_+ "KEY: 99+meta" ++_+ " clipboard(copy:true)" ++_+ "KEY: 120+meta" ++_+ " clipboard(cut:true)" ++_+ "KEY: 122+meta" ++_+ " undo()" ++_+ "KEY: 90+meta+shift" ++_+ " redo()" ++_+ "KEY: 97+meta" ++_+ " selection(all:true)" ++_+ "KEY: 97+ctrl" ++_+ " selection(all:true)" ++_+ "KEY: -113" ++_+ " custom(action:'GoToBookmark', direction:1)" ++_+ "KEY: -113+shift" ++_+ " custom(action:'GoToBookmark', direction:-1)" ++_+ "KEY: -113+meta" ++_+ " custom(action:'ToggleBookmark')" ++_+ "KEY: 91+meta" ++_+ " custom(action:'Indent', direction:'left')" ++_+ "KEY: 93+meta" ++_+ " custom(action:'Indent', direction:'right')" ++_+ "KEY: 47+meta" ++_+ " custom(action:'Comment')" ++_+ "KEY: 43+meta" ++_+ " custom(action:'RuntimeOption', key:'font.size', value:'bigger')" ++_+ "KEY: 45+meta" ++_+ " custom(action:'RuntimeOption', key:'font.size', value:'smaller')" ++_+ "KEY: 101+meta" ++_+ " custom(action:'SetSearchKeyword')" ++_+ "KEY: 103+meta" ++_+ " custom(action:'SearchKeyword', direction:'down')" ++_+ "KEY: 71+shift+meta" ++_+ " custom(action:'SearchKeyword', direction:'up')" ++_+ "KEY: 102+ctrl" ++_+ " custom(action:'SearchInteractive')" ++_+ "KEY: 83+ctrl+shift" ++_+ " custom(action:'SearchInteractive')" ++_+ "KEY: 102+meta" ++_+ " custom(action:'SearchInteractive')" ++_+ "KEY: -13" ++_+ " custom(action:'SmartIndent', split_line:true, indent_tab_when_starts:'class module def if else unless rescue ensure while do __class__')" ++_+ "KEY: -13+meta" ++_+ " custom(action:'SmartIndent', split_line:false, indent_tab_when_starts:'class module def if else unless rescue ensure while do __class__')" ++_+ "KEY: 39" ++_+ " custom(action:'AutoComplete', use_selection:true, text:'\\'')" +)}; + +$class('ac.chap.lang.EAmy < ac.chap.Language'); + + ac.chap.lang.EAmy.prototype.initDefinition = function() + { + $call(this, 'ac.chap.Language.initDefinition'); +this.singleQuoteStringMarker = "'"; +this.singleQuoteStringMarkerException = "\\"; +this.doubleQuoteStringMarker = "\""; +this.doubleQuoteStringMarkerException = "\\" +this.wordDelimiter = /[\w\.\d]/; +this.indentIgnoreMarker = /[\.]/; +this.foldingStartMarkers = [/^\s*<(div)\b.*>/i, /^\s*<(ul)\b.*>/i]; +this.foldingParityMarkers = [/^\s*<(div)\b.*>/i, /^\s*<(ul)\b.*>/i]; +this.foldingStopMarkers = [/^\s*<\/(div)>/i, /^\s*<\/(ul)>/i]; +this.singleRowCommentStartMarkers = []; +this.multiRowCommentStartMarker = ""; +this.chunkRules.push([/(([^\w]|^)(\d{1,}[\d\.Ee]*)([^w]|$))/i, 3, ac.chap.CHUNK_NUMBER]) +this.chunkRules.push([/(\+|\-|\*|\/|\=|\!|\^|\%|\||\&|\<|\>)/i, 0, ac.chap.CHUNK_OPERATOR]) +this.chunkRules.push([/(\(|\)|\[|\]|\{|\})/i, 0, ac.chap.CHUNK_PARENTHESIS]) +this.chunkRules.push([/((<|<\/)([\w-_\:]*)([ >]))/i, 3, ac.chap.CHUNK_KEYWORD]) +this.chunkRules.push([/(([ \t])([\w-_\:]*)(=$))/i, 3, ac.chap.CHUNK_KEYWORD_CUSTOM]) +this.chunkRules.push([/(([^\w]|^)(!DOCTYPE)([^\w]|$))/i, 3, ac.chap.CHUNK_LIBRARY]) +this.chunkRules.push([/(([^\w]|^)(\d{1,}[\d\.Ee]*)([^w]|$))/i, 3, ac.chap.CHUNK_NUMBER]) +this.chunkRules.push([/(\+|\-|\*|\/|\=|\!|\^|\%|\||\&|\<|\>)/i, 0, ac.chap.CHUNK_OPERATOR]) +this.chunkRules.push([/(\(|\)|\[|\]|\{|\})/i, 0, ac.chap.CHUNK_PARENTHESIS]) +this.chunkRules.push([/((<|<\/)([\w-_\:]*)([ >]))/i, 3, ac.chap.CHUNK_KEYWORD]) +this.chunkRules.push([/(([ \t])([\w-_\:]*)(=$))/i, 3, ac.chap.CHUNK_KEYWORD_CUSTOM]) +this.chunkRules.push([/(([^\w]|^)(!DOCTYPE)([^\w]|$))/i, 3, ac.chap.CHUNK_LIBRARY]) +} +var snippet = {}; +snippet = {tab_activation: '', code: ''}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'ie6', code: '$0'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'iegte7', code: '$0'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'ie5', code: '$0'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'ie', code: '$0'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'ienot', code: '${1:${AMY_SELECTED_TEXT: IE Conditional Comment: NOT Internet Explorer }}$0'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'ielte6', code: '$0'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: '', code: '$0'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'ielt6', code: '$0'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: '', code: ''}; +eamy.snippets.push(snippet); +snippet = {tab_activation: '', code: '${0:${AMY_SELECTED_TEXT/\A(.*)<\/em>\z|.*/(?1:$1:$0<\/em>)/m}}'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: '', code: '${0:${AMY_SELECTED_TEXT/\A(.*)<\/strong>\z|.*/(?1:$1:$0<\/strong>)/m}}'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: '', code: ''}; +eamy.snippets.push(snippet); +snippet = {tab_activation: '', code: ''}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'left', code: '←'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'backtab', code: '⇤'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'enter', code: '⌅'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'arrow', code: '→'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'option', code: '⌥'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'shift', code: '⇧'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: '', code: ' '}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'delete', code: '⌦'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'backspace', code: '⌫'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'escape', code: '⎋'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'tab', code: '⇥'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'up', code: '↑'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'control', code: '⌃'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'return', code: '↩'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'down', code: '↓'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'command', code: '⌘'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: '', code: ''}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'doctype', code: '\n'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'doctype', code: '\n'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'doctypexf', code: '\n'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'doctypext', code: '\n'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'doctypex', code: '\n'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'doctypexs', code: '\n'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 't', code: '<{${1:tag_name}}>$0'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: '', code: ''}; +eamy.snippets.push(snippet); +snippet = {tab_activation: '', code: ''}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'body', code: '\n $0\n'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'textarea', code: ''}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'div', code: '\n ${0:$AMY_SELECTED_TEXT}\n'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: '', code: '
'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'title', code: '${1:${AMY_FILENAME/((.+)\..*)?/(?2:$2:Page Title)/}}'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'movie', code: '\n \n \n \n \n'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'input', code: ''}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'head', code: '\n \n ${1:${AMY_FILENAME/((.+)\..*)?/(?2:$2:Page Title)/}}\n $0\n'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'meta', code: ''}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'h1', code: '

${1:$AMY_SELECTED_TEXT}

'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'form', code: '\n $0\n\n

\n'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'link', code: ''}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'style', code: ''}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'table', code: '
\n \n \n
${5:Header}
${0:Data}
'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'base', code: ''}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'scriptsrc', code: ''}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'mailto', code: '
${3:email me}'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'script', code: ''}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'c', code: 'class="$1"'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'i', code: 'id="$1"'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'p', code: '{{pass}}'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'ex', code: '{{extend \'${1:layout.html}\'}}'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'for', code: '{{for ${1:bar} in ${2:foo}:}}\n $0\n{{pass}}'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'if', code: '{{if ${1:foo} ${2:==/!=/=>/=/<} ${3:bar}:}}\n $0\n{{pass}}'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: '=', code: '{{=$0}}'}; +eamy.snippets.push(snippet); + + ADDED applications/admin/static/eamy/bundle_python.js Index: applications/admin/static/eamy/bundle_python.js ================================================================== --- applications/admin/static/eamy/bundle_python.js +++ applications/admin/static/eamy/bundle_python.js @@ -0,0 +1,296 @@ +/* + * eAmy.Offline - Amy Editor embedded for offline use. + * http://www.april-child.com/amy + * + * Published under MIT License. + * Copyright (c) 2007-2008 Petr Krontorád, April-Child.com + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + * + * + * This file is auto-generated from original Fry Framework and Amy Editor sources.. + */ + + +// Generated from theme definition file. +$class('ac.chap.theme.EAmy < ac.chap.Theme'); + + ac.chap.theme.EAmy.prototype.initDefinition = function() + { +// $call(this, 'ac.chap.Theme.initDefinition');this.cssId = 'black'; +//this.background = '#072240'; +//this.textColor = '#DFEFFF'; +//this.caretColor = 'lime'; +//this.caretRowStyleActive = '#041629'; +//this.selectionStyle = '#86553b'; +//this.colorScheme[ac.chap.TOKEN_MULTIROW_COMMENT] = 'color:#0084FF;font-style:italic'; +//this.colorScheme[ac.chap.TOKEN_SINGLEROW_COMMENT] = 'color:#0084FF;font-style:italic'; +//this.colorScheme[ac.chap.TOKEN_SINGLE_QUOTED] = 'color:#00DF00'; +//this.colorScheme[ac.chap.TOKEN_DOUBLE_QUOTED] = 'color:#00DF00'; +//this.colorScheme[ac.chap.CHUNK_KEYWORD] = 'color:#FF9D00'; +//this.colorScheme[ac.chap.CHUNK_NUMBER] = 'color:#FF5B8C'; +//this.colorScheme[ac.chap.CHUNK_OPERATOR] = 'color:#FF9D00;'; +//this.colorScheme[ac.chap.CHUNK_PARENTHESIS] = 'color:#FFF177'; +//this.colorScheme[ac.chap.CHUNK_KEYWORD_CUSTOM] = 'color:#54FFB8'; +//this.colorScheme[ac.chap.CHUNK_FUNCTION_NAME] = 'color:#FFE000'; +//this.colorScheme[ac.chap.CHUNK_LIBRARY] = 'color:#71E5B6'; +//this.colorScheme[ac.chap.CHUNK_LIBRARY_CUSTOM] = 'color:#FF78E5'; +// + $call(this, 'ac.chap.Theme.initDefinition'); +this.cssId = 'twilight'; +this.background = '#141414'; +this.textColor = '#F8F8F8'; +this.caretColor = '#A7A7A7'; +this.caretRowStyleActive = '#1B1B1B'; +this.selectionStyle = '#3C4043'; +this.colorScheme[ac.chap.TOKEN_MULTIROW_COMMENT] = 'color:#605A60;font-style:italic'; +this.colorScheme[ac.chap.TOKEN_SINGLEROW_COMMENT] = 'color:#605A60;font-style:italic'; +this.colorScheme[ac.chap.TOKEN_SINGLE_QUOTED] = 'color:#8B9F67'; +this.colorScheme[ac.chap.TOKEN_DOUBLE_QUOTED] = 'color:#D4F29E'; +this.colorScheme[ac.chap.CHUNK_KEYWORD] = 'color:#D2A964'; +this.colorScheme[ac.chap.CHUNK_NUMBER] = 'color:#DE6848'; +this.colorScheme[ac.chap.CHUNK_OPERATOR] = 'color:#EFC25A;'; +this.colorScheme[ac.chap.CHUNK_PARENTHESIS] = 'color:#ABC4DC'; +this.colorScheme[ac.chap.CHUNK_KEYWORD_CUSTOM] = 'color:#A0849E'; +this.colorScheme[ac.chap.CHUNK_FUNCTION_NAME] = 'color:#DAD280'; +this.colorScheme[ac.chap.CHUNK_LIBRARY] = 'color:#7286A8'; +this.colorScheme[ac.chap.CHUNK_LIBRARY_CUSTOM] = 'color:#A55C29'; +} + + + +// Generated from bundle keymap definition file. +ac.chap.KeyMap.prototype.initDefinition = function() + { + var _ = '\n'; + this.compile + (""+_+ "KEY: 0" ++_+ " insert(character:true)" ++_+ "KEY: -37" ++_+ " caret(move:'left')" ++_+ "KEY: -37+shift" ++_+ " caret(move:'left')" ++_+ " selection(add:true)" ++_+ "KEY: -37+ctrl" ++_+ " caret(move:'prev_regexp', re:'[^|._A-Z ,|(|);]*$')" ++_+ "KEY: -37+alt" ++_+ " caret(move:'prev_word')" ++_+ "KEY: -37+ctrl+shift" ++_+ " caret(move:'prev_regexp', re:'[^|._A-Z ,|(|);]*$')" ++_+ " selection(add:true)" ++_+ "KEY: -37+alt+shift" ++_+ " caret(move:'prev_word')" ++_+ " selection(add:true)" ++_+ "KEY: -37+meta" ++_+ " caret(move:'row_start')" ++_+ "KEY: -37+meta+shift" ++_+ " caret(move:'row_start')" ++_+ " selection(add:true)" ++_+ "KEY: -39" ++_+ " caret(move:'right')" ++_+ "KEY: -39+shift" ++_+ " caret(move:'right')" ++_+ " selection(add:true)" ++_+ "KEY: -39+ctrl" ++_+ " caret(move:'next_regexp', re:'^[^|._A-Z ,|(|);]*')" ++_+ "KEY: -39+alt" ++_+ " caret(move:'next_word')" ++_+ "KEY: -39+ctrl+shift" ++_+ " caret(move:'next_regexp', re:'^[^|._A-Z ,|(|);]*')" ++_+ " selection(add:true)" ++_+ "KEY: -39+alt+shift" ++_+ " caret(move:'next_word')" ++_+ " selection(add:true)" ++_+ "KEY: -39+meta" ++_+ " caret(move:'row_end')" ++_+ "KEY: -39+meta+shift" ++_+ " caret(move:'row_end')" ++_+ " selection(add:true)" ++_+ "KEY: -38" ++_+ " caret(move:'up')" ++_+ "KEY: -38+shift" ++_+ " caret(move:'up')" ++_+ " selection(add:true)" ++_+ "KEY: -40" ++_+ " caret(move:'down')" ++_+ "KEY: -40+shift" ++_+ " caret(move:'down')" ++_+ " selection(add:true)" ++_+ "KEY: -13" ++_+ " insert(row:true)" ++_+ "KEY: -8" ++_+ " delete(character:true)" ++_+ "KEY: -46" ++_+ " delete(character:false)" ++_+ "KEY: 75+ctrl+shift" ++_+ " delete(row:true)" ++_+ "KEY: -27" ++_+ " custom(action:'WordComplete', direction:true)" ++_+ "KEY: -27+shift" ++_+ " custom(action:'WordComplete', direction:false)" ++_+ "KEY: -9" ++_+ " custom(action:'SnippetComplete')" ++_+ "KEY: 123" ++_+ " custom(action:'AutoComplete', use_selection:true, text:'}')" ++_+ "KEY: 34" ++_+ " custom(action:'AutoComplete', use_selection:true, text:'\"')" ++_+ "KEY: 91" ++_+ " custom(action:'AutoComplete', use_selection:true, text:']')" ++_+ "KEY: 40" ++_+ " custom(action:'AutoComplete', use_selection:true, text:')')" ++_+ "KEY: -36" ++_+ " caret(move:'doc_start')" ++_+ "KEY: -36+shift" ++_+ " caret(move:'doc_start')" ++_+ " selection(add:true)" ++_+ "KEY: -35" ++_+ " caret(move:'doc_end')" ++_+ "KEY: -35+shift" ++_+ " caret(move:'doc_end')" ++_+ " selection(add:true)" ++_+ "KEY: -34+meta" ++_+ " caret(move:'page_down')" ++_+ "KEY: -34+meta+shift" ++_+ " caret(move:'page_down')" ++_+ " selection(add:true)" ++_+ "KEY: -33+meta" ++_+ " caret(move:'page_up')" ++_+ "KEY: -33+meta+shift" ++_+ " caret(move:'page_down')" ++_+ " selection(add:true)" ++_+ "KEY: 99+meta" ++_+ " clipboard(copy:true)" ++_+ "KEY: 120+meta" ++_+ " clipboard(cut:true)" ++_+ "KEY: 122+meta" ++_+ " undo()" ++_+ "KEY: 90+meta+shift" ++_+ " redo()" ++_+ "KEY: 97+meta" ++_+ " selection(all:true)" ++_+ "KEY: 97+ctrl" ++_+ " selection(all:true)" ++_+ "KEY: -113" ++_+ " custom(action:'GoToBookmark', direction:1)" ++_+ "KEY: -113+shift" ++_+ " custom(action:'GoToBookmark', direction:-1)" ++_+ "KEY: -113+meta" ++_+ " custom(action:'ToggleBookmark')" ++_+ "KEY: 91+meta" ++_+ " custom(action:'Indent', direction:'left')" ++_+ "KEY: 93+meta" ++_+ " custom(action:'Indent', direction:'right')" ++_+ "KEY: 47+meta" ++_+ " custom(action:'Comment')" ++_+ "KEY: 43+meta" ++_+ " custom(action:'RuntimeOption', key:'font.size', value:'bigger')" ++_+ "KEY: 45+meta" ++_+ " custom(action:'RuntimeOption', key:'font.size', value:'smaller')" ++_+ "KEY: 101+meta" ++_+ " custom(action:'SetSearchKeyword')" ++_+ "KEY: 103+meta" ++_+ " custom(action:'SearchKeyword', direction:'down')" ++_+ "KEY: 71+shift+meta" ++_+ " custom(action:'SearchKeyword', direction:'up')" ++_+ "KEY: 102+ctrl" ++_+ " custom(action:'SearchInteractive')" ++_+ "KEY: 83+ctrl+shift" ++_+ " custom(action:'SearchInteractive')" ++_+ "KEY: 102+meta" ++_+ " custom(action:'SearchInteractive')" ++_+ "KEY: -13" ++_+ " custom(action:'SmartIndent', split_line:true, indent_tab_when_starts:'class module def if else unless rescue ensure while do __class__')" ++_+ "KEY: -13+meta" ++_+ " custom(action:'SmartIndent', split_line:false, indent_tab_when_starts:'class module def if else unless rescue ensure while do __class__')" ++_+ "KEY: 39" ++_+ " custom(action:'AutoComplete', use_selection:true, text:'\\'')" +)}; + +$class('ac.chap.lang.EAmy < ac.chap.Language'); + + ac.chap.lang.EAmy.prototype.initDefinition = function() + { + $call(this, 'ac.chap.Language.initDefinition'); +this.singleQuoteStringMarker = "'"; +this.singleQuoteStringMarkerException = "\\"; +this.doubleQuoteStringMarker = "\""; +this.doubleQuoteStringMarkerException = "\\"; +this.wordDelimiter = /[\w\d]/; +this.indentIgnoreMarker = /[\t \s]/; +this.foldingStartMarkers = [/^\s*def|class/i]; +this.foldingParityMarkers = [/do|(^\s*if)|(^\s*def)|(^\s*class)/i]; +this.foldingStopMarkers = [/^\s{0,1}$/i]; +this.singleRowCommentStartMarkers = ['#']; +this.multiRowCommentStartMarker = "\"\"\""; +this.multiRowCommentEndMarker = "\"\"\""; +this.stringInterpolation = ['(#\{[^\}]*\})', 1]; +this.chunkRules.push([/(([^\w]|^)(\d{1,}[\d\.Ee]*)([^w]|$))/i, 3, ac.chap.CHUNK_NUMBER]) +this.chunkRules.push([/(\+|\-|\*|\/|\=|\!|\^|\%|\||\&|\<|\>)/i, 0, ac.chap.CHUNK_OPERATOR]) +this.chunkRules.push([/(\(|\)|\[|\]|\{|\})/i, 0, ac.chap.CHUNK_PARENTHESIS]) +this.chunkRules.push([/(([^\w]|^)(elif|else|except|finally|for|if|try|while|with)([^\w]|$))/i, 3, ac.chap.CHUNK_KEYWORD]) +this.chunkRules.push([/(([^\w]|^)(@[\w]*|break|continue|pass|raise|return|yield|and|in|is|not|or|as|assert|del|exec|print)([^\w]|$))/i, 3, ac.chap.CHUNK_KEYWORD_CUSTOM]) +this.chunkRules.push([/((def[ ]{1,})([\w]{1,}))/i, 3, ac.chap.CHUNK_FUNCTION_NAME]) +this.chunkRules.push([/(([^\w]|^)(__import__|all|abs|any|apply|callable|chr|cmp|coerce|compile|delattr|dir|divmod|eval|execfile|filter|getattr|globals|hasattr|hash|hex|id|input|intern|isinstance|issubclass|iter|len|locals|map|max|min|oct|ord|pow|range|raw_input|reduce|reload|repr|round|setattr|sorted|sum|unichr|vars|zip|basestring|bool|buffer|classmethod|complex|dict|enumerate|file|float|frozenset|int|list|long|object|open|property|reversed|set|slice|staticmethod|str|super|tuple|type|unicode|xrange)([^\w]|$))/i, 3, ac.chap.CHUNK_LIBRARY]) +this.chunkRules.push([/(([^\w]|^)((__(all|bases|class|debug|dict|doc|file|members|metaclass|methods|name|slots|weakref)__)|(import|from| abs|add|and|call|cmp|coerce|complex|contains|del|delattr|delete|delitem|delslice|div|divmod|enter|eq|exit|float|floordiv|ge|get|getattr|getattribute|getitem|getslice|gt|hash|hex|iadd|iand|idiv|ifloordiv|ilshift|imod|imul|init|int|invert|ior|ipow|irshift|isub|iter|itruediv|ixor|le|len|long|lshift|lt|mod|mul|ne|neg|new|nonzero|oct|or|pos|pow|radd|rand|rdiv|rdivmod|repr|rfloordiv|rlshift|rmod|rmul|ror|rpow|rrshift|rshift|rsub|rtruediv|rxor|set|setattr|setitem|setslice|str|sub|truediv|unicode|xor))([^\w]|$))/i, 3, ac.chap.CHUNK_LIBRARY_CUSTOM]) +} +var snippet = {}; +snippet = {tab_activation: '', code: ''}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'ifmain', code: 'if __name__ == '+"'"+'__main__'+"'"+':\n ${1:main()}$0'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'try', code: 'try:\n ${1:pass}\nexcept ${2:Exception}, ${3:e}:\n ${4:raise e}\nelse:\n ${5:pass}'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'property', code: 'def ${1:foo}():\n doc = "${2:The $1 property.}"\n def fget(self):\n ${3:return self._$1}\n def fset(self, value):\n ${4:self._$1 = value}\n def fdel(self):\n ${5:del self._$1}\n return locals()\n$1 = property(**$1())$0'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: '', code: ''}; +eamy.snippets.push(snippet); +snippet = {tab_activation: '__', code: '__${1:init}__'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: '.', code: 'self.'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: '', code: ''}; +eamy.snippets.push(snippet); +//snippet = {tab_activation: 'def', code: 'def ${1:fname}(${2:`if [ "$TM_CURRENT_LINE" != "" ]\n # poor man'+"'"+'s way ... check if there is an indent or not\n # (cuz we would have lost the class scope by this point)\n then\n echo "self"\n fi`}):\n ${3/.+/"""/}${3:docstring for $1}${3/.+/"""\n/}${3/.+/\t/}${0:pass}'}; +//eamy.snippets.push(snippet); +snippet = {tab_activation: 'def', code: 'def ${1:fname}(${2:`if [ "$TM_CURRENT_LINE" != "" ]\n # poor man'+"'"+'s way ... check if there is an indent or not\n # (cuz we would have lost the class scope by this point)\n then\n echo "self"\n fi`}):\n ${3:}\n ${0:return dict()}'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'class', code: 'class ${1:ClassName}(${2:object}):\n ${3/.+/"""/}${3:docstring for $1}${3/.+/"""\n/}${3/.+/\t/}def __init__(self${4/([^,])?(.*)/(?1:, )/}${4:arg}):\n ${5:super($1, self).__init__()}\n${4/(\A\s*,\s*\Z)|,?\s*([A-Za-z_][a-zA-Z0-9_]*)\s*(=[^,]*)?(,\s*|$)/(?2:\t\tself.$2 = $2\n)/g} $0'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'aurm', code: '@auth.requires_membership(\'$0\'):'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'dbt', code: '${1:db_name}.define_table("${2:table_name}",\n SQLField("${3:field_name}", "${4:string/text/password/blob/upload/boolean/integer/double/time/date/datetime/db.reference_table}", ${5:length=$6}, ${7:default="$8"}),$9\n)'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'dbf', code: 'SQLField("${1:field_name}", "${2:string/text/password/blob/upload/boolean/integer/double/time/date/datetime/db.reference_table}", ${3:length=$4}, ${5:default="$6"}),$7'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'dbi', code: '${1:db_name}.${2:table_name}.insert(\n ${3:field_name}="$4" $5\n)'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 't', code: 'T("$0")'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'rev', code: 'response.view=\'$0\''}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'ref', code: 'response.flash=\'$0\''}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 're', code: 'redirect(\'$0\')'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'rej', code: 'response.json=\'$0\''}; +eamy.snippets.push(snippet); ADDED applications/admin/static/eamy/chap-bg-sidebar.gif Index: applications/admin/static/eamy/chap-bg-sidebar.gif ================================================================== --- applications/admin/static/eamy/chap-bg-sidebar.gif +++ applications/admin/static/eamy/chap-bg-sidebar.gif cannot compute difference between binary files ADDED applications/admin/static/eamy/chap-bookmark-default.gif Index: applications/admin/static/eamy/chap-bookmark-default.gif ================================================================== --- applications/admin/static/eamy/chap-bookmark-default.gif +++ applications/admin/static/eamy/chap-bookmark-default.gif cannot compute difference between binary files ADDED applications/admin/static/eamy/chap-folding-expand-inner.gif Index: applications/admin/static/eamy/chap-folding-expand-inner.gif ================================================================== --- applications/admin/static/eamy/chap-folding-expand-inner.gif +++ applications/admin/static/eamy/chap-folding-expand-inner.gif cannot compute difference between binary files ADDED applications/admin/static/eamy/chap-folding-expand.gif Index: applications/admin/static/eamy/chap-folding-expand.gif ================================================================== --- applications/admin/static/eamy/chap-folding-expand.gif +++ applications/admin/static/eamy/chap-folding-expand.gif cannot compute difference between binary files ADDED applications/admin/static/eamy/chap-folding-start.gif Index: applications/admin/static/eamy/chap-folding-start.gif ================================================================== --- applications/admin/static/eamy/chap-folding-start.gif +++ applications/admin/static/eamy/chap-folding-start.gif cannot compute difference between binary files ADDED applications/admin/static/eamy/chap-folding-stop.gif Index: applications/admin/static/eamy/chap-folding-stop.gif ================================================================== --- applications/admin/static/eamy/chap-folding-stop.gif +++ applications/admin/static/eamy/chap-folding-stop.gif cannot compute difference between binary files ADDED applications/admin/static/eamy/chap-wrapped-row.gif Index: applications/admin/static/eamy/chap-wrapped-row.gif ================================================================== --- applications/admin/static/eamy/chap-wrapped-row.gif +++ applications/admin/static/eamy/chap-wrapped-row.gif cannot compute difference between binary files ADDED applications/admin/static/eamy/eamy.js Index: applications/admin/static/eamy/eamy.js ================================================================== --- applications/admin/static/eamy/eamy.js +++ applications/admin/static/eamy/eamy.js @@ -0,0 +1,8143 @@ +/* + * eAmy.Offline - Amy Editor embedded for offline use. + * http://www.april-child.com/amy + * + * Published under MIT License. + * Copyright (c) 2007-2008 Petr Krontorád, April-Child.com + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + * + * + * This file is auto-generated from original Fry Framework and Amy Editor sources.. + */ + + + + + +/* + * AC Fry - JavaScript Framework v1.0 + * (c)2006 Petr Krontorad, April-Child.com + * Portions of code based on WHOA Bender Framework, (c)2002-2005 Petr Krontorad, WHOA Group. + * http://www.april-child.com. All rights reserved. + * See the license/license.txt for additional details regarding the license. + * Special thanks to Matt Groening and David X. Cohen for all the robots. + */ + +/* Reserving global `fry` object */ +var fry = +{ + version:1.0, + __production_mode:false +}; + +// String prototype enhancements +String.prototype.camelize = function() +{ + return this.replace( /([-_].)/g, function(){ return arguments[0].substr(1).toUpperCase();} ); +} + +String.prototype.decamelize = function() +{ + return this.replace( /([A-Z])/g, function(){ return '-'+arguments[0].toLowerCase();} ); +} + +String.prototype.trim = function() +{ + return this.replace(/(^\s*)|(\s*$)/g, '' ); +} + +String.prototype.stripLines = function() +{ + return this.replace( /\n/g, '' ); +} + +String.prototype.stripMarkup = function() +{ + return this.replace( /<(.|\n)+?>/g, '' ); +} + +String.prototype.replaceMarkup = function( charRep ) +{ + return this.replace( /(<(.|\n)+?>)/g, function() + { + var t = ''; + for ( var i=0; i/g, '>' ).replace( /' ).replace( /&/g, '&' ); +} + +String.prototype.surround = function(t, side) +{ + side = side || 3; + return (1==1&side?t:'')+this+(2==2&side?t:''); +} + +String.prototype.surroundTag = function(t) +{ + return '<'+t+'>'+this+''; +} + +// example of use: var pattern = '? is ?; alert(pattern.embed('Decin', 'sunny')); pattern = '@city is @weather'; alert(pattern.embed({weather:'cloudy', city:'Decin'})) +String.prototype.embed = function() +{ + var t = this; + if ( 1 == arguments.length && 'object' == typeof arguments[0] ) + { + // named placeholders + for ( var i in arguments[0] ) + { + eval('var re=/@'+i+'/g;'); + t = t.replace(re, arguments[0][i]); + } + } + else + { + // anonymous placeholders `?` + for ( var i=0; i opacity ) + { + opacity = 0; + } + if ( 1 < opacity ) + { + opacity = 1; + } + if ( $__tune.isIE ) + { + node.style.filter = 'alpha(opacity='+(100*opacity)+')'; + } + else + { + node.style.opacity = opacity; + node.style.MozOpacity = opacity; + } + }, + getPageScrollPosition:function() + { + var d = document.documentElement; + if ( d && d.scrollTop) + { + return [d.scrollLeft, d.scrollTop]; + } + else if (document.body) + { + return [document.body.scrollLeft, document.body.scrollTop]; + } + else + { + return [0, 0]; + } + } + }, + event: + { + get:function(evt, type) + { + if ( $notset(evt.target) ) + { + evt.target = evt.srcElement; + evt.stopPropagation = function() + { + window.event.cancelBubble = true; + }; + } + evt.stop = function() + { + evt.stopPropagation(); + evt.stopped = true; + } + evt.$ = $(evt.target); + if ( $notset(evt.pageX) ) + { + evt.pageX = evt.clientX + document.body.scrollLeft; + evt.pageY = evt.clientY + document.body.scrollTop; + } + evt.getOffsetX = function() + { + if ( $notset(evt.offsetX) ) + { + var pos = evt.$.abspos(); + evt.offsetX = evt.pageX - pos.x; + evt.offsetY = evt.pageY - pos.y; + } + return evt.offsetX; + } + evt.getOffsetY = function() + { + if ( $notset(evt.offsetY) ) + { + var pos = evt.$.abspos(); + evt.offsetX = evt.pageX - pos.x; + evt.offsetY = evt.pageY - pos.y; + } + return evt.offsetY; + } + evt.isAnyControlKeyPressed = function() + { + return evt.metaKey||evt.ctrlKey||evt.altKey||evt.shiftKey; + } + evt.KEY_ESCAPE = 27; + evt.KEY_ENTER = 13; + evt.KEY_ARR_RIGHT = 39; + evt.KEY_ARR_LEFT = 37; + evt.KEY_ARR_UP = 38; + evt.KEY_ARR_DOWN = 40; + return evt; + }, + addListener:function(node, type, listener) + { + if ( $__tune.isIE && node.attachEvent ) + { + node.attachEvent('on'+type, listener); + } + else + { + node.addEventListener(type, listener, false); + } + }, + removeListener:function(node, type, listener) + { + if ( node.detachEvent ) + { + node.detachEvent('on'+type, listener); + node['on'+type] = null; + } + else if ( node.removeEventListener ) + { + node.removeEventListener(type, listener, false); + } + } + }, + behavior: + { + disablePageScroll:function() + { + if ( $notset($__tune.__prop.page_scroll) ) + { + $__tune.__prop.page_scroll = [$().s().overflow, $().ga('scroll')]; + } + $().s('overflow:hidden').sa('scroll', 'no'); + }, + enablePageScroll:function() + { + $().s('overflow:auto').sa('scroll', 'yes'); + }, + disableCombos:function() + { + $().g('select', function(node) + { + node.sa('__dis_combo', node.s().visibility); + $(node).v(false); + }); + }, + enableCombos:function() + { + $().g('select', function(node) + { + node.s({visibility:node.ga('__dis_combo') || 'visible'}); + }); + }, + clearSelection:function() + { + try + { + if ( window.getSelection ) + { + if ( $__tune.isSafari ) + { + window.getSelection().collapse(); + } + else + { + window.getSelection().removeAllRanges(); + } + } + else + { + if ( document.selection ) + { + if ( document.selection.empty ) + { + document.selection.empty(); + } + else + { + if ( document.selection.clear ) + { + document.selection.clear(); + } + } + } + } + } + catch (e) {} + }, + makeBodyUnscrollable:function() + { + $().s('position:fixed').w(fry.ui.info.page.width); + } + }, + ui: + { + scrollbarWidth:-1!=navigator.appVersion.indexOf('intosh')?15:17 + }, + selection: + { + setRange:function(el, selectionStart, selectionEnd) + { + if (el.setSelectionRange) + { + el.focus(); + el.setSelectionRange(selectionStart, selectionEnd); + } + else if (el.createTextRange) + { + var range = el.createTextRange(); + range.collapse(true); + range.moveEnd('character', selectionEnd); + range.moveStart('character', selectionStart); + range.select(); + } + } + } +} +// some browsers masks its presence having Gecko string somewhere inside its userAgent field... +$__tune.isGecko = !$__tune.isSafari&&!$__tune.isIE&&-1!=navigator.userAgent.indexOf('ecko'); +$__tune.isSafari2 = $__tune.isSafari && -1 != navigator.appVersion.indexOf('Kit/4'); +$__tune.isSafari3 = $__tune.isSafari && -1 != navigator.appVersion.indexOf('Kit/5'); + + +// Node manipulations + +function ACNode(node) +{ + this.$ = node; + if ( node ) + { + node.setAttribute('fryis', '1'); + } +} +// `$$` creates new node +ACNode.prototype.$$ = function(tagName) +{ + return $$(tagName); +} +// *is* - tells whether node is a part of the active DOM tree (that is displayed on page). Node may exist only in memory before appending or after removing when references, in which case node.is() will return false. +ACNode.prototype.is = function() +{ + return this.$ && null != this.$.parentNode; +} +// *i*d +ACNode.prototype.i = function(id) +{ + if ( 'undefined' == typeof id ) + { + return this.$.id||''; + } + this.$.id = id; + return this; +} +// class *n*ame +ACNode.prototype.n = function(n) +{ + if ( 'undefined' == typeof n) + { + return this.$.className||''; + } + this.$.className = n; + return this; +} +// *e*vent listener, if called with one argument only, previously registered listeners are removed +ACNode.prototype.e = function(t, c, oneUseOnly) +{ + var ser_type_id = 'fryse-'+t; + if ( !c ) + { + if ( null != this.$.getAttribute(ser_type_id) ) + { + var ser_listeners = this.$.getAttribute(ser_type_id).split(','); + // console.log('*E* removing listeners for %s, listeners: %s', t, ser_listeners); + for ( var i=0; i h ) + { + this.$.style.fontSize = '1px'; + } + return this; +} +// *s*tyle information - argument can be either "{color:'red', backgroundColor:'blue'}" or "'color:red;background-color:blue'" +ACNode.prototype.s = function(s) +{ + if ( 'undefined' == typeof s ) + { + return this.$.style; + } + if ( 'object' == typeof s ) + { + for ( var n in s ) + { + this.$.style[n] = s[n]; + } + } + else if ( 'string' == typeof s ) + { + if ( '' != s ) + { + var styles = s.split(';'); + for ( var i=0; idiv>table>tbody>tr>td>` and node at `td` +// to return second div you would call `gp('tr/tbody/table/div')` or `tr/table/div:1`, first div could be acquired using `table/div:2` etc. you can use `*` for any node. +ACNode.prototype.gp = function(q) +{ + if ( 'string' == typeof q ) + { + q = q.split('/'); + } + var fq = []; + for ( var i=0; i to && to>=from ) + { + self.clearInterval(t); + } + else + { + c(i, control); + if ( control.stopped ) + { + self.clearInterval(t); + } + } + i++; + }, interval); +} +// $dotimes +// ======== +// Repeats embedded code n times. +/* Usage: + $dotimes(20, function(i) + { + // your code, i is the counter parameter + }) +*/ +var $dotimes = function(n, c) +{ + for ( var i=0; i i ) + { + control.skip(); + return; + } + tr.n(0==i%2 ? 'even' : 'odd'); + if ( 20 < i ) + { + control.stop(); + } + }) +*/ +var $foreach = function(o, c) +{ + if ( !o ) + { + c = null; + return; + } + if ( 'undefined' == typeof o.length && 'function' != typeof o.__length ) + { + c = null; + return; + } + var n = 'function' == typeof o.__length ? o.__length() : o.length; + var control = + { + stopped:false, + stop:function() + { + this.stopped = true; + }, + skipped:false, + skip:function() + { + this.skipped = true; + }, + removed:false, + remove:function(stopAfterwards) + { + this.removed = true; + this.stopped = true == stopAfterwards; + } + } + // cannot just extend Array.prototype for `item()` method due bug in IE6 iteration mechanism. Some day (>2010 :) this might get fixed and will become obsolete + for ( var i=0; i= evt.keyCode) + { + fry.keyboard.pushKey(evt.keyCode + 32, fry.keyboard.META_KEY); + evt.preventDefault(); + return true; + } + } + } + fry.keyboard.initialized = true; + fry.keyboard.start(); +} + +fry.keyboard.start = function() +{ + fry.keyboard.stopped = false; +} + +fry.keyboard.stop = function() +{ + fry.keyboard.stopped = true; +} + +fry.keyboard.disableTextfieldsEditation = function() +{ + fry.keyboard.allowTextfieldsEditation(true); +} + +fry.keyboard.allowTextfieldsEditation = function(disable) +{ + var lst = document.getElementsByTagName('input'); + var n = lst.length; + for (var i=0; i evt.keyCode)) + { + // control code + mask++; + } + if (!fry.keyboard.pushKey(code, mask)) + { + return true; + } + } + evt.preventDefault(); + evt.stopPropagation(); + return false; +} + +fry.keyboard.paste.ff_mac = function(evt) +{ + fry.keyboard.last_down_evt = null; + // catching Command+C, Command+X, it's a FF.mac hack + if (evt.metaKey && ((67 == evt.keyCode && 0 == evt.charCode && 67 == evt.which) || (88 == evt.keyCode && 0 == evt.charCode && 88 == evt.which))) + { + return fry.keyboard.shared.copy(evt); + } + else + { + return 86 == evt.keyCode && 0 == evt.charCode && 86 == evt.which && evt.metaKey; + } +} + +fry.keyboard.down.ff_mac = function(evt) +{ + return false; +} + +fry.keyboard.press.ff_mac = function(evt) +{ + if (null != fry.keyboard.last_down_evt) + { + return; + } + var mask = (evt.altKey ? 2 : 0) + (evt.ctrlKey ? 4 : 0) + (evt.shiftKey ? 8 : 0) + (evt.metaKey ? 16 : 0); + if (!evt.charCode || (evt.charCode == evt.keyCode)) + { + // control code + fry.keyboard.pushKey(evt.keyCode, 1 + mask); + } + else + { + if (!fry.keyboard.pushKey(evt.charCode, mask)) + { + return true; + } + } + evt.preventDefault(); + evt.stopPropagation(); + return false; +} + +fry.keyboard.paste.webkit = function(evt) +{ + if ($__tune.isMac) + { + return (86 == evt.keyCode && (0 == evt.charCode || 118 == evt.charCode) && evt.metaKey); + } + else + { + return (86 == evt.keyCode && (0 == evt.charCode || 118 == evt.charCode) && evt.ctrlKey); + } +} + +fry.keyboard.down.webkit = function(evt) +{ + if (0 != evt.keyCode && (48 > evt.keyCode || (111 < evt.keyCode && 128 > evt.keyCode) || 60000 < evt.charCode)) + { + var mask = (evt.altKey ? 2 : 0) + (evt.ctrlKey ? 4 : 0) + (evt.shiftKey ? 8 : 0) + (evt.metaKey ? 16 : 0); + if (!evt.charCode || 111 < evt.keyCode || 32 > evt.charCode || 60000 < evt.charCode) + { + // control code + fry.keyboard.pushKey(evt.keyCode, 1 + mask); + } + else + { + if (!fry.keyboard.pushKey(evt.charCode, mask)) + { + return true; + } + } + evt.preventDefault(); + evt.stopPropagation(); + fry.keyboard.last_down_evt = null; + return false; + } + fry.keyboard.last_down_evt = evt; + return true; +} + +fry.keyboard.press.webkit = function(evt) +{ + if (null != fry.keyboard.last_down_evt) + { + var mask = (evt.altKey ? 2 : 0) + (evt.ctrlKey ? 4 : 0) + (evt.shiftKey ? 8 : 0) + (evt.metaKey ? 16 : 0); + var code = !evt.keyCode ? evt.charCode : evt.keyCode; + if (evt.keyCode == evt.charCode && evt.keyCode == fry.keyboard.last_down_evt.charCode && evt.keyCode > 60000) + { + code = fry.keyboard.last_down_evt.keyCode; + } + if (evt.keyCode == fry.keyboard.last_down_evt.keyCode && 48 > evt.keyCode) + { + // control code + mask++; + } + else + { + var r_mask = fry.keyboard.SHIFT_KEY + fry.keyboard.META_KEY; + if (r_mask == (mask & r_mask) && 97 <= code && 122 >= code) + { + code -= 32; + } + } + if (!fry.keyboard.pushKey(code, mask)) + { + return true; + } + } + evt.preventDefault(); + evt.stopPropagation(); + return false; +} + +fry.keyboard.paste.ie = function(evt) +{ + if (evt.ctrlKey && (67 == evt.keyCode || 88 == evt.keyCode)) + { + // ctrl+c, ctrl+x + return fry.keyboard.shared.copy(evt); + } + else + { + return false; + } +} + +fry.keyboard.down.ie = function(evt) +{ + fry.keyboard.last_down_evt = evt; + if (48 > evt.keyCode || (111 < evt.keyCode && 128 > evt.keyCode)) + { + // control code for IE + var mask = 1 + (evt.altKey ? 2 : 0) + (evt.ctrlKey ? 4 : 0) + (evt.shiftKey ? 8 : 0) + (evt.metaKey ? 16 : 0); + return !fry.keyboard.pushKey(evt.keyCode, mask) + } + else + { + var code = evt.keyCode; + // disabling some other keys (A, F, N, R, S, T) + if (82 == evt.keyCode || 65 == evt.keyCode || 83 == evt.keyCode || 70 == evt.keyCode || 78 == evt.keyCode || 84 == evt.keyCode) + { + if (!evt.shiftKey) + { + code += 32; + } + var mask = (evt.altKey ? 2 : 0) + (evt.ctrlKey ? 4 : 0) + (evt.shiftKey ? 8 : 0) + (evt.metaKey ? 16 : 0); + return !fry.keyboard.pushKey(code, mask); + } + } + return true; +} + +fry.keyboard.press.ie = function(evt) +{ + if (null != fry.keyboard.last_down_evt) + { + var mask = (evt.altKey ? 2 : 0) + (evt.ctrlKey ? 4 : 0) + (evt.shiftKey ? 8 : 0) + (evt.metaKey ? 16 : 0); + return !fry.keyboard.pushKey(evt.keyCode, mask); + } + return false; +} + + +fry.keyboard.paste.opera = function(evt) +{ + return 86 == evt.keyCode && 86 == evt.which && evt.ctrlKey; +} + +fry.keyboard.down.opera = function(evt) +{ + fry.keyboard.last_down_evt = evt; + return false; +} + +fry.keyboard.press.opera = function(evt) +{ + var e = fry.keyboard.last_down_evt; + var mask = (evt.altKey ? 2 : 0) + (evt.ctrlKey ? 4 : 0) + (evt.shiftKey ? 8 : 0) + (evt.metaKey ? 16 : 0); + var prev_mask = (e.altKey ? 2 : 0) + (e.ctrlKey ? 4 : 0) + (e.shiftKey ? 8 : 0) + (e.metaKey ? 16 : 0); + if ((evt.keyCode == fry.keyboard.last_down_evt.keyCode || 0 == e.keyCode) && (0 == evt.which || 48 > e.keyCode || 111 < e.keyCode)) + { + mask++; + } + if (!fry.keyboard.pushKey(evt.keyCode, mask)) + { + return true; + } + evt.preventDefault(); + evt.stopPropagation(); + return false; +} + +fry.keyboard.addListener = function(listener) +{ + fry.keyboard.listener = listener; +} + +fry.keyboard.removeListener = function(listener) +{ + fry.keyboard.listener = null; +} + + +fry.keyboard.pushKey = function(code, mask) +{ + if (32 == code) + { + mask = mask & 65534; + } + var was_clipboard_copy = false; + var was_clipboard_cut = false; + if ($__tune.isMac) + { + was_clipboard_copy = (99 == code) && (fry.keyboard.META_KEY == (mask & fry.keyboard.META_KEY)); + was_clipboard_cut = (120 == code) && (fry.keyboard.META_KEY == (mask & fry.keyboard.META_KEY)); + } + else + { + was_clipboard_copy = (1 == code || 99 == code) && (fry.keyboard.CTRL_KEY == (mask & fry.keyboard.CTRL_KEY)); + was_clipboard_cut = (24 == code || 120 == code) && (fry.keyboard.CTRL_KEY == (mask & fry.keyboard.CTRL_KEY)); + } + if (was_clipboard_copy || was_clipboard_cut) + { + fry.keyboard.prepareClipboard(); + fry.keyboard.clipboard.copiedContent = ''; + var was_custom_content = false; + if (fry.keyboard.listener) + { + var content = fry.keyboard.listener(0, fry.keyboard.CONTROL_CODE | fry.keyboard.SIG_CLIPBOARD_GET); + if ('string' == typeof content) + { + was_custom_content = true; + fry.keyboard.clipboard.copiedContent = content; + } + } + if (was_custom_content) + { + fry.keyboard.clipboard.node.value = fry.keyboard.clipboard.copiedContent; + fry.keyboard.clipboard.node.select(); + fry.keyboard.clipboard.node.focus(); + } + fry.keyboard.pushKey(0, fry.keyboard.CONTROL_CODE | (was_clipboard_cut ? fry.keyboard.CUT : fry.keyboard.COPY)); + fry.keyboard.clipboard.content = fry.keyboard.clipboard.copiedContent; + // returning false will enforce propagation + return !was_custom_content; + } + if (fry.keyboard.PASTE == (mask & fry.keyboard.PASTE)) + { + fry.keyboard.clipboard.pastedContent = code; + fry.keyboard.clipboard.content = fry.keyboard.clipboard.pastedContent; + code = 0; + } + // filtering out solo-keys Ctrl, Shift, Alt that are triggered by some browsers. + if ((17 == code && 5 == (mask & 5)) || (16 == code && 9 == (mask & 9)) || (18 == code && 3 == (mask & 3))) + { + return true; + } + // filtering out Command+V on FF.mac + if (118 == code && 16 == mask) + { + return true; + } + if (fry.keyboard.listener) + { + // console.info(code); + if (13 != code && 0 != (mask & (fry.keyboard.CONTROL_CODE + fry.keyboard.META_KEY + fry.keyboard.CTRL_KEY))) + { + // some keystroke occured that we really want to know about the listener result, we have to call it immediatelly + return fry.keyboard.listener(code, mask); + } + else + { + // let's ease the pain of the browser + setTimeout('fry.keyboard.listener('+code+','+mask+')', 10); + } + } + else + { + fry.keyboard.buffer.unshift([code, mask]); + } + return true; +} + +fry.keyboard.popKey = function() +{ + return fry.keyboard.buffer.pop(); +} + +fry.keyboard.getClipboardContent = function() +{ + return fry.keyboard.clipboard.content; +} + + +/*--------*/ +var client = {conf:{fry:{backendURL:''}}}; +var eamy = +{ + snippets:[], + instances:[] +}; + +/* + * ac.Chap - Text Editing Component - Core + */ + +if ( 'undefined' == typeof ac ) +{ + var ac = {chap:{}}; +} + +ac.chap = +{ + state: + { + active:null + }, + + TOKEN_MULTIROW_COMMENT:0, + TOKEN_SINGLEROW_COMMENT:1, + TOKEN_SINGLE_QUOTED:2, + TOKEN_DOUBLE_QUOTED:3, + TOKEN_NEWROW:4, + TOKEN_WHITESPACE:5, + + ROWSTATE_NONE:0, + ROWSTATE_FOLD_START:1, + ROWSTATE_FOLD_STOP:2, + ROWSTATE_FOLD_EXPAND:4, + ROWSTATE_FOLD_COLLAPSED:8, + ROWSTATE_SELECTION:16, + ROWSTATE_BOOKMARK:32, + + CHUNK_KEYWORD:4, + CHUNK_NUMBER:5, + CHUNK_OPERATOR:6, + CHUNK_PARENTHESIS:7, + CHUNK_KEYWORD_CUSTOM:8, + CHUNK_FUNCTION_NAME:9, + CHUNK_LIBRARY:10, + CHUNK_LIBRARY_CUSTOM:11, + + ACTION_CARET:1, + ACTION_SELECTION:2, + ACTION_INSERT:3, + ACTION_UPDATE:4, + ACTION_DELETE:5, + ACTION_CLIPBOARD:6, + ACTION_UNDO:7, + ACTION_REDO:8, + ACTION_CUSTOM:9, + + ACTION_RES_REDRAWCARET:1, + ACTION_RES_REDRAWTEXT:2, + ACTION_RES_SELECTIONCHANGED:4, + ACTION_RES_SCROLLTOCARET:8, + + CKEY_NONE:0, + CKEY_ALT:2, + CKEY_CTRL:4, + CKEY_SHIFT:8, + CKEY_META:16, + + TRANSLOG_TYPE_INSERT:1, + TRANSLOG_TYPE_REMOVE:2, + + ACTION_LISTENER_BEFORE:1, + ACTION_LISTENER_AFTER:2, + ACTION_LISTENER_BOTH:3 + +} + + + +ac.chap.activeComponent = null; +ac.chap.instanceId = 1; + +ac.chap.getActiveComponent = function() +{ + return ac.chap.activeComponent; +} + +ac.chap.setActiveComponent = function(component) +{ + fry.keyboard.initialize(); + if (null != ac.chap.activeComponent) + { + ac.chap.activeComponent.blur(); + } + ac.chap.activeComponent = component; + if (null != component) + { + if (ac.widget) + { + ac.widget.focus(component); + } + ac.chap.activeComponent.focus(); + } + else + { + if (!ac.widget) + { + fry.keyboard.stop(); + } + } +} + +ac.chap.route = function(type, windowId, viewIndex, pars) +{ + if ( null == ac.chap.activeComponent ) + { + return; + } + if ( 'undefined' == typeof ac.chap.activeComponent.views[viewIndex] ) + { + return; + } + switch ( type ) + { + case 'expand-folding': + { + ac.chap.activeComponent.expandFolding(pars); + } + } +} + +ac.chap.caretThread = setInterval(function() +{ + if (null != ac.chap.activeComponent) + { + ac.chap.activeComponent.showCaret(true, true); + } +}, 600); + +$(document.documentElement).e($__tune.isSafari2?'mousedown':'click', function(evt) +{ + var elem = evt.$.$; + while ( null != elem && document.documentElement != elem ) + { + if ( 'true' == elem.getAttribute('chap-view') ) + { + evt.stop(); + return; + } + elem = elem.parentNode; + } + ac.chap.setActiveComponent(null); +}); + + +ac.chap.keyboardListener = function(code, mask) +{ + if (null == ac.chap.activeComponent) + { + return; + } + return ac.chap.activeComponent.standaloneKeyboardListener(code, mask); +} +if ('undefined' == typeof ac['widget']) +{ + // chap is not a part of Fry MVC, must handle keyboardListener itself + fry.keyboard.addListener(ac.chap.keyboardListener); +} + +$class('ac.chap.Window', +{ + construct:function(options, userId) + { + this.instanceId = ac.chap.instanceId++; + this.ident = 'ac-chap-' + this.instanceId; + this.userId = userId | 0; + this.caret = null; + this.options = null; + this.state = null; + + this.views = []; + this.activeView = null; + this.viewLayoutNodes = []; + + this.char_map = []; + this.row_id_map = []; + this.syntax_map = []; + this.style_map = []; + + this.row_id_sequence = 1; + + this.language = null; + this.keymap = null; + this.snippets = []; + this.commands = []; + + this.selection = null; + this.transaction_log = []; + this.redo_log = []; + + this.setOptions(options||{}); + this.setState(); + }, + destruct:function() + { + $delete(this.state); + $delete(this.options); + $delete(this.char_map); + $delete(this.row_id_map); + $delete(this.syntax_map); + $delete(this.style_map); + this.hide(); + $delete(this.activeView); + } +}); + +ac.chap.Window.prototype.focus = function() +{ + this.showCaret(); +} + +ac.chap.Window.prototype.blur = function() +{ + this.hideCaret(); +} + +// compatibility layer with AC Fry Widget library +ac.chap.Window.prototype.onFocus = function() +{ + ac.chap.setActiveComponent(this); + this.focus(); +} + +ac.chap.Window.prototype.onBlur = function() +{ + ac.chap.setActiveComponent(null); + this.blur(); +} + +ac.chap.Window.prototype.onResize = function(width, height) +{ +} + +ac.chap.Window.prototype.onSystemClipboardCopy = function() +{ + return this.getSelection(); +} + +ac.chap.Window.prototype.onSystemClipboardCut = function() +{ + this.runAction(ac.chap.ACTION_CLIPBOARD, {cut:true}); + return this.processActionResult(true, true); +} + +ac.chap.Window.prototype.onSystemClipboardPaste = function(content) +{ + this.runAction(ac.chap.ACTION_CLIPBOARD, {paste:true, content:content}); + return this.processActionResult(true, true); +} + +ac.chap.Window.prototype.hasKeyboardListenerActive = function() +{ + return true; +} +ac.chap.Window.prototype.onCut = function(selection, callbackOk) +{ +} + +ac.chap.Window.prototype.onPaste = function(selection, wasCut) +{ +} + +ac.chap.Window.prototype.setOptions = function(options) +{ + this.options = + { + initialCaretPosition:[0,0], + tokenizerLazyLaunch:900, + syntaxHighlightingEnabled:true, + remoteBackendURL:'', + font:{ + size:11, + family: $__tune.isMac ? "Consolas, 'Bitstream Vera Sans mono', 'Courier', 'Monaco', monospaced" : "Consolas, 'Courier New', 'Courier', monospaced", + allowedSizes: [8, 9, 10, 11, 12, 13, 14, 17, 21, 24, 27, 30, 34, 38, 42] + } + }; + if ( $isset(options.initial_caret_position) ) + { + this.options.initialCaretPosition = [options.initial_caret_position[0], options.initial_caret_position[1]]; + } + if ( $isset(options.language) ) + { + this.language = $new(options.language); + } + else + { + this.language = $new(ac.chap.Language); + } + if ( $isset(options.keymap) ) + { + this.keymap = $new(options.keymap); + } + else + { + this.keymap = $new(ac.chap.KeyMap); + } + if ( $isset(options.syntaxHighlightingEnabled) ) + { + this.options.syntaxHighlightingEnabled = options.syntaxHighlightingEnabled; + } + if ( $isset(options.remoteBackendURL) ) + { + this.options.remoteBackendURL = options.remoteBackendURL; + } + else + { + if ( client && client.conf && client.conf.fry ) + { + this.options.remoteBackendURL = client.conf.fry.backendURL; + } + } + if ($isset(options.font)) + { + if ($isset(options.font['size'])) + { + this.options.font.size = options.font.size; + } + if ($isset(options.font['family'])) + { + this.options.font.family = options.font.family; + } + } +} + +ac.chap.Window.prototype.setState = function() +{ + this.state = + { + lastKeyTimePressed:0, + caretPhase:1, + lastKeyCode:0, + lastControlKey:0, + lastCaretPosition:[], + tokenizerTimer:null, + scheduledTokenizerTime:0, + transactionLogStopped:false, + actionListeners:[], + actionListenersStopped:false, + caretListener:null, + commandListener:null, + transactionListener:[null,800], + passThroughKeysListener:null + } + this.caret = + { + position:[this.options.initialCaretPosition[0], this.options.initialCaretPosition[1]], + mode:1 // 1 normal, 2 overwrite + } +} + +ac.chap.Window.prototype.addView = function(layoutNode, options, renderAfter) +{ + var view_index = this.views.length; + this.viewLayoutNodes.push(layoutNode); + this.views.push($new(ac.chap.View, this, view_index, options||{})); + this.row_id_map[view_index] = []; + if ( 0 < view_index ) + { +// console.log(view_index); + // creating duplicate + var n = this.row_id_map[0].length; + for ( var i=0; i= 0; i--) + { + if (font_size > font_sizes[i]) + { + font_size = font_sizes[i]; + break; + } + } + } + else + { + if (font_sizes[0] <= value && value <= font_sizes[font_sizes.length-1]) + { + font_size = value; + } + } + this.options.font.size = font_size; + redraw = true; + } + else if ('font.family' == key) + { + this.options.font.family = value; + redraw = true; + } + else if ('word.wrap' == key) + { + if (this.activeView) + { + this.hideCaret(); + this.activeView.options.wordWrap = value; + this.activeView.reloadOptions(); + this.showCaret(); + } + } + if (redraw) + { + this.hideCaret(); + var num_views = this.views.length; + for (var i=0; i caret_col ) + { + caret_col++; + } + else + { + if ( 'undefined' != typeof this.char_map[caret_row+1] ) + { + caret_row++; + caret_col = 0; + } + } + this.setCaretPosition(caret_row, caret_col); + return ac.chap.ACTION_RES_REDRAWCARET; + } + else if ( 'up' == direction ) + { + if ( 0 < caret_row ) + { + var move_end = this.char_map[caret_row].length == caret_col; + if ( move_end ) + { + caret_col = this.char_map[caret_row-1].length; + } + else + { + caret_col = Math.min(this.char_map[caret_row-1].length, caret_col); + } + this.setCaretPosition(caret_row-1, caret_col); + return ac.chap.ACTION_RES_REDRAWCARET; + } + } + else if ( 'down' == direction ) + { + if ( 'undefined' != typeof this.char_map[caret_row+1] ) + { + var move_end = this.char_map[caret_row].length == caret_col; + if ( move_end ) + { + caret_col = this.char_map[caret_row+1].length; + } + else + { + caret_col = Math.min(this.char_map[caret_row+1].length, caret_col); + } + this.setCaretPosition(caret_row+1, caret_col); + return ac.chap.ACTION_RES_REDRAWCARET; + } + } + else if ( 'prev_word' == direction ) + { + if ( 0 < caret_col ) + { + var ch = this.char_map[caret_row].charAt(caret_col-1); + var re = this.language.wordDelimiter; + var look_for_wch = re.test(ch); + while ( 0 != caret_col ) + { + ch = this.char_map[caret_row].charAt(caret_col-1); + if ( look_for_wch != re.test(ch) ) + { + break; + } + caret_col--; + } + } + else + { + if ( 0 < caret_row ) + { + caret_row--; + caret_col = this.char_map[caret_row].length; + } + } + this.setCaretPosition(caret_row, caret_col); + return ac.chap.ACTION_RES_REDRAWCARET; + } + else if ( 'next_word' == direction ) + { + if ( this.char_map[caret_row].length > caret_col ) + { + var ch = this.char_map[caret_row].charAt(caret_col); + var re = this.language.wordDelimiter; + var look_for_wch = re.test(ch); + while ( this.char_map[caret_row].length > caret_col ) + { + ch = this.char_map[caret_row].charAt(caret_col); + if ( look_for_wch != re.test(ch) ) + { + break; + } + caret_col++; + } + } + else + { + if ( 'undefined' != typeof this.char_map[caret_row+1] ) + { + caret_row++; + caret_col = 0; + } + } + this.setCaretPosition(caret_row, caret_col); + return ac.chap.ACTION_RES_REDRAWCARET; + } + else if ( 'prev_regexp' == direction ) + { + if ( 0 < caret_col ) + { + var row = this.char_map[caret_row].substring(0, caret_col); + var re = new RegExp(params['re'].replace('|', '\\')); + var matches = re.exec(row); + if (0 == matches.length) + { + console.warning('Invalid RE definition for `prev_regexp\' direction in ACTION_CARET.move action in keymap.'); + caret_col--; + } + else + { + caret_col -= matches[0].length + 1; + } + } + else + { + if ( 0 < caret_row ) + { + caret_row--; + caret_col = this.char_map[caret_row].length; + } + } + this.setCaretPosition(caret_row, caret_col); + return ac.chap.ACTION_RES_REDRAWCARET; + } + else if ( 'next_regexp' == direction ) + { + if ( this.char_map[caret_row].length > caret_col ) + { + var row = this.char_map[caret_row].substr(caret_col + 1); + var re = new RegExp(params['re'].replace('|', '\\')); + var matches = re.exec(row); + if (0 == matches.length) + { + console.warning('Invalid RE definition for `next_regexp\' direction in ACTION_CARET.move action in keymap.'); + caret_col++; + } + else + { + caret_col += matches[0].length + 1; + } + } + else + { + if ( 'undefined' != typeof this.char_map[caret_row+1] ) + { + caret_row++; + caret_col = 0; + } + } + this.setCaretPosition(caret_row, caret_col); + return ac.chap.ACTION_RES_REDRAWCARET; + } + else if ( 'row_start' == direction ) + { + if ( 0 < caret_col ) + { + caret_col = 0; + this.setCaretPosition(caret_row, caret_col); + return ac.chap.ACTION_RES_REDRAWCARET; + } + } + else if ( 'row_end' == direction ) + { + if ( this.char_map[caret_row].length > caret_col ) + { + caret_col = this.char_map[caret_row].length; + this.setCaretPosition(caret_row, caret_col); + return ac.chap.ACTION_RES_REDRAWCARET; + } + } + else if ( 'page_up' == direction ) + { + if (this.activeView) + { + var row = caret_row - this.activeView.numRows; + if (0 > row) + { + row = 0; + } + this.setCaretPosition(row, 0 != caret_col ? this.char_map[row].length : 0); + return ac.chap.ACTION_RES_REDRAWCARET; + } + return 0; + } + else if ( 'page_down' == direction ) + { + if (this.activeView) + { + var row = caret_row + this.activeView.numRows; + if (this.char_map.length <= row) + { + row = this.char_map.length-1; + } + this.setCaretPosition(row, 0 != caret_col ? this.char_map[row].length : 0); + return ac.chap.ACTION_RES_REDRAWCARET; + } + return 0; + } + else if ( 'doc_start' == direction ) + { + this.setCaretPosition(0,0); + return ac.chap.ACTION_RES_REDRAWCARET; + } + else if ( 'doc_end' == direction ) + { + var last_index = this.char_map.length-1 + this.setCaretPosition(last_index, this.char_map[last_index].length-1); + return ac.chap.ACTION_RES_REDRAWCARET; + } + } + else if ( $isset(params.moveBy) ) + { + var offset = params.moveBy; + if ( 'column' == offset ) + { + //move by params.value columns, newlines are counted as column, params.value may be negative indication caret moving to the left + if ( 0 < params.value ) + { + var range_source = (this.char_map[caret_row].substr(caret_col)+'\n'+this.char_map.slice(caret_row+1).join('\n')).substr(0, params.value); + range_source = range_source.split('\n'); + caret_row += range_source.length-1; + caret_col = range_source[range_source.length-1].length + (1==range_source.length ? caret_col : 0); + } + else + { + var range_source = (this.char_map.slice(0, caret_row).join('\n')+'\n'+this.char_map[caret_row].substr(0, caret_col)); + range_source = range_source.substr(range_source.length+params.value); + range_source = range_source.split('\n'); + caret_row -= (range_source.length-1); + caret_col = (1==range_source.length ? caret_col : this.char_map[caret_row].length) - range_source[0].length; + } + this.setCaretPosition(caret_row, caret_col); + return ac.chap.ACTION_RES_REDRAWCARET; + } + else if ( 'row' == offset ) + { + // move by params.value rows + } + else if ( 'page' == offset ) + { + // move by params.value pages + } + } + else if ( $isset(params.moveTo) ) + { + // move to params.moveTo[0], params.moveTo[1] + this.setCaretPosition(params.moveTo[0], params.moveTo[1]); + return ac.chap.ACTION_RES_REDRAWCARET; + } + };break; + case ac.chap.ACTION_SELECTION: + { + if ( $isset(params.remove) ) + { + var changed = this.removeSelection(); + return ac.chap.ACTION_RES_REDRAWCARET | (changed ? ac.chap.ACTION_RES_REDRAWTEXT : 0); + } + else if ( $isset(params.add) ) + { + this.addSelection([caret_row, caret_col], this.state.lastCaretPosition); + this.renderSelection(); + return ac.chap.ACTION_RES_REDRAWCARET | ac.chap.ACTION_RES_SELECTIONCHANGED; + } + else if ( $isset(params.all) ) + { + $__tune.behavior.clearSelection(); + this.addAllSelection(); + this.renderSelection(); + return ac.chap.ACTION_RES_REDRAWCARET | ac.chap.ACTION_RES_SELECTIONCHANGED; + } + };break; + case ac.chap.ACTION_INSERT: + { + var str = null; + if ( $isset(params.row) ) + { + var ins_content = '\n'; + caret_col = 0; + // if ( 0 < caret_row ) + // { + // // indenting by previous row + // var t = this.char_map[caret_row]; + // var n = t.length; + // var re = this.language.indentIgnoreMarker; + // while ( caret_col width ) + { + // will go before [row,column] + source = this.char_map.slice(0, row).join('\n')+'\n'+this.char_map[row].substr(0, column); + return source.substr(source.length+width); + } + else + { + // will go after [row,column] + source = this.char_map.slice(row).join('\n').substr(column); + return source.substr(0, width); + } +} + +ac.chap.Window.prototype.getWordAt = function(row, column, numWords) +{ + // if numWords <0 returns words before otherwise after position. if omitted, default value is -1 that is word before caret + // also, if more than one word is required, returns array of words as result + row = $getdef(row, this.caret.position[0]); + column = $getdef(column, this.caret.position[1]); + numWords = $getdef(numWords, -1); + var words = []; + var re = this.language.wordDelimiter; + var required_words = numWords; + var direction_after = 0 < numWords; + var next_word = true; + required_words = Math.abs(-numWords); + if ( !direction_after ) + { + column--; + if ( -1 == column ) + { + row--; + if ( -1 == row ) + { + return words; + } + column = this.char_map[row].length-1; + } + } + while (true) + { + var ch = this.char_map[row].charAt(column) + if ( re.test(ch) ) + { + if ( next_word ) + { + words.push(''); + next_word = false; + } + // word character found + if ( direction_after ) + { + words[words.length-1] += ch; + } + else + { + words[words.length-1] = ch + words[words.length-1]; + } + } + else + { + if ( required_words == words.length ) + { + break; + } + next_word = true; + } + + column += (direction_after ? 1 : -1); + if ( 0 > column ) + { + row--; + if ( -1 == row ) + { + break; + } + next_word = true; + column = this.char_map[row].length-1; + } + else if ( this.char_map[row].length <= column ) + { + row++; + if ( this.char_map.length == row ) + { + break; + } + next_word = true; + column = 0; + } + } + if ( 1 == required_words ) + { + return words[0] ? words[0] : false; + } + return words; +} + +ac.chap.Window.prototype.getLineAt = function(row) +{ + return this.char_map[row]; +} + +ac.chap.Window.prototype.getText = function() +{ + return this.char_map.join('\n'); +} + +ac.chap.Window.prototype.getNumRows = function() +{ + return this.char_map.length; +} + +ac.chap.Window.prototype.getSyntaxHighlightingSource = function() +{ + if (null != this.activeView) + { + return this.activeView.getSyntaxHighlightingSource(); + } + return 'ni'; +} + +ac.chap.Window.prototype.getCaretAbsolutePosition = function() +{ + if ( null != this.activeView ) + { + var pos = this.activeView.getRenderedCharPosition(this.caret.position[0], this.caret.position[1]); + if ( null != pos ) + { + var root_pos = this.activeView.nodeScrollArea.abspos(); + return [pos[0]+root_pos.x+this.activeView.options.colWidth, pos[1]+root_pos.y+this.activeView.options.rowHeight]; + } + } + return null; +} + +ac.chap.Window.prototype.stopTransactionLog = function() +{ + this.state.transactionLogStopped = true; +} + +ac.chap.Window.prototype.startTransactionLog = function() +{ + this.state.transactionLogStopped = false; +} + +ac.chap.Window.prototype.addAllSelection = function() +{ + var num_rows = this.char_map.length; + var num_views = this.views.length; + for ( var i=0; i offset_x - mid_char_w ) + { + col_index = i; + break; + } + else if ( w - mid_char_w < offset_x && (dim[0] % w + 2*mid_char_w >= offset_x)) + { + // last char + col_index = i+1; + break; + } + } + i++; + } +// console.log('%s, %s', row_index, col_index); + if ( i == num_chars ) + { + col_index = i; + } +// console.log('CHANGE CARET to: %s', col_index); + this.hideCaret(); + if ( evt.shiftKey ) + { + this.addSelection([row_index, col_index], this.state.lastCaretPosition); + this.renderText(); + } + else + { + this.setCaretPosition(row_index, col_index); + this.state.caretPhase = 1; + view.showCaret(); + this.state.lastCaretPosition = [this.caret.position[0], this.caret.position[1]]; + if ( this.removeSelection() ) + { + this.renderText(); + } + } + ac.chap.setActiveComponent(this); +} + +ac.chap.Window.prototype.foldingize = function() +{ + var startRowIndex = 0; + + var source_rows = this.char_map.slice(startRowIndex); + + // creating folding info + var n = source_rows.length; + var foldings = []; + var foldings_index = -1; + for ( var i=0; i ixs[i][1] ) + { + found_marker_index = i; + lowest = ixs[i][1]; + } + } + } + if ( -1 == found_marker_index ) + { + break; + } + var start_index = ixs[found_marker_index][1]; + var skipped_source = source.substr(0, start_index); + var num_skipped_rows = skipped_source.split('\n').length; + cursor.row += num_skipped_rows - 1; + cursor.col = (1 == num_skipped_rows ? col_offset : 0) + skipped_source.length - ('\n'+skipped_source).lastIndexOf('\n'); + + if ( 'undefined' == typeof syntax_map[cursor.row] ) + { + syntax_map[cursor.row] = []; + } + + var start_marker_len = ixs[found_marker_index][2].length; + var end_marker_len = ixs[found_marker_index][3].length; + source = source.substr(start_index+start_marker_len); + + var token_type = ixs[found_marker_index][0]; + + var end_index = source.indexOf(ixs[found_marker_index][3]); + var sub_source = source; + var end_index_offset = 0; + var except = false; + while ( 0 < end_index && '' != ixs[found_marker_index][4] && ixs[found_marker_index][4] == sub_source.charAt(end_index-end_marker_len) ) + { + except = true; + end_index_offset += end_index + end_marker_len; + sub_source = sub_source.substr(end_index+end_marker_len); + end_index = sub_source.indexOf(ixs[found_marker_index][3]); + } + if ( except && -1 != end_index ) + { + end_index += end_index_offset; + } + if ( -1 == end_index ) + { + syntax_map[cursor.row].push([token_type, cursor.col, -1, '']); + fillRowTokens(token_type, cursor.row+1, -1); + break; + } + else + { + var block_source = source.substr(0, end_index); + var num_block_rows = '\n' == ixs[found_marker_index][3] ? 1 : block_source.split('\n').length; + var cursor_col_end = block_source.length - ('\n'+block_source).lastIndexOf('\n'); + + syntax_map[cursor.row].push([token_type, cursor.col, 1 == num_block_rows ? (cursor.col+end_index+start_marker_len+end_marker_len) : -1, ixs[found_marker_index][2]]); + fillRowTokens(token_type, cursor.row+1, cursor.row+num_block_rows-1); + if ( 1 == num_block_rows ) + { + col_offset = cursor.col + end_index + start_marker_len + end_marker_len; + if ( '\n' == ixs[found_marker_index][3] ) + { + cursor.row++; + col_offset = 0; + } + } + else + { + if ( 'undefined' == typeof syntax_map[cursor.row+num_block_rows] ) + { + syntax_map[cursor.row+num_block_rows-1] = []; + } + syntax_map[cursor.row+num_block_rows-1].push([token_type, -1, cursor_col_end + end_marker_len, '']); +// var a = block_source.split('\n'); + col_offset = cursor_col_end + end_marker_len; + cursor.row += num_block_rows -1; + } +// console.log(num_block_rows); + source = source.substr(end_index+end_marker_len); + } + } + delete ixs; + delete source; + + var n = Math.max(syntax_map.length, this.syntax_map.length); + for ( i=0; i'; + } + } + return ht; + } + var nodes = this.nodeEditArea.childNodes; + var ht = ''; + for (var i=0; i') ) + { + str = str.replace(/>/g, '>'); + } + if ( -1 != str.indexOf('<') ) + { + str = str.replace( / 1 2 3 =  1 2 3 +function ch_encode_markup_spaces(str) +{ + var n = str.length - str.replace(/ /g, '').length; + for ( var i=0; i/g, '').length); + return arguments[1]+(is_inside?'~`~`~`~`':' '); + }); + } + return str.replace(/~`~`~`~`/g, ' '); +} + + +ac.chap.View.prototype.getRenderedCharDimension = function(rowIndex, colIndex) +{ + return [this.options.colWidth, this.options.rowHeight]; +} + +ac.chap.View.prototype.getRenderedStringDimension = function(rowIndex, colIndex, width) +{ + if ( 'undefined' != typeof this.window.char_map[rowIndex] ) + { + if ( colIndex < this.window.char_map[rowIndex].length ) + { + var str = this.window.char_map[rowIndex].substr(colIndex, width); + var ix = 0; + var tab = this.options.tabelator; + while ( -1 != ix ) + { + ix = str.indexOf('\t'); + if ( -1 != ix ) + { + str = str.substr(0,ix)+tab.substr(0, tab.length-(ix % tab.length))+str.substr(ix+1); + } + } +// console.log('(getrenderedstringdimension) = [%s], ix:%s w:%s %s', this.options.colWidth*str.length, colIndex, width, str); + return [this.options.colWidth*str.length, this.options.rowHeight]; + } + } + return [0,0]; +} + +ac.chap.View.prototype.getVirtualStringDimension = function(row, colIndex, width) +{ + if ( colIndex < row.length ) + { + var str = row.substr(colIndex, width); + var ix = 0; + var tab = this.options.tabelator; + while ( -1 != ix ) + { + ix = str.indexOf('\t'); + if ( -1 != ix ) + { + str = str.substr(0,ix)+tab.substr(0, tab.length-(ix % tab.length))+str.substr(ix+1); + } + } +// console.log('(getrenderedstringdimension) = [%s], ix:%s w:%s %s', this.options.colWidth*str.length, colIndex, width, str); + return [this.options.colWidth*str.length, this.options.rowHeight]; + } + return [0,0]; +} + +ac.chap.View.prototype.getRenderedCharPosition = function(rowIndex, colIndex) +{ + var node_row = this.getRowNode(rowIndex); + if ( null != node_row && null != node_row.parentNode ) + { + var offset_x = this.getRenderedStringDimension(rowIndex, 0, colIndex)[0]; + var offset_y = 0; + var dim = this.getRenderedCharDimension(rowIndex, colIndex); + offset_x -= dim[0]; + if ( this.options.wordWrap ) + { + if ( 0 < colIndex ) + { + var w = this.options.colWidth * (this.numCols); + offset_y = this.options.rowHeight * (Math.floor(offset_x/w)); + offset_x = (offset_x) % w; + } + } + return [offset_x, node_row.offsetTop+offset_y, node_row]; + } + return null; +} + +ac.chap.View.prototype.getRowNode = function(rowIndex) +{ + return document.getElementById('row-'+this.window.instanceId+'-'+this.index+'-'+rowIndex); +} + +ac.chap.View.prototype.getVirtualCharPosition = function(nodeRow, row, colIndex) +{ + var offset_x = this.getVirtualStringDimension(row, 0, colIndex)[0]; + var offset_y = 0; + var dim = this.getRenderedCharDimension(0, colIndex); + offset_x -= dim[0]; + if ( this.options.wordWrap ) + { + if ( 0 < colIndex ) + { + var w = this.options.colWidth * (this.numCols); + offset_y = this.options.rowHeight * (Math.floor(offset_x/w)); + offset_x = (offset_x) % w; + } + } + return [offset_x, nodeRow.offsetTop+offset_y]; +} + +ac.chap.View.prototype.showCaret = function(skipScroll) +{ + var caret_row = this.window.caret.position[0]; + var caret_col = this.window.caret.position[1]; + + pos = this.getRenderedCharPosition(caret_row, caret_col); + if ( null != pos ) + { + // caret is visible + var node_row = pos[2]; + var node = document.getElementById('ac-chap-caret-'+this.window.instanceId); + if ( null != node ) + { + node.parentNode.removeChild(node); + } + if ( 1 == this.window.state.caretPhase ) + { + // displaying caret + node = document.createElement('div'); + node.id = 'ac-chap-caret-'+this.window.instanceId; + node.style.position = 'absolute'; + node.style.font = '1px arial'; // IE + node.style.width = this.options.colWidth + 'px'; + node.style.height = this.options.rowHeight + 'px'; + pos[2] = this.options.colWidth; + pos[3] = this.options.rowHeight; + pos = this.theme.adjustCaretPosition(this.window.caret.mode, pos); + node.style.left = pos[0]+'px'; + node.style.top = pos[1]+'px'; + this.theme.renderCaret(this.window.caret.mode, node); + this.nodeCaret = node_row.appendChild(node); + node_row.style.background = this.theme.caretRowStyleActive; + + if ( !skipScroll ) + { + // might be out of borders, at least partially + if ( 0 > node_row.offsetTop - (this.nodeScrollArea.$.scrollTop % this.options.rowHeight) ) + { + // top margin overlay, first rendered row is partially hidden + this.scrollToRow(caret_row); + } + else if ( node_row.offsetTop > this.options.rowHeight*(this.numRows-1)-$__tune.ui.scrollbarWidth ) + { + // bottom margin overlay + this.scrollToRow(caret_row-Math.floor(this.numRows/2)); + } + } + this.nodeCaretRow = node_row; + } + if ('undefined' != typeof this.state.lastCaretRowIndex && this.state.lastCaretRowIndex != caret_row) + { + var last_node_row = this.getRowNode(this.state.lastCaretRowIndex); + if (last_node_row) + { + last_node_row.style.background = 'transparent'; + } + } + this.state.lastCaretRowIndex = caret_row; + } + else + { + if ( !skipScroll ) + { + // scrolling into view + this.scrollToRow(caret_row - Math.floor(this.numRows/2)); + } + } +} + +ac.chap.View.prototype.hideCaret = function(skipCaretRow) +{ + if ( null != this.nodeCaret && null != this.nodeCaret.parentNode ) + { + this.nodeCaret.parentNode.removeChild(this.nodeCaret); + this.nodeCaret = null; + } + if ( !skipCaretRow && null != this.nodeCaretRow ) + { +// console.log('off(hide) caret line background for %s', this.nodeCaretRow.id); + this.nodeCaretRow.style.background = 'transparent'; + } +// console.log('hide caret'); +} + +ac.chap.View.prototype.scrollToRow = function(rowIndex, setCaretToo, dontRefreshCaret) +{ + this.nodeScrollArea.$.scrollTop = this.options.rowHeight * rowIndex - Math.floor(this.nodeRoot.$.offsetHeight/3); + if (setCaretToo) + { + this.window.runAction(ac.chap.ACTION_CARET, {moveTo:[rowIndex, 0]}); + this.window.runAction(ac.chap.ACTION_CARET, {move:'row_end'}); + } + if (!dontRefreshCaret) + { + this.window.state.caretPhase = 1; + this.showCaret(true); + } +} + +ac.chap.View.prototype.expandFolding = function(rowIndex) +{ + if ( 'undefined' == typeof this.window.row_id_map[this.index][rowIndex] ) + { + return; + } + var row_state = this.window.row_id_map[this.index][rowIndex][2]; + if (0 == (ac.chap.ROWSTATE_FOLD_EXPAND & row_state)) + { + return; + } + var end_row_index = this.window.row_id_map[this.index][rowIndex][3][1]; + this.window.row_id_map[this.index][rowIndex][2] &= (65535 - ac.chap.ROWSTATE_FOLD_EXPAND); + this.window.row_id_map[this.index][rowIndex][1] = false; + for ( var i=rowIndex+1; i<=end_row_index; i++ ) + { + this.window.row_id_map[this.index][i][1] = false; + this.window.row_id_map[this.index][i][2] &= (65535 - ac.chap.ROWSTATE_FOLD_COLLAPSED); + } + this.recalculateVisibleRows(); + var me = this; + // console.log('expanding: %i, start: %i', rowIndex, end_row_index); + $runafter(40, function(){me.renderText(true)}); +} + +ac.chap.View.prototype.resize = function() +{ + var h = this.nodeRoot.p().h(); + this.nodeRoot.h(h); + $(this.nodeSidebar).h(h); + this.nodeScrollArea.h(h); + this.nodeFillArea.h(h-$__tune.ui.scrollbarWidth); + $(this.nodeSelectionArea).h(h-$__tune.ui.scrollbarWidth+this.options.rowHeight); + this.recalculateNumRows(); + this.recalculateVisibleRows(); + this.renderText(true); +} + +ac.chap.View.prototype.reloadOptions = function() +{ + this.calculateColRowDim(); + this.recalculateNumCols(false, true); + this.recalculateNumRows(); + this.recalculateVisibleRows(); + this.renderSidebarStub(); + this.renderText(true); +} + +ac.chap.View.prototype.recalculateNumCols = function(node, withoutScrollbar) +{ + node = node || this.nodeRoot; + var w = node.$.offsetWidth; + if (withoutScrollbar) + { + w -= $__tune.ui.scrollbarWidth+61; + } + this.numCols = Math.floor(w/this.options.colWidth); +} + +ac.chap.View.prototype.recalculateNumRows = function(node) +{ + node = node || this.nodeRoot; + this.numRows = Math.floor(node.$.offsetHeight/this.options.rowHeight); +} + +ac.chap.View.prototype.showInteractiveSearch = function() +{ + this.hideInteractiveSearch(); + var pos = this.nodeRoot.abspos(); + var node = $().a($$()).pos(true).x(pos.x+58).y(pos.y).z(2000).w(this.nodeRoot.w()-$__tune.ui.scrollbarWidth-61).h(24).o(0.8); + node.s('background:#000;border:1px solid #777;border-top:0;'); + var search_key_id = 'is_key_?'.embed(this.window.ident); + var ht = '
'; + node.t(ht); + var me = this; + var status_node = node.g('td:0'); + var search_key_node = node.g('input:0'); + var original_caret_pos = [me.window.caret.position[0], me.window.caret.position[1]]; + var last_keyword = ''; + var selection = me.window.getSelection(); + search_key_node.e('keydown', function(evt) + { + evt.stopPropagation(); + if (40 == evt.keyCode) + { + me.window.runAction(ac.chap.ACTION_CUSTOM, {action:'SearchKeyword', direction:'down'}); + me.scrollToRow(me.window.caret.position[0], false, true); + me.window.processActionResult(true, true); + evt.preventDefault(); + return true; + } + else if (38 == evt.keyCode) + { + me.window.runAction(ac.chap.ACTION_CUSTOM, {action:'SearchKeyword', direction:'up'}); + me.scrollToRow(me.window.caret.position[0], false, true); + me.window.processActionResult(true, true); + evt.preventDefault(); + return true; + } + else if (27 == evt.keyCode) + { + finish(true); + evt.preventDefault(); + return true; + } + else if (13 == evt.keyCode) + { + finish(); + evt.preventDefault(); + return true; + } + }).e('keyup', function(evt) + { + evt.stopPropagation(); + search(); + + }).$.focus(); + node.g('img:0').e('click', function(evt) + { + evt.stopPropagation(); + finish(true); + }); + + function finish(canceled) + { + if (canceled) + { + me.window.removeSelection(); + me.window.runAction(ac.chap.ACTION_CARET, {moveTo:[original_caret_pos[0], original_caret_pos[1]]}); + me.scrollToRow(me.window.caret.position[0], false, true); + me.window.processActionResult(true, true); + } + ac.chap.setActiveComponent(me.window); + me.hideInteractiveSearch(); + } + + function update_status(numFound) + { + status_node.t('Found ? results.'.embed(numFound)); + } + + function search() + { + var keyword = search_key_node.$.value.trim(); + if ('' == keyword || last_keyword == keyword) + { + if ('' == keyword) + { + update_status(0); + } + return; + } + update_status(me.window.getText().split(keyword).length - 1); + last_keyword = keyword; + me.window.removeSelection(); + me.window.runAction(ac.chap.ACTION_CARET, {moveTo:[original_caret_pos[0], original_caret_pos[1]]}); + me.window.runAction(ac.chap.ACTION_CUSTOM, {action:'SetSearchKeyword', keyword:keyword}); + me.window.runAction(ac.chap.ACTION_CUSTOM, {action:'SearchKeyword', direction:'down'}); + me.scrollToRow(me.window.caret.position[0], false, true); + me.window.processActionResult(true, true); + } + + if (null != selection) + { + search_key_node.$.value = selection; + search_key_node.$.select(); + search_key_node.$.focus(); + search(); + } + + + + this.interactiveSearchNode = node; +} + +ac.chap.View.prototype.hideInteractiveSearch = function() +{ + if (this.interactiveSearchNode && this.interactiveSearchNode.is()) + { + this.interactiveSearchNode.rs(); + } +} + +ac.chap.View.prototype.render = function(node) +{ + var w = node.$.offsetWidth; + var h = node.$.offsetHeight; + this.recalculateNumRows(node); + this.recalculateNumCols(node); + node.sa('chap-view', 'true'); + var me = this; + this.nodeRoot = node.a($$()).pos(true).w(w).h(h).n('acw-chap').s('background:?'.embed(this.theme.background)); + this.interactiveSearchNode = null; + + var w_rows = 58; + w -= w_rows; + this.nodeSidebar = this.nodeRoot.a($$()).pos(true).x(0).y(0).w(w_rows).h(h).s('overflow:hidden').n('sidebar'); + + // rendering sidebar stub + this.renderSidebarStub(); + + + this.nodeScrollArea = this.nodeRoot.a($$()).pos(true).x(w_rows).y(0).w(w).h(h).n('scroll-area').s('overflow:auto'); + this.nodeScrollArea.e('scroll', function(evt) + { + var offset = Math.floor(me.nodeScrollArea.$.scrollTop/me.options.rowHeight); + var map = me.window.row_id_map[me.index]; + var row_index = 0; + for ( var i=0; i' + ch_encode_markup(token[1]) + ''; + } + else + { + rend_chunk += ch_encode_markup(token[1]); + } + i += token[1].length - 1; + // offset = i + token[1].length; + offset = i + 1; + // console.log(token, offset, rend_chunk); + } + rend_chunk += ch_encode_markup(chunk.substr(offset)); + chunk = rend_chunk; + } + else + { + chunk = ch_encode_markup(chunk); + } + return chunk; +} + +ac.chap.View.prototype.renderTextRow = function(node, rowIndex, renderedPreviously) +{ + var row = this.window.char_map[rowIndex]; + var rendered_row = ''; + var offset = 0; + var font_style = ';font:' + this.window.options.font.size + 'px ' + this.window.options.font.family; + var interpolation = this.window.language.stringInterpolation; + + var row_state = this.window.row_id_map[this.index][rowIndex][2]; + + if ( 'undefined' != typeof this.window.syntax_map[rowIndex] && 0 < this.window.syntax_map[rowIndex].length ) + { + // console.log(this.window.syntax_map[rowIndex]); + var n = this.window.syntax_map[rowIndex].length; + for ( var i=0; i' + ch_encode_markup(chunk.substring(0, m.index)) + ''; + new_chunk += this.renderChunk(chunk.substr(m.index, m[interpolation[1]].length)); + chunk = chunk.substr(m.index + m[interpolation[1]].length); + // console.warn(chunk); + } + new_chunk += '' + ch_encode_markup(chunk) + ''; + // console.info(new_chunk); + rendered_row += new_chunk; + } + else + { + rendered_row += ''+ch_encode_markup(chunk)+''; + // console.log(rendered_row); + } + } + else + { + rendered_row += ch_encode_markup(chunk); + } + offset = -1 == end_offset ? row.length : end_offset; + } + } + rendered_row += this.renderChunk(row.substr(offset)); + // console.log(rendered_row); + // rendering custom selection (search results, errors and such) + // !!!!!!!!!! + // NOT USED NOW !!!!! + // !!!!!!!!!! + // !!!!!!!!!! + // !!!!!!!!!! + // !!!!!!!!!! + if ( false && ac.chap.ROWSTATE_SELECTION == (row_state & ac.chap.ROWSTATE_SELECTION) ) + { + var range = this.window.row_id_map[this.index][rowIndex][5]; + if ( 0 == range[0] && this.window.char_map[rowIndex].length == range[1] ) + { + rendered_row = ''+rendered_row+''; + } + else + { + // console.log(range); + var raw = rendered_row; + var n = raw.length; + var col_index = 0; + var selection_started = false; + var offset = 0; + // console.log('before: %s', raw); + for ( var i=0; i'); + i += ix; + // console.log('ix: %s', ix); + + if ( selection_started ) + { + var c = ''; + rendered_row = rendered_row.substr(0, i+offset+1)+c+rendered_row.substr(i+offset+1); + offset += c.length; + } + continue; + } + if ( range[0] == col_index ) + { + selection_started = true; + var c = ''; + rendered_row = rendered_row.substr(0, i+offset)+c+rendered_row.substr(i+offset); + offset += c.length; + } + if ( selection_started && range[1]-1 < col_index ) + { + selection_started = false; + var c = ''; + rendered_row = rendered_row.substr(0, i+offset)+c+rendered_row.substr(i+offset); + break; + } + if ( '&' == ch ) + { + if ( '<' == raw.substr(i, 4) || '>' == raw.substr(i, 4) ) + { + i += 3; + } + else if ( '&' == raw.substr(i, 5) ) + { + i += 4; + } + } + col_index++; + } + if ( selection_started ) + { + rendered_row += ''; + } + } + } +// console.log(rendered_row); + // making intelligent tabelators - note, using simple replace of \t doesn't work + var ix = 0; + var tab = this.options.tabelator; + var raw = this.window.char_map[rowIndex]; + var tab_stack = []; + while ( -1 != ix ) + { + ix = raw.indexOf('\t'); + if ( -1 != ix ) + { + var tab_length = tab.length - (ix % tab.length); + raw = raw.substr(0,ix)+tab.substr(0, tab_length)+raw.substr(ix+1); + tab_stack.push(tab_length); + } + } + for ( var i=0; i'); + if ( -1 == ix ) + { + break; + } + i += ix; + continue; + } + var n_ch = 0; + if ( '&' == ch ) + { + if ( '<' == rendered_row.substr(i,4) || '>' == rendered_row.substr(i,4) ) + { + n_ch = 3; + } + else if ( '&' == rendered_row.substr(i,5) ) + { + n_ch = 4; + } + } + printable += ch.charAt(0); + if ( this.numCols == printable.length ) + { + raw = raw.substr(0, i+offset+n_ch)+'
'+raw.substr(i+offset+n_ch); + num_subrows++; + offset += 4; + printable = ''; + } + i += n_ch; + } + rendered_row = raw; + } + if ( ac.chap.ROWSTATE_FOLD_EXPAND == (row_state & ac.chap.ROWSTATE_FOLD_EXPAND) ) + { + var end_index = this.window.row_id_map[this.index][rowIndex][3][1]; + var content = ch_encode_markup(this.window.char_map.slice(rowIndex, end_index+1).join('\n').replace(/"/ig, "''")); + rendered_row += '
'.embed(content); + } + node.setAttribute('num-subrows', num_subrows); + + if ( $__tune.isIE ) + { + // IE trims input source in innerHTML + rendered_row = ch_encode_markup_spaces(rendered_row); + } + node.innerHTML = rendered_row; +} + +ac.chap.View.prototype.recalculateVisibleRows = function() +{ + var map = this.window.row_id_map[this.index]; + var n = map.length; + var i = 0; + var num_visibles = 0; + while ( i < n ) + { + var state = map[i][2]; + if ( 0 == (ac.chap.ROWSTATE_FOLD_COLLAPSED & state) ) + { + num_visibles++; + } + i++; + } + this.numVisibleRows = num_visibles; +} + +ac.chap.View.prototype.getVisibleRowIndices = function() +{ + var map = this.window.row_id_map[this.index]; + var i = 0; + var index = this.startRow; + var indices = []; + while ( i++ <= this.numRows ) + { + if ( 'undefined' == typeof this.window.row_id_map[this.index][index] ) + { + break; + } + var state = this.window.row_id_map[this.index][index][2]; + if ( ac.chap.ROWSTATE_FOLD_COLLAPSED == (ac.chap.ROWSTATE_FOLD_COLLAPSED & state) ) + { + // collapsed + i--; + index++; + continue; + } + indices.push(index); + if ( ac.chap.ROWSTATE_FOLD_EXPAND == (state & ac.chap.ROWSTATE_FOLD_EXPAND) ) + { + // collapsed folding + var refered_row_index = this.window.row_id_map[this.index][index][3][1]; + index = refered_row_index + 1; + } + else + { + index++; + } + } + return indices; +} + +ac.chap.View.prototype.renderRowSidebar = function(position, rowIndex, rowNode, forceCompleteRedraw) +{ + if (!this.nodeSidebar.firstChild.childNodes.item(position)) + { + return; + } + var bar_node = this.nodeSidebar.firstChild.childNodes.item(position).firstChild; + if ( 0 == rowNode.offsetHeight ) + { + rowNode.style.height = this.options.rowHeight; + } + var num_subrows = parseInt(rowNode.getAttribute('num-subrows')); + var cache_id = forceCompleteRedraw ? 'none' : (num_subrows+':'+this.window.row_id_map[this.index][rowIndex].join('-')); + if (bar_node.getAttribute('sidebar-cache-id') == cache_id && 'none' != cache_id) + { + return; + } + if ('none' != cache_id) + { + bar_node.setAttribute('sidebar-cache-id', cache_id); + bar_node.firstChild.style.fontSize = (this.window.options.font.size-2) + 'px'; + } + // console.log(cache_id); + + var row_height = num_subrows * this.options.rowHeight; + bar_node.parentNode.style.height = row_height + 'px'; + if (forceCompleteRedraw) + { + bar_node.firstChild.style.fontSize = (this.window.options.font.size-2) + 'px'; + } + var ht = rowIndex+1; + if ( this.options.wordWrap ) + { + var htt = '
'; + for ( var i=1; i this.numCols ) + { + ix_r++; + ix_c -= this.numCols; + } + if ( ii == range[1] ) + { + offset[2] = ix_r; + offset[3] = ix_c; + break; + } + } + if ( -1 == offset[3] ) + { + offset[2] = ix_r+1;//offset[0]+1; + } + node_row_selection.style.top = (node_row.offsetTop + this.options.rowHeight*offset[0]) + 'px'; +// console.log('%o', offset); + if ( offset[0] == offset[2] ) + { + // selection stays non-wrapped + node_row_selection.style.left = offset[1]*this.options.colWidth + 'px'; + node_row_selection.style.width = ((offset[3]-offset[1])*this.options.colWidth) + 'px'; +// console.log(node_row_selection.style.width); + } + else + { + // finishing current node + if ( -1 == offset[1] ) + { + // caret stays on the end of the row + node_row_selection.style.left = (ix_c*this.options.colWidth) + 'px'; + node_row_selection.style.width = (node_row.offsetWidth - (ix_c*this.options.colWidth)) + 'px'; + } + else + { + node_row_selection.style.left = (offset[1]*this.options.colWidth) + 'px'; + node_row_selection.style.width = (node_row.offsetWidth - (offset[1]*this.options.colWidth)) + 'px'; + } + // marking as non-cacheable + node_row_selection.removeAttribute('cachid'); + // creating additional ones + for ( ii=offset[0]+1; ii<=offset[2]; ii++ ) + { + node_row_selection = node_cache.appendChild(document.createElement('div')); + node_row_selection.style.background = this.theme.selectionStyle; + node_row_selection.style.position = 'absolute'; + node_row_selection.style.left = '0px'; + node_row_selection.style.top = (node_row.offsetTop+ii*this.options.rowHeight) + 'px'; + node_row_selection.style.height = this.options.rowHeight + 'px'; + if ( ii != offset[2] ) + { + node_row_selection.style.width = node_row.offsetWidth + 'px'; + } + else + { + node_row_selection.style.width = ((offset[3])*this.options.colWidth) + 'px'; + } + } + } + + } + else + { + var offset_x1 = this.getRenderedStringDimension(row_index, 0, range[0])[0]; + var offset_x2 = this.getRenderedStringDimension(row_index, 0, range[1]+1)[0]; + node_row_selection.style.left = offset_x1 + 'px'; + node_row_selection.style.width = (offset_x2 - offset_x1) + 'px'; + } + } + } + // console.log('selection after range: %o', this.window.row_id_map[this.index][row_index][3]); + + } + } + // console.log('%o', this.window.row_id_map[this.index][0]); + var ht = node_cache.innerHTML; + this.nodeSelectionArea.innerHTML = ht; +} + +ac.chap.View.prototype.renderText = function(forceCompleteRedraw) +{ + var row_indices = this.getVisibleRowIndices(); + var num_rows = row_indices.length; +// console.log('view: %s - row indices: %o', this.index, row_indices); + +// console.log('view: %s - num rows x cols [%s x %s]', this.index, this.numRows, this.numCols); + // checking to see if only one row changed - the most usual case + var changed_row_index = -1; + var changed_row_position = -1; + for ( var i=0; i fill_area_h ) + { + fill_area_h = this.nodeRoot.h()-$__tune.ui.scrollbarWidth; + } + this.nodeFillArea.h(fill_area_h); + + } + else + { + this.nodeEditAreaCache.innerHTML = ''; + } + if ( parseInt(this.nodeSidebar.firstChild.style.top) != top_offset ) + { + this.nodeSidebar.firstChild.style.top = (top_offset)+'px'; + this.nodeSidebar.firstChild.style.height = (this.nodeSidebar.offsetHeight - $__tune.ui.scrollbarWidth - top_offset)+'px'; + } + this.renderSelection(); +} + + + +/* + * ac.Chap - Text Editing Component widget - Settings file + */ + +if ( 'undefined' == typeof ac ) +{ + var ac = {chap:{}}; +} + + +$class('ac.chap.KeyMap', +{ + construct:function() + { + this.definition = {}; + this.isMac = true; + this.wordCompleteCache = null; + this.snippetCompleteCache = null; + this.searchKeyword = ''; + this.initDefinition(); + } +}); + +ac.chap.KeyMap.prototype.initDefinition = function() +{ +} + +ac.chap.KeyMap.prototype.importCommands = function(commands) +{ + var n = commands.length; + for ( var i=0; i looking_for_len && words_prev[i+1].substr(0, looking_for_len) == looking_for ) + { + if ( -1 == found_words_index.indexOf(' '+words_prev[i+1]) ) + { + found_words.push(words_prev[i+1]); + found_words_index += ' '+words_prev[i+1]; + } + } + if ( words_next[i] && words_next[i].length > looking_for_len && words_next[i].substr(0, looking_for_len) == looking_for ) + { + if ( -1 == found_words_index.indexOf(' '+words_next[i]) ) + { + found_words.push(words_next[i]); + found_words_index += ' '+words_next[i]; + } + } + } + if ( 1 < found_words.length ) + { +// console.log('results found: %o', found_words); + wcc.results = found_words; + wcc.index = 0; + proceed_complete = true; + } + } + else + { + proceed_complete = true; + } + var num_results = wcc.results.length; + if ( proceed_complete && 0 < num_results ) + { + var index = wcc.index; + index += params.direction ? 1 : -1; + if ( num_results <= index ) + { + index = 0; + } + else if ( 0 > index ) + { + index = num_results-1; + } +// console.log('n:%s i:%s', num_results, index); + // let's not add the following operation to the transaction/undo log + component.stopTransactionLog(); + component.runAction(ac.chap.ACTION_CARET, {move:'prev_word'}); + component.runAction(ac.chap.ACTION_SELECTION, {add:true}); + component.runAction(ac.chap.ACTION_DELETE, {character:false}); + component.runAction(ac.chap.ACTION_INSERT, {string:wcc.results[index]}); + component.startTransactionLog(); + wcc.index = index; + wcc.position = [component.caret.position[0], component.caret.position[1]]; + } + else + { + wcc.results = []; + } + this.wordCompleteCache = wcc; + return ac.chap.ACTION_RES_REDRAWCARET | ac.chap.ACTION_RES_REDRAWTEXT; +} + +ac.chap.KeyMap.prototype.getAffectedRows = function(component, caretRow) +{ + var from_row = caretRow; + var to_row = caretRow; + if (null != component.selection) + { + var start_pos = component.selection.startPosition[0]; + var end_pos = component.selection.endPosition[0]; + if (-1 == component.selection.endPosition[1]) + { + end_pos--; + } + from_row = Math.min(start_pos, end_pos); + to_row = Math.max(start_pos, end_pos); + } + return [from_row, to_row]; +} + +ac.chap.KeyMap.prototype.action_Indent = function(keyCode, controlKeysMask, caretRow, caretCol, component, params) +{ + var affected_rows = this.getAffectedRows(component, caretRow); + var tab = component.getTabelator(); + for (var i=affected_rows[0]; i<=affected_rows[1]; i++) + { + if ('right' == params.direction) + { + component.insertIntoCharacterMap(tab, i, 0); + } + else + { + var row = component.char_map[i]; + var index = 0; + while (('\t' == row.charAt(index) || ' ' == row.charAt(index)) && (row.length > index) && (tab.length > index)) index++; + if (0 < index) + { + component.removeFromCharacterMap(i, 0, i, index); + } + } + } + return ac.chap.ACTION_RES_REDRAWCARET | ac.chap.ACTION_RES_REDRAWTEXT | ac.chap.ACTION_RES_SELECTIONCHANGED; +} + +ac.chap.KeyMap.prototype.action_Comment = function(keyCode, controlKeysMask, caretRow, caretCol, component, params) +{ + if (!component.language) + { + return 0; + } + var markers = component.language.singleRowCommentStartMarkers; + if (0 == markers.length) + { + return 0; + } + var marker = markers[0]; + var tab = component.getTabelator(); + var affected_rows = this.getAffectedRows(component, caretRow); + var tab = component.getTabelator(); + var prepend_text = marker + ' '; + for (var i=affected_rows[0]; i<=affected_rows[1]; i++) + { + var row = component.char_map[i]; + var index = row.indexOf(marker); + if (-1 != index && 0 == row.substring(0, index).replace(tab, '').replace(' ', '')) + { + // was commented + component.removeFromCharacterMap(i, 0, i, index+marker.length); + } + else + { + // will be commented + component.insertIntoCharacterMap(marker, i, 0); + } + } + return ac.chap.ACTION_RES_REDRAWCARET | ac.chap.ACTION_RES_REDRAWTEXT | ac.chap.ACTION_RES_SELECTIONCHANGED; +} + +ac.chap.KeyMap.prototype.action_RuntimeOption = function(keyCode, controlKeysMask, caretRow, caretCol, component, params) +{ + component.setRuntimeOption(params['key'], params['value']); + return ac.chap.ACTION_RES_REDRAWCARET | ac.chap.ACTION_RES_REDRAWTEXT | ac.chap.ACTION_RES_SELECTIONCHANGED; +} + +ac.chap.KeyMap.prototype.action_SearchInteractive = function(keyCode, controlKeysMask, caretRow, caretCol, component, params) +{ + component.showInteractiveSearch(); + return ac.chap.ACTION_RES_REDRAWCARET | ac.chap.ACTION_RES_SELECTIONCHANGED; +} + +ac.chap.KeyMap.prototype.action_SetSearchKeyword = function(keyCode, controlKeysMask, caretRow, caretCol, component, params) +{ + if (params['keyword']) + { + this.searchKeyword = params['keyword']; + return 0; + } + if (component.selection && component.selection.startPosition[0] == component.selection.endPosition[0]) + { + this.searchKeyword = component.getSelection(); + return ac.chap.ACTION_RES_SELECTIONCHANGED; + } + return 0; +} + +ac.chap.KeyMap.prototype.action_SearchKeyword = function(keyCode, controlKeysMask, caretRow, caretCol, component, params) +{ + if ('' == this.searchKeyword) + { + return 0; + } + row_index = caretRow; + var index = 0; + var search_down = 'down' == params['direction']; + do + { + var row = component.char_map[row_index]; + var offset = 0; + console.log(ac.chap.activeComponent.activeView); + if (row_index == caretRow) + { + if (search_down) + { + // if (row.substring(caretCol, this.searchKeyword.length) == this.searchKeyword) + // { + // // offset = this.searchKeyword.length; + // } + row = row.substr(caretCol) + offset += caretCol; + } + else + { + if (row.substring(caretCol-this.searchKeyword.length, caretCol)) + { + offset = this.searchKeyword.length; + } + row = row.substring(0, caretCol - offset); + offset = 0; + } + } + index = search_down ? row.indexOf(this.searchKeyword) : row.lastIndexOf(this.searchKeyword); + if (-1 != index) + { + index += offset; + component.runAction(ac.chap.ACTION_SELECTION, {remove:true}); + component.runAction(ac.chap.ACTION_CARET, {moveTo:[row_index, index]}); + component.runAction(ac.chap.ACTION_CARET, {store:true}); + component.runAction(ac.chap.ACTION_CARET, {moveTo:[row_index, index+this.searchKeyword.length]}); + component.runAction(ac.chap.ACTION_SELECTION, {add:true}); + return ac.chap.ACTION_RES_REDRAWCARET | ac.chap.ACTION_RES_SELECTIONCHANGED | ac.chap.ACTION_RES_SCROLLTOCARET; + } + row_index += search_down ? 1 : -1; + if (search_down && row_index == component.char_map.length) + { + row_index = 0; + } + else if (!search_down && -1 == row_index) + { + row_index = component.char_map.length-1; + } + } + while (caretRow != row_index); + return ac.chap.ACTION_RES_REDRAWCARET | ac.chap.ACTION_RES_SELECTIONCHANGED; +} + +ac.chap.KeyMap.prototype.action_SmartIndent = function(keyCode, controlKeysMask, caretRow, caretCol, component, params) +{ + // console.log(params); + var line = component.getLineAt(caretRow); + var m = params['indent_tab_when_ends'] ? line.match(/^([ \t]*)(.*)$/) : line.match(/^([ \t]*)([^ \t]*)/); + // console.log(m); + var prepend_text = m[1]; + if (params['indent_tab_when_ends'] || params['indent_tab_when_starts']) + { + m[2] = m[2].trim(); + var indent_when_values = params['indent_tab_when_ends'] ? params['indent_tab_when_ends'].split(' ') : params['indent_tab_when_starts'].split(' '); + for (var i=0; i#'); + code = code.substr(start_ix+4, m[3].length)+'##'+code.substr(start_ix+m[0].length-m[1].length); + any_change = true; + } + else + { + break; + } + } + code_chunks.push(code); + code = code_chunks.join(''); + } + re = /##/; + while ( true ) + { + m = re.exec(code) + if ( null == m ) + { + break; + } + tabstops[m[1]][1] = tabstops[m[1]][1].replace(/##/g, ''); + tabstops[m[1]][2] = m.index; + tabstops[m[1]][3] = tabstops[m[1]][1].length; + code = code.substr(0, m.index)+code.substr(m.index+m[0].length).replace('##', ''); + } + + // [getting mirrors] + re = /\{\$\{(\d)\:([^\}]*)\}\}/; + while ( true ) + { + m = re.exec(code) + if ( null == m ) + { + break; + } + code = code.substr(0, m.index)+m[2]+code.substr(m.index+m[0].length); + var ix_end = code.indexOf('{$'+m[1]+'}'); + if ( -1 == ix_end ) + { + console.error('Invalid snippet definition. Mirror `?` does not have `{$?}` mirrored location specified.'.embed(m[1], m[1])); + break; + } + tabstops[m[1]] = ['mi', m[2], m.index, m[2].length, ix_end]; + if ( m.index > ix_end ) + { + console.error('Unsupported feature. Mirror `?` should have mirrored location positioned AFTER itself.'.embed(m[1])); + } + code = code.substr(0, ix_end)+code.substr(ix_end+4); + } + + // [getting tabstops] + re = /(^|[^\\])\$(\d)/; + while (true) + { + m = re.exec(code); + if ( null == m ) + { + break; + } + tab_id = m[2]; + if ( tabstops[tab_id] ) + { + console.error('Invalid snippet definition. Tabstop `?` already defined as placeholder at position `?`. Snippet source: `?`.'.embed(tab_id, m.index, code)); + break; + } + var start_ix = m.index+m[1].length; + tabstops[tab_id] = ['ts', '', start_ix, 0]; + code = code.substr(0, start_ix)+code.substr(start_ix+2); + var offset = m[1].length + 2; + for ( var tab_id in tabstops ) + { + // console.log('adjusting: %s, %s < %s', tab_id, start_ix, tabstops[tab_id][2]); + if ( 'mi' == tabstops[tab_id][0] ) + { + if ( start_ix < tabstops[tab_id][2] ) + { + tabstops[tab_id][2] -= offset; + } + if ( start_ix < tabstops[tab_id][4] ) + { + tabstops[tab_id][4] -= offset; + } + } + else if ( 'ph' == tabstops[tab_id][0] && start_ix < tabstops[tab_id][2] ) + { + tabstops[tab_id][2] -= offset; + } + } + } + // $0 not defined, will be at the end of the snippet by default + if ( !tabstops[0] ) + { + tabstops[0] = ['ts', '', code.length, 0]; + } + + // [postprocessing - unescape] + code = code.replace(/\0/g, '$'); + + + for ( var tab_id in tabstops ) + { + var placeholder = tabstops[tab_id]; +// console.log('#%s : %o', tab_id, placeholder); + } + var scc = + { + firstInitialized:true, + insertCaretPosition:[caretRow, caretCol], + tabstops: tabstops, + callbackIndex: -1, + activeTabStopIndex:tabstops[1] ? 1 : 0, + activeTabStopRange:[], + activeTabStopContent:'', + activeTabStopNested:[], + wasSelection:wasSelection, + tabActivation:tabActivation + } + + if ( !scc.wasSelection ) + { + component.runAction(ac.chap.ACTION_CARET, {move:'prev_word'}); + component.runAction(ac.chap.ACTION_SELECTION, {add:true}); + component.runAction(ac.chap.ACTION_DELETE, {character:true}); + } + + var tabstop = tabstops[scc.activeTabStopIndex]; + + + component.runAction(ac.chap.ACTION_INSERT, {string:code.substr(0, tabstop[2])}); + component.runAction(ac.chap.ACTION_INSERT, {string:code.substr(tabstop[2]), skipCaretChange:true}); + + // selecting default value + var selection_changed = false; + if ( '' != tabstop[1] ) + { + component.runAction(ac.chap.ACTION_CARET, {store:true}); + component.runAction(ac.chap.ACTION_CARET, {moveBy:'column', value:tabstop[1].length}); + component.runAction(ac.chap.ACTION_SELECTION, {add:true}); + selection_changed = true; + } + + if ( 0 != scc.activeTabStopIndex ) + { + // starting action listener + this.snippetCompleteCache = scc; + this.snippetCompleteCache.callbackIndex = component.addActionListener(ac.chap.ACTION_LISTENER_BOTH, this, this.snippetCompleteActionListener); + } + } + return ac.chap.ACTION_RES_REDRAWCARET | ac.chap.ACTION_RES_REDRAWTEXT | (selection_changed ? ac.chap.ACTION_RES_SELECTIONCHANGED : 0); +// return ac.chap.ACTION_RES_REDRAWCARET | ac.chap.ACTION_RES_REDRAWTEXT; +} + +ac.chap.KeyMap.prototype.snippetCompleteActionListener = function(component, action, type, actionResult, actionType, params, caretRow, caretCol) +{ + var scc = action.snippetCompleteCache; + + if ( ac.chap.ACTION_LISTENER_BEFORE == type && !scc.firstInitialized ) + { + // before action listener + // check if we are still in the tabstop range +// var offset = component.char_map[caretRow].substr() + // console.log('activeTabStopRange: %o', scc.activeTabStopRange); + if ( caretRow < scc.activeTabStopRange[0] || caretRow > scc.activeTabStopRange[2] || caretCol < scc.activeTabStopRange[1] || caretCol > scc.activeTabStopRange[3] ) + { + // out of range, canceling whole snippet logic + component.removeActionListener(scc.callbackIndex); + component.removeSelection(); + delete action.snippetCompleteCache; + // console.log('CANCELED'); + return; + } +// console.log('before: %s - [%s,%s]', actionType, caretRow, caretCol); + } + else + { + // after action listener + if ( scc.firstInitialized ) + { + scc.firstInitialized = false; + var tabstop = scc.tabstops[scc.activeTabStopIndex]; + caretCol -= tabstop[1].length; + var code_rows = tabstop[1].split('\n'); + var num_code_rows = code_rows.length; + var to_caret_row = caretRow + num_code_rows - 1; + var to_caret_col = (to_caret_row == caretRow ? caretCol : 0) + code_rows[num_code_rows-1].length; + scc.activeTabStopRange = [caretRow, caretCol, to_caret_row, to_caret_col]; + scc.stopMarker = component.char_map[to_caret_row].substr(to_caret_col); + // creating list of nested tabstops + for ( var i in scc.tabstops ) + { + if ( i == scc.activeTabStopIndex ) + { + continue; + } + var c_tabstop = scc.tabstops[i]; + if ( c_tabstop[2] >= tabstop[2] && (c_tabstop[2] + c_tabstop[3] <= tabstop[2] + tabstop[3]) ) + { + scc.activeTabStopNested[i] = true; + } + } + if ( 'mi' != tabstop[0] ) + { + scc.activeTabStopContent = tabstop[1]; + } + scc.firstRealRun = false; + +// action.snippetCompletePostInit(component, caretRow, caretCol); + // console.log('firstRealRun: %o', scc.activeTabStopRange); + } + else + { + // console.log('next: %o', scc.activeTabStopRange); + // adjust the range by finding the stop marker + var n = component.char_map.length; + var i = scc.activeTabStopRange[0]; + var found = false; + var max_iter = 50; +// var offset_range = [scc.activeTabStopRange[2], scc.activeTabStopRange[3]]; + var new_content = ''; + var old_content = scc.activeTabStopContent; + while ( i active_offset ) + { + if ( scc.activeTabStopNested[i] ) + { + // nested + delete scc.tabstops[i]; + } + else + { + tabstop[2] += offset; + // console.log('ADJUSTING: #%s by %s, new: %s', i, offset, tabstop[2]); + } + } + } + } + } + // console.log('after %s(%s, %s) : %o', actionType, caretRow, caretCol, scc.activeTabStopRange); + } +} + + +ac.chap.KeyMap.prototype.compile = function(source) +{ + /* example: + + KEY: -13+shift + selection(add:true) + caret(move:'up') + + KEY: -27 + caret(move:'row_end') + + ... + .. + . + */ + var rows = source.split('\n'); + var n = rows.length; + var re_definition = /^KEY\:\s*[-\d]*[\+\w\s]*$/; + var re_chain = /^[^\(]*\(.*\)\s*$/; + var src = ''; + var chain = []; + var last_key_code = null; + for ( var i=0; i)/i, 0, ac.chap.CHUNK_OPERATOR], + [/(\(|\)|\[|\]|\{|\})/i, 0, ac.chap.CHUNK_PARENTHESIS] + ]; + this.wordDelimiter = /[\w\.\d]/; + this.indentIgnoreMarker = /[\.]/; +} + + +ac.chap.lang = {}; + + + + + + +/* loader stuff - you are free to modify as needed */ + + +// !! Make sure, bundle_*.js is loaded prior launching this function - the bundle defines ac.chap.langEAmy, EAmyJavaScript etc. +function showEditor(templateNode) +{ + var source = templateNode.value; + templateNode = $(templateNode); + var w = templateNode.w(); + var h = templateNode.h(); + + var node = templateNode.p().ib($$(), templateNode).w(w).h(h); + templateNode.d(false); + + var language = ac.chap.lang.JavaScript; + var keymap = ac.chap.keymap.EAmyJavaScript; + + var instance = $new(ac.chap.Window, {language:ac.chap.lang.EAmy, keymap:ac.chap.Keymap}); + instance.addView(node, {theme:ac.chap.theme.EAmy, rowHeight:11, colWidth:7, wordWrap:true, tabelator:' '}); + + instance.show(); + instance.setSnippets(eamy.snippets); + instance.keymap.importSnippets(eamy.snippets); + instance.edit(source); + eamy.instances.push(instance); + +} + +// !! Remove from here and include in your section if you want. +// document.write(''); + + +// Performed upon loading the page. You are free to remove it and call the showEditor() (or modified version of it) in order to launch the editing component. Code of the showEditor should give you enough clue. +$__tune.event.addListener(self, 'load', function(evt) +{ + // this is basically a search for any
"; + html= t.translate(html, editAreas[id]["settings"]["language"]); + span.innerHTML= html; + father= d.getElementById(id).parentNode; + next= d.getElementById(id).nextSibling; + if(next==null) + father.appendChild(span); + else + father.insertBefore(span, next); + } + + if(!editAreas[id]["initialized"]) + { + t.execCommand(id, "EA_init"); // ini callback + if(editAreas[id]["settings"]["display"]=="later"){ + editAreas[id]["initialized"]= true; + return; + } + } + + if(t.isIE){ // launch IE selection checkup + t.init_ie_textarea(id); + } + + // get toolbar content + var area=editAreas[id]; + + for(i=0; i'; + } + + // add plugins scripts if not already loaded by the compressor (but need to load language in all the case) + for(i=0; i'; + t.iframe_script+=''; + } + + + // create css link for the iframe if the whole css text has not been already loaded by the compressor + if(!t.iframe_css){ + t.iframe_css=""; + } + + + // create template + template= t.template.replace(/\[__BASEURL__\]/g, t.baseURL); + template= template.replace("[__TOOLBAR__]",html_toolbar_content); + + + // fill template with good language sentences + template= t.translate(template, area["settings"]["language"], "template"); + + // add css_code + template= template.replace("[__CSSRULES__]", t.iframe_css); + // add js_code + template= template.replace("[__JSCODE__]", t.iframe_script); + + // add version_code + template= template.replace("[__EA_VERSION__]", t.version); + //template=template.replace(/\{\$([^\}]+)\}/gm, this.traduc_template); + + //editAreas[area["settings"]["id"]]["template"]= template; + + area.textarea=d.getElementById(area["settings"]["id"]); + editAreas[area["settings"]["id"]]["textarea"]=area.textarea; + + // if removing previous instances from DOM before (fix from Marcin) + if(typeof(window.frames["frame_"+area["settings"]["id"]])!='undefined') + delete window.frames["frame_"+area["settings"]["id"]]; + + // insert template in the document after the textarea + father= area.textarea.parentNode; + /* var container= document.createElement("div"); + container.id= "EditArea_frame_container_"+area["settings"]["id"]; + */ + content= d.createElement("iframe"); + content.name= "frame_"+area["settings"]["id"]; + content.id= "frame_"+area["settings"]["id"]; + content.style.borderWidth= "0px"; + setAttribute(content, "frameBorder", "0"); // IE + content.style.overflow="hidden"; + content.style.display="none"; + + + next= area.textarea.nextSibling; + if(next==null) + father.appendChild(content); + else + father.insertBefore(content, next) ; + f=window.frames["frame_"+area["settings"]["id"]]; + f.document.open(); + f.editAreas=editAreas; + f.area_id= area["settings"]["id"]; + f.document.area_id= area["settings"]["id"]; + f.document.write(template); + f.document.close(); + + // frame.editAreaLoader=this; + //editAreas[area["settings"]["id"]]["displayed"]=true; + + }, + + toggle : function(id, toggle_to){ + + /* if((editAreas[id]["displayed"]==true && toggle_to!="on") || toggle_to=="off"){ + this.toggle_off(id); + }else if((editAreas[id]["displayed"]==false && toggle_to!="off") || toggle_to=="on"){ + this.toggle_on(id); + }*/ + if(!toggle_to) + toggle_to= (editAreas[id]["displayed"]==true)?"off":"on"; + if(editAreas[id]["displayed"]==true && toggle_to=="off"){ + this.toggle_off(id); + }else if(editAreas[id]["displayed"]==false && toggle_to=="on"){ + this.toggle_on(id); + } + + return false; + }, + + // static function + toggle_off : function(id){ + var fs=window.frames,f,t,parNod,nxtSib,selStart,selEnd,scrollTop,scrollLeft; + if(fs["frame_"+id]) + { + f = fs["frame_"+id]; + t = editAreas[id]["textarea"]; + if(f.editArea.fullscreen['isFull']) + f.editArea.toggle_full_screen(false); + editAreas[id]["displayed"]=false; + + // set wrap to off to keep same display mode (some browser get problem with this, so it need more complex operation + t.wrap = "off"; // for IE + setAttribute(t, "wrap", "off"); // for Firefox + parNod = t.parentNode; + nxtSib = t.nextSibling; + parNod.removeChild(t); + parNod.insertBefore(t, nxtSib); + + // restore values + t.value= f.editArea.textarea.value; + selStart = f.editArea.last_selection["selectionStart"]; + selEnd = f.editArea.last_selection["selectionEnd"]; + scrollTop = f.document.getElementById("result").scrollTop; + scrollLeft = f.document.getElementById("result").scrollLeft; + + + document.getElementById("frame_"+id).style.display='none'; + + t.style.display="inline"; + + try{ // IE will give an error when trying to focus an invisible or disabled textarea + t.focus(); + } catch(e){}; + if(this.isIE){ + t.selectionStart= selStart; + t.selectionEnd = selEnd; + t.focused = true; + set_IE_selection(t); + }else{ + if(this.isOpera && this.isOpera < 9.6 ){ // Opera bug when moving selection start and selection end + t.setSelectionRange(0, 0); + } + try{ + t.setSelectionRange(selStart, selEnd); + } catch(e) {}; + } + t.scrollTop= scrollTop; + t.scrollLeft= scrollLeft; + f.editArea.execCommand("toggle_off"); + + } + }, + + // static function + toggle_on : function(id){ + var fs=window.frames,f,t,selStart=0,selEnd=0,scrollTop=0,scrollLeft=0,curPos,elem; + + if(fs["frame_"+id]) + { + f = fs["frame_"+id]; + t = editAreas[id]["textarea"]; + area= f.editArea; + area.textarea.value= t.value; + + // store display values; + curPos = editAreas[id]["settings"]["cursor_position"]; + + if(t.use_last==true) + { + selStart = t.last_selectionStart; + selEnd = t.last_selectionEnd; + scrollTop = t.last_scrollTop; + scrollLeft = t.last_scrollLeft; + t.use_last=false; + } + else if( curPos == "auto" ) + { + try{ + selStart = t.selectionStart; + selEnd = t.selectionEnd; + scrollTop = t.scrollTop; + scrollLeft = t.scrollLeft; + //alert(scrollTop); + }catch(ex){} + } + + // set to good size + this.set_editarea_size_from_textarea(id, document.getElementById("frame_"+id)); + t.style.display="none"; + document.getElementById("frame_"+id).style.display="inline"; + area.execCommand("focus"); // without this focus opera doesn't manage well the iframe body height + + + // restore display values + editAreas[id]["displayed"]=true; + area.execCommand("update_size"); + + f.document.getElementById("result").scrollTop= scrollTop; + f.document.getElementById("result").scrollLeft= scrollLeft; + area.area_select(selStart, selEnd-selStart); + area.execCommand("toggle_on"); + + + } + else + { + /* if(this.isIE) + get_IE_selection(document.getElementById(id)); */ + elem= document.getElementById(id); + elem.last_selectionStart= elem.selectionStart; + elem.last_selectionEnd= elem.selectionEnd; + elem.last_scrollTop= elem.scrollTop; + elem.last_scrollLeft= elem.scrollLeft; + elem.use_last=true; + editAreaLoader.start(id); + } + }, + + set_editarea_size_from_textarea : function(id, frame){ + var elem,width,height; + elem = document.getElementById(id); + + width = Math.max(editAreas[id]["settings"]["min_width"], elem.offsetWidth)+"px"; + height = Math.max(editAreas[id]["settings"]["min_height"], elem.offsetHeight)+"px"; + if(elem.style.width.indexOf("%")!=-1) + width = elem.style.width; + if(elem.style.height.indexOf("%")!=-1) + height = elem.style.height; + //alert("h: "+height+" w: "+width); + + frame.style.width= width; + frame.style.height= height; + }, + + set_base_url : function(){ + var t=this,elems,i,docBasePath; + + if( !this.baseURL ){ + elems = document.getElementsByTagName('script'); + + for( i=0; i'; + html += ''; + return html; + }, + + get_control_html : function(button_name, lang) { + var t=this,i,but,html,si; + for (i=0; i"; + case "|": + case "separator": + return ''; + case "select_font": + html= ""; + return html; + case "syntax_selection": + html= ""; + return html; + } + + return "["+button_name+"]"; + }, + + + get_template : function(){ + if(this.template=="") + { + var xhr_object = null; + if(window.XMLHttpRequest) // Firefox + xhr_object = new XMLHttpRequest(); + else if(window.ActiveXObject) // Internet Explorer + xhr_object = new ActiveXObject("Microsoft.XMLHTTP"); + else { // XMLHttpRequest not supported + alert("XMLHTTPRequest not supported. EditArea not loaded"); + return; + } + + xhr_object.open("GET", this.baseURL+"template.html", false); + xhr_object.send(null); + if(xhr_object.readyState == 4) + this.template=xhr_object.responseText; + else + this.has_error(); + } + }, + + // translate text + translate : function(text, lang, mode){ + if(mode=="word") + text=editAreaLoader.get_word_translation(text, lang); + else if(mode="template"){ + editAreaLoader.current_language= lang; + text=text.replace(/\{\$([^\}]+)\}/gm, editAreaLoader.translate_template); + } + return text; + }, + + translate_template : function(){ + return editAreaLoader.get_word_translation(EditAreaLoader.prototype.translate_template.arguments[1], editAreaLoader.current_language); + }, + + get_word_translation : function(val, lang){ + var i; + + for( i in editAreaLoader.lang[lang]){ + if(i == val) + return editAreaLoader.lang[lang][i]; + } + return "_"+val; + }, + + load_script : function(url){ + var t=this,d=document,script,head; + + if( t.loadedFiles[url] ) + return; + //alert("load: "+url); + try{ + script= d.createElement("script"); + script.type= "text/javascript"; + script.src= url; + script.charset= "UTF-8"; + d.getElementsByTagName("head")[0].appendChild(script); + }catch(e){ + d.write(''); + } + + t.loadedFiles[url] = true; + }, + + add_event : function(obj, name, handler) { + try{ + if (obj.attachEvent) { + obj.attachEvent("on" + name, handler); + } else{ + obj.addEventListener(name, handler, false); + } + }catch(e){} + }, + + remove_event : function(obj, name, handler){ + try{ + if (obj.detachEvent) + obj.detachEvent("on" + name, handler); + else + obj.removeEventListener(name, handler, false); + }catch(e){} + }, + + + // reset all the editareas in the form that have been reseted + reset : function(e){ + var formObj,is_child,i,x; + + formObj = editAreaLoader.isIE ? window.event.srcElement : e.target; + if(formObj.tagName!='FORM') + formObj= formObj.form; + + for( i in editAreas ){ + is_child= false; + for( x=0;x old_sel["start"]) // if text was selected, cursor at the end + this.setSelectionRange(id, new_sel["end"], new_sel["end"]); + else // cursor in the middle + this.setSelectionRange(id, old_sel["start"]+open_tag.length, old_sel["start"]+open_tag.length); + }, + + // hide both EditArea and normal textarea + hide : function(id){ + var fs= window.frames,d=document,t=this,scrollTop,scrollLeft,span; + if(d.getElementById(id) && !t.hidden[id]) + { + t.hidden[id]= {}; + t.hidden[id]["selectionRange"]= t.getSelectionRange(id); + if(d.getElementById(id).style.display!="none") + { + t.hidden[id]["scrollTop"]= d.getElementById(id).scrollTop; + t.hidden[id]["scrollLeft"]= d.getElementById(id).scrollLeft; + } + + if(fs["frame_"+id]) + { + t.hidden[id]["toggle"]= editAreas[id]["displayed"]; + + if(fs["frame_"+id] && editAreas[id]["displayed"]==true){ + scrollTop = fs["frame_"+ id].document.getElementById("result").scrollTop; + scrollLeft = fs["frame_"+ id].document.getElementById("result").scrollLeft; + }else{ + scrollTop = d.getElementById(id).scrollTop; + scrollLeft = d.getElementById(id).scrollLeft; + } + t.hidden[id]["scrollTop"]= scrollTop; + t.hidden[id]["scrollLeft"]= scrollLeft; + + if(editAreas[id]["displayed"]==true) + editAreaLoader.toggle_off(id); + } + + // hide toggle button and debug box + span= d.getElementById("EditAreaArroundInfos_"+id); + if(span){ + span.style.display='none'; + } + + // hide textarea + d.getElementById(id).style.display= "none"; + } + }, + + // restore hidden EditArea and normal textarea + show : function(id){ + var fs= window.frames,d=document,t=this,span; + if((elem=d.getElementById(id)) && t.hidden[id]) + { + elem.style.display= "inline"; + elem.scrollTop= t.hidden[id]["scrollTop"]; + elem.scrollLeft= t.hidden[id]["scrollLeft"]; + span= d.getElementById("EditAreaArroundInfos_"+id); + if(span){ + span.style.display='inline'; + } + + if(fs["frame_"+id]) + { + + // restore toggle button and debug box + + + // restore textarea + elem.style.display= "inline"; + + // restore EditArea + if(t.hidden[id]["toggle"]==true) + editAreaLoader.toggle_on(id); + + scrollTop = t.hidden[id]["scrollTop"]; + scrollLeft = t.hidden[id]["scrollLeft"]; + + if(fs["frame_"+id] && editAreas[id]["displayed"]==true){ + fs["frame_"+ id].document.getElementById("result").scrollTop = scrollTop; + fs["frame_"+ id].document.getElementById("result").scrollLeft = scrollLeft; + }else{ + elem.scrollTop = scrollTop; + elem.scrollLeft = scrollLeft; + } + + } + // restore selection + sel = t.hidden[id]["selectionRange"]; + t.setSelectionRange(id, sel["start"], sel["end"]); + delete t.hidden[id]; + } + }, + + // get the current file datas (for multi file editing mode) + getCurrentFile : function(id){ + return this.execCommand(id, 'get_file', this.execCommand(id, 'curr_file')); + }, + + // get the given file datas (for multi file editing mode) + getFile : function(id, file_id){ + return this.execCommand(id, 'get_file', file_id); + }, + + // get all the openned files datas (for multi file editing mode) + getAllFiles : function(id){ + return this.execCommand(id, 'get_all_files()'); + }, + + // open a file (for multi file editing mode) + openFile : function(id, file_infos){ + return this.execCommand(id, 'open_file', file_infos); + }, + + // close the given file (for multi file editing mode) + closeFile : function(id, file_id){ + return this.execCommand(id, 'close_file', file_id); + }, + + // close the given file (for multi file editing mode) + setFileEditedMode : function(id, file_id, to){ + var reg1,reg2; + reg1 = new RegExp('\\\\', 'g'); + reg2 = new RegExp('"', 'g'); + return this.execCommand(id, 'set_file_edited_mode("'+ file_id.replace(reg1, '\\\\').replace(reg2, '\\"') +'", '+ to +')'); + }, + + + // allow to access to editarea functions and datas (for advanced users only) + execCommand : function(id, cmd, fct_param){ + switch(cmd){ + case "EA_init": + if(editAreas[id]['settings']["EA_init_callback"].length>0) + eval(editAreas[id]['settings']["EA_init_callback"]+"('"+ id +"');"); + break; + case "EA_delete": + if(editAreas[id]['settings']["EA_delete_callback"].length>0) + eval(editAreas[id]['settings']["EA_delete_callback"]+"('"+ id +"');"); + break; + case "EA_submit": + if(editAreas[id]['settings']["submit_callback"].length>0) + eval(editAreas[id]['settings']["submit_callback"]+"('"+ id +"');"); + break; + } + if(window.frames["frame_"+id] && window.frames["frame_"+ id].editArea){ + if(fct_param!=undefined) + return eval('window.frames["frame_'+ id +'"].editArea.'+ cmd +'(fct_param);'); + else + return eval('window.frames["frame_'+ id +'"].editArea.'+ cmd +';'); + } + return false; + } +}; + + var editAreaLoader= new EditAreaLoader(); + var editAreas= {}; + ADDED applications/admin/static/edit_area/elements_functions.js Index: applications/admin/static/edit_area/elements_functions.js ================================================================== --- applications/admin/static/edit_area/elements_functions.js +++ applications/admin/static/edit_area/elements_functions.js @@ -0,0 +1,336 @@ +/**** + * This page contains some general usefull functions for javascript + * + ****/ + + + // need to redefine this functiondue to IE problem + function getAttribute( elm, aName ) { + var aValue,taName,i; + try{ + aValue = elm.getAttribute( aName ); + }catch(exept){} + + if( ! aValue ){ + for( i = 0; i < elm.attributes.length; i ++ ) { + taName = elm.attributes[i] .name.toLowerCase(); + if( taName == aName ) { + aValue = elm.attributes[i] .value; + return aValue; + } + } + } + return aValue; + }; + + // need to redefine this function due to IE problem + function setAttribute( elm, attr, val ) { + if(attr=="class"){ + elm.setAttribute("className", val); + elm.setAttribute("class", val); + }else{ + elm.setAttribute(attr, val); + } + }; + + /* return a child element + elem: element we are searching in + elem_type: type of the eleemnt we are searching (DIV, A, etc...) + elem_attribute: attribute of the searched element that must match + elem_attribute_match: value that elem_attribute must match + option: "all" if must return an array of all children, otherwise return the first match element + depth: depth of search (-1 or no set => unlimited) + */ + function getChildren(elem, elem_type, elem_attribute, elem_attribute_match, option, depth) + { + if(!option) + var option="single"; + if(!depth) + var depth=-1; + if(elem){ + var children= elem.childNodes; + var result=null; + var results= []; + for (var x=0;x0){ + results= results.concat(result); + } + }else if(result!=null){ + return result; + } + } + } + } + if(option=="all") + return results; + } + return null; + }; + + function isChildOf(elem, parent){ + if(elem){ + if(elem==parent) + return true; + while(elem.parentNode != 'undefined'){ + return isChildOf(elem.parentNode, parent); + } + } + return false; + }; + + function getMouseX(e){ + + if(e!=null && typeof(e.pageX)!="undefined"){ + return e.pageX; + }else{ + return (e!=null?e.x:event.x)+ document.documentElement.scrollLeft; + } + }; + + function getMouseY(e){ + if(e!=null && typeof(e.pageY)!="undefined"){ + return e.pageY; + }else{ + return (e!=null?e.y:event.y)+ document.documentElement.scrollTop; + } + }; + + function calculeOffsetLeft(r){ + return calculeOffset(r,"offsetLeft") + }; + + function calculeOffsetTop(r){ + return calculeOffset(r,"offsetTop") + }; + + function calculeOffset(element,attr){ + var offset=0; + while(element){ + offset+=element[attr]; + element=element.offsetParent + } + return offset; + }; + + /** return the computed style + * @param: elem: the reference to the element + * @param: prop: the name of the css property + */ + function get_css_property(elem, prop) + { + if(document.defaultView) + { + return document.defaultView.getComputedStyle(elem, null).getPropertyValue(prop); + } + else if(elem.currentStyle) + { + var prop = prop.replace(/-\D/gi, function(sMatch) + { + return sMatch.charAt(sMatch.length - 1).toUpperCase(); + }); + return elem.currentStyle[prop]; + } + else return null; + } + +/**** + * Moving an element + ***/ + + var _mCE; // currently moving element + + /* allow to move an element in a window + e: the event + id: the id of the element + frame: the frame of the element + ex of use: + in html: + or + in javascript: document.getElementById("my_div").onmousedown= start_move_element + */ + function start_move_element(e, id, frame){ + var elem_id=(e.target || e.srcElement).id; + if(id) + elem_id=id; + if(!frame) + frame=window; + if(frame.event) + e=frame.event; + + _mCE= frame.document.getElementById(elem_id); + _mCE.frame=frame; + frame.document.onmousemove= move_element; + frame.document.onmouseup= end_move_element; + /*_mCE.onmousemove= move_element; + _mCE.onmouseup= end_move_element;*/ + + //alert(_mCE.frame.document.body.offsetHeight); + + mouse_x= getMouseX(e); + mouse_y= getMouseY(e); + //window.status=frame+ " elem: "+elem_id+" elem: "+ _mCE + " mouse_x: "+mouse_x; + _mCE.start_pos_x = mouse_x - (_mCE.style.left.replace("px","") || calculeOffsetLeft(_mCE)); + _mCE.start_pos_y = mouse_y - (_mCE.style.top.replace("px","") || calculeOffsetTop(_mCE)); + return false; + }; + + function end_move_element(e){ + _mCE.frame.document.onmousemove= ""; + _mCE.frame.document.onmouseup= ""; + _mCE=null; + }; + + function move_element(e){ + var newTop,newLeft,maxLeft; + + if( _mCE.frame && _mCE.frame.event ) + e=_mCE.frame.event; + newTop = getMouseY(e) - _mCE.start_pos_y; + newLeft = getMouseX(e) - _mCE.start_pos_x; + + maxLeft = _mCE.frame.document.body.offsetWidth- _mCE.offsetWidth; + max_top = _mCE.frame.document.body.offsetHeight- _mCE.offsetHeight; + newTop = Math.min(Math.max(0, newTop), max_top); + newLeft = Math.min(Math.max(0, newLeft), maxLeft); + + _mCE.style.top = newTop+"px"; + _mCE.style.left = newLeft+"px"; + return false; + }; + +/*** + * Managing a textarea (this part need the navigator infos from editAreaLoader + ***/ + + var nav= editAreaLoader.nav; + + // allow to get infos on the selection: array(start, end) + function getSelectionRange(textarea){ + return {"start": textarea.selectionStart, "end": textarea.selectionEnd}; + }; + + // allow to set the selection + function setSelectionRange(t, start, end){ + t.focus(); + + start = Math.max(0, Math.min(t.value.length, start)); + end = Math.max(start, Math.min(t.value.length, end)); + + if( nav.isOpera && nav.isOpera < 9.6 ){ // Opera bug when moving selection start and selection end + t.selectionEnd = 1; + t.selectionStart = 0; + t.selectionEnd = 1; + t.selectionStart = 0; + } + t.selectionStart = start; + t.selectionEnd = end; + //textarea.setSelectionRange(start, end); + + if(nav.isIE) + set_IE_selection(t); + }; + + + // set IE position in Firefox mode (textarea.selectionStart and textarea.selectionEnd). should work as a repeated task + function get_IE_selection(t){ + var d=document,div,range,stored_range,elem,scrollTop,relative_top,line_start,line_nb,range_start,range_end,tab; + if(t && t.focused) + { + if(!t.ea_line_height) + { // calculate the lineHeight + div= d.createElement("div"); + div.style.fontFamily= get_css_property(t, "font-family"); + div.style.fontSize= get_css_property(t, "font-size"); + div.style.visibility= "hidden"; + div.innerHTML="0"; + d.body.appendChild(div); + t.ea_line_height= div.offsetHeight; + d.body.removeChild(div); + } + //t.focus(); + range = d.selection.createRange(); + try + { + stored_range = range.duplicate(); + stored_range.moveToElementText( t ); + stored_range.setEndPoint( 'EndToEnd', range ); + if(stored_range.parentElement() == t){ + // the range don't take care of empty lines in the end of the selection + elem = t; + scrollTop = 0; + while(elem.parentNode){ + scrollTop+= elem.scrollTop; + elem = elem.parentNode; + } + + // var scrollTop= t.scrollTop + document.body.scrollTop; + + // var relative_top= range.offsetTop - calculeOffsetTop(t) + scrollTop; + relative_top= range.offsetTop - calculeOffsetTop(t)+ scrollTop; + // alert("rangeoffset: "+ range.offsetTop +"\ncalcoffsetTop: "+ calculeOffsetTop(t) +"\nrelativeTop: "+ relative_top); + line_start = Math.round((relative_top / t.ea_line_height) +1); + + line_nb = Math.round(range.boundingHeight / t.ea_line_height); + + range_start = stored_range.text.length - range.text.length; + tab = t.value.substr(0, range_start).split("\n"); + range_start += (line_start - tab.length)*2; // add missing empty lines to the selection + t.selectionStart = range_start; + + range_end = t.selectionStart + range.text.length; + tab = t.value.substr(0, range_start + range.text.length).split("\n"); + range_end += (line_start + line_nb - 1 - tab.length)*2; + t.selectionEnd = range_end; + } + } + catch(e){} + } + if( t && t.id ) + { + setTimeout("get_IE_selection(document.getElementById('"+ t.id +"'));", 50); + } + }; + + function IE_textarea_focus(){ + event.srcElement.focused= true; + } + + function IE_textarea_blur(){ + event.srcElement.focused= false; + } + + // select the text for IE (take into account the \r difference) + function set_IE_selection( t ){ + var nbLineStart,nbLineStart,nbLineEnd,range; + if(!window.closed){ + nbLineStart=t.value.substr(0, t.selectionStart).split("\n").length - 1; + nbLineEnd=t.value.substr(0, t.selectionEnd).split("\n").length - 1; + try + { + range = document.selection.createRange(); + range.moveToElementText( t ); + range.setEndPoint( 'EndToStart', range ); + range.moveStart('character', t.selectionStart - nbLineStart); + range.moveEnd('character', t.selectionEnd - nbLineEnd - (t.selectionStart - nbLineStart) ); + range.select(); + } + catch(e){} + } + }; + + + editAreaLoader.waiting_loading["elements_functions.js"]= "loaded"; ADDED applications/admin/static/edit_area/highlight.js Index: applications/admin/static/edit_area/highlight.js ================================================================== --- applications/admin/static/edit_area/highlight.js +++ applications/admin/static/edit_area/highlight.js @@ -0,0 +1,407 @@ + // change_to: "on" or "off" + EditArea.prototype.change_highlight= function(change_to){ + if(this.settings["syntax"].length==0 && change_to==false){ + this.switchClassSticky(_$("highlight"), 'editAreaButtonDisabled', true); + this.switchClassSticky(_$("reset_highlight"), 'editAreaButtonDisabled', true); + return false; + } + + if(this.do_highlight==change_to) + return false; + + + this.getIESelection(); + var pos_start= this.textarea.selectionStart; + var pos_end= this.textarea.selectionEnd; + + if(this.do_highlight===true || change_to==false) + this.disable_highlight(); + else + this.enable_highlight(); + this.textarea.focus(); + this.textarea.selectionStart = pos_start; + this.textarea.selectionEnd = pos_end; + this.setIESelection(); + + }; + + EditArea.prototype.disable_highlight= function(displayOnly){ + var t= this, a=t.textarea, new_Obj, old_class, new_class; + + t.selection_field.innerHTML=""; + t.selection_field_text.innerHTML=""; + t.content_highlight.style.visibility="hidden"; + // replacing the node is far more faster than deleting it's content in firefox + new_Obj= t.content_highlight.cloneNode(false); + new_Obj.innerHTML= ""; + t.content_highlight.parentNode.insertBefore(new_Obj, t.content_highlight); + t.content_highlight.parentNode.removeChild(t.content_highlight); + t.content_highlight= new_Obj; + old_class= parent.getAttribute( a,"class" ); + if(old_class){ + new_class= old_class.replace( "hidden","" ); + parent.setAttribute( a, "class", new_class ); + } + + a.style.backgroundColor="transparent"; // needed in order to see the bracket finders + + //var icon= document.getElementById("highlight"); + //setAttribute(icon, "class", getAttribute(icon, "class").replace(/ selected/g, "") ); + //t.restoreClass(icon); + //t.switchClass(icon,'editAreaButtonNormal'); + t.switchClassSticky(_$("highlight"), 'editAreaButtonNormal', true); + t.switchClassSticky(_$("reset_highlight"), 'editAreaButtonDisabled', true); + + t.do_highlight=false; + + t.switchClassSticky(_$("change_smooth_selection"), 'editAreaButtonSelected', true); + if(typeof(t.smooth_selection_before_highlight)!="undefined" && t.smooth_selection_before_highlight===false){ + t.change_smooth_selection_mode(false); + } + + // this.textarea.style.backgroundColor="#FFFFFF"; + }; + + EditArea.prototype.enable_highlight= function(){ + var t=this, a=t.textarea, new_class; + t.show_waiting_screen(); + + t.content_highlight.style.visibility="visible"; + new_class =parent.getAttribute(a,"class")+" hidden"; + parent.setAttribute( a, "class", new_class ); + + // IE can't manage mouse click outside text range without this + if( t.isIE ) + a.style.backgroundColor="#FFFFFF"; + + t.switchClassSticky(_$("highlight"), 'editAreaButtonSelected', false); + t.switchClassSticky(_$("reset_highlight"), 'editAreaButtonNormal', false); + + t.smooth_selection_before_highlight=t.smooth_selection; + if(!t.smooth_selection) + t.change_smooth_selection_mode(true); + t.switchClassSticky(_$("change_smooth_selection"), 'editAreaButtonDisabled', true); + + + t.do_highlight=true; + t.resync_highlight(); + + t.hide_waiting_screen(); + }; + + /** + * Ask to update highlighted text + * @param Array infos - Array of datas returned by EditArea.get_selection_infos() + */ + EditArea.prototype.maj_highlight= function(infos){ + // for speed mesure + var debug_opti="",tps_start= new Date().getTime(), tps_middle_opti=new Date().getTime(); + var t=this, hightlighted_text, updated_highlight; + var textToHighlight=infos["full_text"], doSyntaxOpti = false, doHtmlOpti = false, stay_begin="", stay_end="", trace_new , trace_last; + + if(t.last_text_to_highlight==infos["full_text"] && t.resync_highlight!==true) + return; + + // OPTIMISATION: will search to update only changed lines + if(t.reload_highlight===true){ + t.reload_highlight=false; + }else if(textToHighlight.length==0){ + textToHighlight="\n "; + }else{ + // get text change datas + changes = t.checkTextEvolution(t.last_text_to_highlight,textToHighlight); + + // check if it can only reparse the changed text + trace_new = t.get_syntax_trace(changes.newTextLine).replace(/\r/g, ''); + trace_last = t.get_syntax_trace(changes.lastTextLine).replace(/\r/g, ''); + doSyntaxOpti = ( trace_new == trace_last ); + + // check if the difference comes only from a new line created + // => we have to remember that the editor can automaticaly add tabulation or space after the new line) + if( !doSyntaxOpti && trace_new == "\n"+trace_last && /^[ \t\s]*\n[ \t\s]*$/.test( changes.newText.replace(/\r/g, '') ) && changes.lastText =="" ) + { + doSyntaxOpti = true; + } + + // we do the syntax optimisation + if( doSyntaxOpti ){ + + tps_middle_opti=new Date().getTime(); + + stay_begin= t.last_hightlighted_text.split("\n").slice(0, changes.lineStart).join("\n"); + if(changes.lineStart>0) + stay_begin+= "\n"; + stay_end= t.last_hightlighted_text.split("\n").slice(changes.lineLastEnd+1).join("\n"); + if(stay_end.length>0) + stay_end= "\n"+stay_end; + + // Final check to see that we're not in the middle of span tags + if( stay_begin.split(' trace: "+trace_new + +"\nchanged_last_text: "+ch.lastText+" => trace: "+trace_last + //debug_opti+= "\nchanged: "+ infos["full_text"].substring(ch.posStart, ch.posNewEnd); + + "\nchanged_line: "+ch.newTextLine + + "\nlast_changed_line: "+ch.lastTextLine + +"\nstay_begin: "+ stay_begin.slice(-100) + +"\nstay_end: "+ stay_end.substr( 0, 100 ); + //debug_opti="start: "+stay_begin_len+ "("+nb_line_start_unchanged+") end: "+ (stay_end_len)+ "("+(splited.length-nb_line_end_unchanged)+") "; + //debug_opti+="changed: "+ textToHighlight.substring(stay_begin_len, textToHighlight.length-stay_end_len)+" \n"; + + //debug_opti+="changed: "+ stay_begin.substr(stay_begin.length-200)+ "----------"+ textToHighlight+"------------------"+ stay_end.substr(0,200) +"\n"; + +"\n"; + } + + + // END OPTIMISATION + } + + tps_end_opti = new Date().getTime(); + + // apply highlight + updated_highlight = t.colorize_text(textToHighlight); + tpsAfterReg = new Date().getTime(); + + /*** + * see if we can optimize for updating only the required part of the HTML code + * + * The goal here will be to find the text node concerned by the modification and to update it + */ + //------------------------------------------- + + // disable latest optimization tricks (introduced in 0.8.1 and removed in 0.8.2), TODO: check for another try later + doSyntaxOpti = doHtmlOpti = false; + if( doSyntaxOpti ) + { + try + { + var replacedBloc, i, nbStart = '', nbEnd = '', newHtml, lengthOld, lengthNew; + replacedBloc = t.last_hightlighted_text.substring( stay_begin.length, t.last_hightlighted_text.length - stay_end.length ); + + lengthOld = replacedBloc.length; + lengthNew = updated_highlight.length; + + // find the identical caracters at the beginning + for( i=0; i < lengthOld && i < lengthNew && replacedBloc.charAt(i) == updated_highlight.charAt(i) ; i++ ) + { + } + nbStart = i; + // find the identical caracters at the end + for( i=0; i + nbStart < lengthOld && i + nbStart < lengthNew && replacedBloc.charAt(lengthOld-i-1) == updated_highlight.charAt(lengthNew-i-1) ; i++ ) + { + } + nbEnd = i; + //console.log( nbStart, nbEnd, replacedBloc, updated_highlight ); + // get the changes + lastHtml = replacedBloc.substring( nbStart, lengthOld - nbEnd ); + newHtml = updated_highlight.substring( nbStart, lengthNew - nbEnd ); + + // We can do the optimisation only if we havn't touch to span elements + if( newHtml.indexOf('').replace( /&/g, '&'); + + nbOpendedSpan = beginStr.split(' 0 ) + { + nbClosed--; + parentSpan = parentSpan.parentNode; + } + + // find the position of the last opended tag + while( parentSpan.parentNode != t.content_highlight && parentSpan.parentNode.tagName != 'PRE' && ( tmpMaxStartOffset = Math.max( 0, beginStr.lastIndexOf( '', maxStartOffset ) ); + + // count the number of sub spans + nbSubSpanBefore = beginStr.substr( lastEndPos ).split('' ) ) == -1 ? beginStr.length : beginStr.length - ( lastIndex + 1 ); + //nbUnchangedChars = ? beginStr.length : beginStr.substr( lastIndex + 1 ).replace( /</g, '<').replace( />/g, '>').replace( /&/g, '&').length; + + if( ( lastIndex = beginStr.lastIndexOf( '>' ) ) == -1 ) + { + nbUnchangedChars = beginStr.length; + } + else + { + nbUnchangedChars = beginStr.substr( lastIndex + 1 ).replace( /</g, '<').replace( />/g, '>').replace( /&/g, '&').length; + //nbUnchangedChars += beginStr.substr( ).replace( /&/g, '&').replace( //g, '>').length - beginStr.length; + } + //alert( nbUnchangedChars ); + // console.log( span, textNode, nbOpendedSpan,nbClosedSpan, span.nextSibling, textNode.length, nbUnchangedChars, lastHtml, lastHtml.length, newHtml, newHtml.length ); + // alert( textNode.parentNode.className +'-'+ textNode.parentNode.tagName+"\n"+ textNode.data +"\n"+ nbUnchangedChars +"\n"+ lastHtml.length +"\n"+ newHtml +"\n"+ newHtml.length ); + // console.log( nbUnchangedChars, lastIndex, beginStr.length, beginStr.replace(/&/g, '&'), lastHtml.length, '|', newHtml.replace( /\t/g, 't').replace( /\n/g, 'n').replace( /\r/g, 'r'), lastHtml.replace( /\t/g, 't').replace( /\n/g, 'n').replace( /\r/, 'r') ); + // console.log( textNode.data.replace(/&/g, '&') ); + // IE only manage \r for cariage return in textNode and not \n or \r\n + if( t.isIE ) + { + nbUnchangedChars -= ( beginStr.substr( beginStr.length - nbUnchangedChars ).split("\n").length - 1 ); + //alert( textNode.data.replace(/\r/g, '_r').replace(/\n/g, '_n')); + textNode.replaceData( nbUnchangedChars, lastHtml.replace(/\n/g, '').length, newHtml.replace(/\n/g, '') ); + } + else + { + textNode.replaceData( nbUnchangedChars, lastHtml.length, newHtml ); + } + //--------] + } + } + // an exception shouldn't occured but if replaceData failed at least it won't break everything + catch( e ) + { + // throw e; + // console.log( e ); + doHtmlOpti = false; + } + + } + + /*** END HTML update's optimisation ***/ + // end test + + // console.log( (TPS6-TPS5), (TPS5-TPS4), (TPS4-TPS3), (TPS3-TPS2), (TPS2-TPS1), _CPT ); + // get the new highlight content + tpsAfterOpti2 = new Date().getTime(); + hightlighted_text = stay_begin + updated_highlight + stay_end; + if( !doHtmlOpti ) + { + // update the content of the highlight div by first updating a clone node (as there is no display in the same time for t node it's quite faster (5*)) + var new_Obj= t.content_highlight.cloneNode(false); + if( ( t.isIE && t.isIE < 8 ) || ( t.isOpera && t.isOpera < 9.6 ) ) + new_Obj.innerHTML= "
" + hightlighted_text + "
"; + else + new_Obj.innerHTML= ""+ hightlighted_text +""; + + t.content_highlight.parentNode.replaceChild(new_Obj, t.content_highlight); + + t.content_highlight= new_Obj; + } + + t.last_text_to_highlight= infos["full_text"]; + t.last_hightlighted_text= hightlighted_text; + + tps3=new Date().getTime(); + + if(t.settings["debug"]){ + //lineNumber=tab_text.length; + //t.debug.value+=" \nNB char: "+_$("src").value.length+" Nb line: "+ lineNumber; + + t.debug.value= "Tps optimisation "+(tps_end_opti-tps_start) + +" | tps reg exp: "+ (tpsAfterReg-tps_end_opti) + +" | tps opti HTML : "+ (tpsAfterOpti2-tpsAfterReg) + ' '+ ( doHtmlOpti ? 'yes' : 'no' ) + +" | tps update highlight content: "+ (tps3-tpsAfterOpti2) + +" | tpsTotal: "+ (tps3-tps_start) + + "("+tps3+")\n"+ debug_opti; + // t.debug.value+= "highlight\n"+hightlighted_text;*/ + } + + }; + + EditArea.prototype.resync_highlight= function(reload_now){ + this.reload_highlight=true; + this.last_text_to_highlight=""; + this.focus(); + if(reload_now) + this.check_line_selection(false); + }; ADDED applications/admin/static/edit_area/images/autocompletion.gif Index: applications/admin/static/edit_area/images/autocompletion.gif ================================================================== --- applications/admin/static/edit_area/images/autocompletion.gif +++ applications/admin/static/edit_area/images/autocompletion.gif cannot compute difference between binary files ADDED applications/admin/static/edit_area/images/close.gif Index: applications/admin/static/edit_area/images/close.gif ================================================================== --- applications/admin/static/edit_area/images/close.gif +++ applications/admin/static/edit_area/images/close.gif cannot compute difference between binary files ADDED applications/admin/static/edit_area/images/fullscreen.gif Index: applications/admin/static/edit_area/images/fullscreen.gif ================================================================== --- applications/admin/static/edit_area/images/fullscreen.gif +++ applications/admin/static/edit_area/images/fullscreen.gif cannot compute difference between binary files ADDED applications/admin/static/edit_area/images/go_to_line.gif Index: applications/admin/static/edit_area/images/go_to_line.gif ================================================================== --- applications/admin/static/edit_area/images/go_to_line.gif +++ applications/admin/static/edit_area/images/go_to_line.gif cannot compute difference between binary files ADDED applications/admin/static/edit_area/images/help.gif Index: applications/admin/static/edit_area/images/help.gif ================================================================== --- applications/admin/static/edit_area/images/help.gif +++ applications/admin/static/edit_area/images/help.gif cannot compute difference between binary files ADDED applications/admin/static/edit_area/images/highlight.gif Index: applications/admin/static/edit_area/images/highlight.gif ================================================================== --- applications/admin/static/edit_area/images/highlight.gif +++ applications/admin/static/edit_area/images/highlight.gif cannot compute difference between binary files ADDED applications/admin/static/edit_area/images/load.gif Index: applications/admin/static/edit_area/images/load.gif ================================================================== --- applications/admin/static/edit_area/images/load.gif +++ applications/admin/static/edit_area/images/load.gif cannot compute difference between binary files ADDED applications/admin/static/edit_area/images/move.gif Index: applications/admin/static/edit_area/images/move.gif ================================================================== --- applications/admin/static/edit_area/images/move.gif +++ applications/admin/static/edit_area/images/move.gif cannot compute difference between binary files ADDED applications/admin/static/edit_area/images/newdocument.gif Index: applications/admin/static/edit_area/images/newdocument.gif ================================================================== --- applications/admin/static/edit_area/images/newdocument.gif +++ applications/admin/static/edit_area/images/newdocument.gif cannot compute difference between binary files ADDED applications/admin/static/edit_area/images/opacity.png Index: applications/admin/static/edit_area/images/opacity.png ================================================================== --- applications/admin/static/edit_area/images/opacity.png +++ applications/admin/static/edit_area/images/opacity.png cannot compute difference between binary files ADDED applications/admin/static/edit_area/images/processing.gif Index: applications/admin/static/edit_area/images/processing.gif ================================================================== --- applications/admin/static/edit_area/images/processing.gif +++ applications/admin/static/edit_area/images/processing.gif cannot compute difference between binary files ADDED applications/admin/static/edit_area/images/redo.gif Index: applications/admin/static/edit_area/images/redo.gif ================================================================== --- applications/admin/static/edit_area/images/redo.gif +++ applications/admin/static/edit_area/images/redo.gif cannot compute difference between binary files ADDED applications/admin/static/edit_area/images/reset_highlight.gif Index: applications/admin/static/edit_area/images/reset_highlight.gif ================================================================== --- applications/admin/static/edit_area/images/reset_highlight.gif +++ applications/admin/static/edit_area/images/reset_highlight.gif cannot compute difference between binary files ADDED applications/admin/static/edit_area/images/save.gif Index: applications/admin/static/edit_area/images/save.gif ================================================================== --- applications/admin/static/edit_area/images/save.gif +++ applications/admin/static/edit_area/images/save.gif cannot compute difference between binary files ADDED applications/admin/static/edit_area/images/search.gif Index: applications/admin/static/edit_area/images/search.gif ================================================================== --- applications/admin/static/edit_area/images/search.gif +++ applications/admin/static/edit_area/images/search.gif cannot compute difference between binary files ADDED applications/admin/static/edit_area/images/smooth_selection.gif Index: applications/admin/static/edit_area/images/smooth_selection.gif ================================================================== --- applications/admin/static/edit_area/images/smooth_selection.gif +++ applications/admin/static/edit_area/images/smooth_selection.gif cannot compute difference between binary files ADDED applications/admin/static/edit_area/images/spacer.gif Index: applications/admin/static/edit_area/images/spacer.gif ================================================================== --- applications/admin/static/edit_area/images/spacer.gif +++ applications/admin/static/edit_area/images/spacer.gif cannot compute difference between binary files ADDED applications/admin/static/edit_area/images/statusbar_resize.gif Index: applications/admin/static/edit_area/images/statusbar_resize.gif ================================================================== --- applications/admin/static/edit_area/images/statusbar_resize.gif +++ applications/admin/static/edit_area/images/statusbar_resize.gif cannot compute difference between binary files ADDED applications/admin/static/edit_area/images/undo.gif Index: applications/admin/static/edit_area/images/undo.gif ================================================================== --- applications/admin/static/edit_area/images/undo.gif +++ applications/admin/static/edit_area/images/undo.gif cannot compute difference between binary files ADDED applications/admin/static/edit_area/images/word_wrap.gif Index: applications/admin/static/edit_area/images/word_wrap.gif ================================================================== --- applications/admin/static/edit_area/images/word_wrap.gif +++ applications/admin/static/edit_area/images/word_wrap.gif cannot compute difference between binary files ADDED applications/admin/static/edit_area/keyboard.js Index: applications/admin/static/edit_area/keyboard.js ================================================================== --- applications/admin/static/edit_area/keyboard.js +++ applications/admin/static/edit_area/keyboard.js @@ -0,0 +1,145 @@ +var EA_keys = {8:"Retour arriere",9:"Tabulation",12:"Milieu (pave numerique)",13:"Entrer",16:"Shift",17:"Ctrl",18:"Alt",19:"Pause",20:"Verr Maj",27:"Esc",32:"Space",33:"Page up",34:"Page down",35:"End",36:"Begin",37:"Left",38:"Up",39:"Right",40:"Down",44:"Impr ecran",45:"Inser",46:"Suppr",91:"Menu Demarrer Windows / touche pomme Mac",92:"Menu Demarrer Windows",93:"Menu contextuel Windows",112:"F1",113:"F2",114:"F3",115:"F4",116:"F5",117:"F6",118:"F7",119:"F8",120:"F9",121:"F10",122:"F11",123:"F12",144:"Verr Num",145:"Arret defil"}; + + + +function keyDown(e){ + if(!e){ // if IE + e=event; + } + + // send the event to the plugins + for(var i in editArea.plugins){ + if(typeof(editArea.plugins[i].onkeydown)=="function"){ + if(editArea.plugins[i].onkeydown(e)===false){ // stop propaging + if(editArea.isIE) + e.keyCode=0; + return false; + } + } + } + + var target_id=(e.target || e.srcElement).id; + var use=false; + if (EA_keys[e.keyCode]) + letter=EA_keys[e.keyCode]; + else + letter=String.fromCharCode(e.keyCode); + + var low_letter= letter.toLowerCase(); + + if(letter=="Page up" && !AltPressed(e) && !editArea.isOpera){ + editArea.execCommand("scroll_page", {"dir": "up", "shift": ShiftPressed(e)}); + use=true; + }else if(letter=="Page down" && !AltPressed(e) && !editArea.isOpera){ + editArea.execCommand("scroll_page", {"dir": "down", "shift": ShiftPressed(e)}); + use=true; + }else if(editArea.is_editable==false){ + // do nothing but also do nothing else (allow to navigate with page up and page down) + return true; + }else if(letter=="Tabulation" && target_id=="textarea" && !CtrlPressed(e) && !AltPressed(e)){ + if(ShiftPressed(e)) + editArea.execCommand("invert_tab_selection"); + else + editArea.execCommand("tab_selection"); + + use=true; + if(editArea.isOpera || (editArea.isFirefox && editArea.isMac) ) // opera && firefox mac can't cancel tabulation events... + setTimeout("editArea.execCommand('focus');", 1); + }else if(letter=="Entrer" && target_id=="textarea"){ + if(editArea.press_enter()) + use=true; + }else if(letter=="Entrer" && target_id=="area_search"){ + editArea.execCommand("area_search"); + use=true; + }else if(letter=="Esc"){ + editArea.execCommand("close_all_inline_popup", e); + use=true; + }else if(CtrlPressed(e) && !AltPressed(e) && !ShiftPressed(e)){ + switch(low_letter){ + case "f": + editArea.execCommand("area_search"); + use=true; + break; + case "r": + editArea.execCommand("area_replace"); + use=true; + break; + case "q": + editArea.execCommand("close_all_inline_popup", e); + use=true; + break; + case "h": + editArea.execCommand("change_highlight"); + use=true; + break; + case "g": + setTimeout("editArea.execCommand('go_to_line');", 5); // the prompt stop the return false otherwise + use=true; + break; + case "e": + editArea.execCommand("show_help"); + use=true; + break; + case "z": + use=true; + editArea.execCommand("undo"); + break; + case "y": + use=true; + editArea.execCommand("redo"); + break; + default: + break; + } + } + + // check to disable the redo possibility if the textarea content change + if(editArea.next.length > 0){ + setTimeout("editArea.check_redo();", 10); + } + + setTimeout("editArea.check_file_changes();", 10); + + + if(use){ + // in case of a control that sould'nt be used by IE but that is used => THROW a javascript error that will stop key action + if(editArea.isIE) + e.keyCode=0; + return false; + } + //alert("Test: "+ letter + " ("+e.keyCode+") ALT: "+ AltPressed(e) + " CTRL "+ CtrlPressed(e) + " SHIFT "+ ShiftPressed(e)); + + return true; + +}; + + +// return true if Alt key is pressed +function AltPressed(e) { + if (window.event) { + return (window.event.altKey); + } else { + if(e.modifiers) + return (e.altKey || (e.modifiers % 2)); + else + return e.altKey; + } +}; + +// return true if Ctrl key is pressed +function CtrlPressed(e) { + if (window.event) { + return (window.event.ctrlKey); + } else { + return (e.ctrlKey || (e.modifiers==2) || (e.modifiers==3) || (e.modifiers>5)); + } +}; + +// return true if Shift key is pressed +function ShiftPressed(e) { + if (window.event) { + return (window.event.shiftKey); + } else { + return (e.shiftKey || (e.modifiers>3)); + } +}; ADDED applications/admin/static/edit_area/langs/bg.js Index: applications/admin/static/edit_area/langs/bg.js ================================================================== --- applications/admin/static/edit_area/langs/bg.js +++ applications/admin/static/edit_area/langs/bg.js @@ -0,0 +1,54 @@ +/* + * Bulgarian translation + * Author: Valentin Hristov + * Company: SOFTKIT Bulgarian + * Site: http://www.softkit-bg.com + */ +editAreaLoader.lang["bg"]={ +new_document: "нов документ", +search_button: "търсене и замяна", +search_command: "търси следващия / отвори прозорец с търсачка", +search: "търсене", +replace: "замяна", +replace_command: "замяна / отвори прозорец с търсачка", +find_next: "намери следващия", +replace_all: "замени всички", +reg_exp: "реголярни изрази", +match_case: "чуствителен към регистъра", +not_found: "няма резултат.", +occurrence_replaced: "замяната е осъществена.", +search_field_empty: "Полето за търсене е празно", +restart_search_at_begin: "До края на документа. Почни с началото.", +move_popup: "премести прозореца с търсачката", +font_size: "--Размер на шрифта--", +go_to_line: "премени към реда", +go_to_line_prompt: "премени към номера на реда:", +undo: "отмени", +redo: "върни", +change_smooth_selection: "включи/изключи някой от функциите за преглед (по красиво, но повече натоварва)", +highlight: "превключване на оцветяване на синтаксиса включена/изключена", +reset_highlight: "въстанови оцветяване на синтаксиса (ако не е синхронизиран с текста)", +word_wrap: "режим на пренасяне на дълги редове", +help: "за програмата", +save: "съхрани", +load: "зареди", +line_abbr: "Стр", +char_abbr: "Стлб", +position: "Позиция", +total: "Всичко", +close_popup: "затвори прозореца", +shortcuts: "Бързи клавиши", +add_tab: "добави табулация в текста", +remove_tab: "премахни табулацията в текста", +about_notice: "Внимание: използвайте функцията оцветяване на синтаксиса само за малки текстове", +toggle: "Превключи редактор", +accesskey: "Бърз клавиш", +tab: "Tab", +shift: "Shift", +ctrl: "Ctrl", +esc: "Esc", +processing: "Зареждане...", +fullscreen: "на цял екран", +syntax_selection: "--Синтаксис--", +close_tab: "Затвори файла" +}; ADDED applications/admin/static/edit_area/langs/cs.js Index: applications/admin/static/edit_area/langs/cs.js ================================================================== --- applications/admin/static/edit_area/langs/cs.js +++ applications/admin/static/edit_area/langs/cs.js @@ -0,0 +1,48 @@ +editAreaLoader.lang["cs"]={ +new_document: "Nový dokument", +search_button: "Najdi a nahraď", +search_command: "Hledej další / otevři vyhledávací pole", +search: "Hledej", +replace: "Nahraď", +replace_command: "Nahraď / otevři vyhledávací pole", +find_next: "Najdi další", +replace_all: "Nahraď vše", +reg_exp: "platné výrazy", +match_case: "vyhodnocené výrazy", +not_found: "nenalezené.", +occurrence_replaced: "výskyty nahrazené.", +search_field_empty: "Pole vyhledávání je prázdné", +restart_search_at_begin: "Dosažen konec souboru, začínám od začátku.", +move_popup: "Přesuň vyhledávací okno", +font_size: "--Velikost textu--", +go_to_line: "Přejdi na řádek", +go_to_line_prompt: "Přejdi na řádek:", +undo: "krok zpět", +redo: "znovu", +change_smooth_selection: "Povolit nebo zakázat některé ze zobrazených funkcí (účelnější zobrazení požaduje větší zatížení procesoru)", +highlight: "Zvýrazňování syntaxe zap./vyp.", +reset_highlight: "Obnovit zvýraznění (v případě nesrovnalostí)", +word_wrap: "toggle word wrapping mode", +help: "O programu", +save: "Uložit", +load: "Otevřít", +line_abbr: "Ř.", +char_abbr: "S.", +position: "Pozice", +total: "Celkem", +close_popup: "Zavřít okno", +shortcuts: "Zkratky", +add_tab: "Přidat tabulování textu", +remove_tab: "Odtsranit tabulování textu", +about_notice: "Upozornění! Funkce zvýrazňování textu je k dispozici pouze pro malý text", +toggle: "Přepnout editor", +accesskey: "Přístupová klávesa", +tab: "Záložka", +shift: "Shift", +ctrl: "Ctrl", +esc: "Esc", +processing: "Zpracovávám ...", +fullscreen: "Celá obrazovka", +syntax_selection: "--vyber zvýrazňovač--", +close_tab: "Close file" +}; ADDED applications/admin/static/edit_area/langs/de.js Index: applications/admin/static/edit_area/langs/de.js ================================================================== --- applications/admin/static/edit_area/langs/de.js +++ applications/admin/static/edit_area/langs/de.js @@ -0,0 +1,48 @@ +editAreaLoader.lang["de"]={ +new_document: "Neues Dokument", +search_button: "Suchen und Ersetzen", +search_command: "Weitersuchen / öffne Suchfeld", +search: "Suchen", +replace: "Ersetzen", +replace_command: "Ersetzen / öffne Suchfeld", +find_next: "Weitersuchen", +replace_all: "Ersetze alle Treffer", +reg_exp: "reguläre Ausdrücke", +match_case: "passt auf den Begriff
", +not_found: "Nicht gefunden.", +occurrence_replaced: "Die Vorkommen wurden ersetzt.", +search_field_empty: "Leeres Suchfeld", +restart_search_at_begin: "Ende des zu durchsuchenden Bereiches erreicht. Es wird die Suche von Anfang an fortgesetzt.", //find a shorter translation +move_popup: "Suchfenster bewegen", +font_size: "--Schriftgröße--", +go_to_line: "Gehe zu Zeile", +go_to_line_prompt: "Gehe zu Zeilennummmer:", +undo: "Rückgängig", +redo: "Wiederherstellen", +change_smooth_selection: "Aktiviere/Deaktiviere einige Features (weniger Bildschirmnutzung aber mehr CPU-Belastung)", +highlight: "Syntax Highlighting an- und ausschalten", +reset_highlight: "Highlighting zurücksetzen (falls mit Text nicht konform)", +word_wrap: "Toggle word wrapping mode", +help: "Info", +save: "Speichern", +load: "Öffnen", +line_abbr: "Ln", +char_abbr: "Ch", +position: "Position", +total: "Gesamt", +close_popup: "Popup schließen", +shortcuts: "Shortcuts", +add_tab: "Tab zum Text hinzufügen", +remove_tab: "Tab aus Text entfernen", +about_notice: "Bemerkung: Syntax Highlighting ist nur für kurze Texte", +toggle: "Editor an- und ausschalten", +accesskey: "Accesskey", +tab: "Tab", +shift: "Shift", +ctrl: "Ctrl", +esc: "Esc", +processing: "In Bearbeitung...", +fullscreen: "Full-Screen", +syntax_selection: "--Syntax--", +close_tab: "Close file" +}; ADDED applications/admin/static/edit_area/langs/dk.js Index: applications/admin/static/edit_area/langs/dk.js ================================================================== --- applications/admin/static/edit_area/langs/dk.js +++ applications/admin/static/edit_area/langs/dk.js @@ -0,0 +1,48 @@ +editAreaLoader.lang["dk"]={ +new_document: "nyt tomt dokument", +search_button: "søg og erstat", +search_command: "find næste / åben søgefelt", +search: "søg", +replace: "erstat", +replace_command: "erstat / åben søgefelt", +find_next: "find næste", +replace_all: "erstat alle", +reg_exp: "regular expressions", +match_case: "forskel på store/små bogstaver
", +not_found: "not found.", +occurrence_replaced: "occurences replaced.", +search_field_empty: "Search field empty", +restart_search_at_begin: "End of area reached. Restart at begin.", +move_popup: "flyt søgepopup", +font_size: "--Skriftstørrelse--", +go_to_line: "gå til linie", +go_to_line_prompt: "gå til linienummer:", +undo: "fortryd", +redo: "gentag", +change_smooth_selection: "slå display funktioner til/fra (smartere display men mere CPU krævende)", +highlight: "slå syntax highlight til/fra", +reset_highlight: "nulstil highlight (hvis den er desynkroniseret fra teksten)", +word_wrap: "toggle word wrapping mode", +help: "om", +save: "gem", +load: "hent", +line_abbr: "Ln", +char_abbr: "Ch", +position: "Position", +total: "Total", +close_popup: "luk popup", +shortcuts: "Genveje", +add_tab: "tilføj tabulation til tekst", +remove_tab: "fjern tabulation fra tekst", +about_notice: "Husk: syntax highlight funktionen bør kun bruge til små tekster", +toggle: "Slå editor til / fra", +accesskey: "Accesskey", +tab: "Tab", +shift: "Skift", +ctrl: "Ctrl", +esc: "Esc", +processing: "Processing...", +fullscreen: "fullscreen", +syntax_selection: "--Syntax--", +close_tab: "Close file" +}; ADDED applications/admin/static/edit_area/langs/en.js Index: applications/admin/static/edit_area/langs/en.js ================================================================== --- applications/admin/static/edit_area/langs/en.js +++ applications/admin/static/edit_area/langs/en.js @@ -0,0 +1,48 @@ +editAreaLoader.lang["en"]={ +new_document: "new empty document", +search_button: "search and replace", +search_command: "search next / open search area", +search: "search", +replace: "replace", +replace_command: "replace / open search area", +find_next: "find next", +replace_all: "replace all", +reg_exp: "regular expressions", +match_case: "match case", +not_found: "not found.", +occurrence_replaced: "occurences replaced.", +search_field_empty: "Search field empty", +restart_search_at_begin: "End of area reached. Restart at begin.", +move_popup: "move search popup", +font_size: "--Font size--", +go_to_line: "go to line", +go_to_line_prompt: "go to line number:", +undo: "undo", +redo: "redo", +change_smooth_selection: "enable/disable some display features (smarter display but more CPU charge)", +highlight: "toggle syntax highlight on/off", +reset_highlight: "reset highlight (if desyncronized from text)", +word_wrap: "toggle word wrapping mode", +help: "about", +save: "save", +load: "load", +line_abbr: "Ln", +char_abbr: "Ch", +position: "Position", +total: "Total", +close_popup: "close popup", +shortcuts: "Shortcuts", +add_tab: "add tabulation to text", +remove_tab: "remove tabulation to text", +about_notice: "Notice: syntax highlight function is only for small text", +toggle: "Toggle editor", +accesskey: "Accesskey", +tab: "Tab", +shift: "Shift", +ctrl: "Ctrl", +esc: "Esc", +processing: "Processing...", +fullscreen: "fullscreen", +syntax_selection: "--Syntax--", +close_tab: "Close file" +}; ADDED applications/admin/static/edit_area/langs/eo.js Index: applications/admin/static/edit_area/langs/eo.js ================================================================== --- applications/admin/static/edit_area/langs/eo.js +++ applications/admin/static/edit_area/langs/eo.js @@ -0,0 +1,48 @@ +editAreaLoader.lang["eo"]={ +new_document: "nova dokumento (vakigas la enhavon)", +search_button: "serĉi / anstataŭigi", +search_command: "pluserĉi / malfermi la serĉo-fenestron", +search: "serĉi", +replace: "anstataŭigi", +replace_command: "anstataŭigi / malfermi la serĉo-fenestron", +find_next: "serĉi", +replace_all: "anstataŭigi ĉion", +reg_exp: "regula esprimo", +match_case: "respekti la usklecon", +not_found: "ne trovita.", +occurrence_replaced: "anstataŭigoj plenumitaj.", +search_field_empty: "La kampo estas malplena.", +restart_search_at_begin: "Fino de teksto ĝisrirata, ĉu daŭrigi el la komenco?", +move_popup: "movi la serĉo-fenestron", +font_size: "--Tipara grando--", +go_to_line: "iri al la linio", +go_to_line_prompt: "iri al la linio numero:", +undo: "rezigni", +redo: "refari", +change_smooth_selection: "ebligi/malebligi la funkcioj de vidigo (pli bona vidigo, sed pli da ŝarĝo de la ĉeforgano)", +highlight: "ebligi/malebligi la sintaksan kolorigon", +reset_highlight: "repravalorizi la sintaksan kolorigon (se malsinkronigon de la teksto)", +word_wrap: "toggle word wrapping mode", +help: "pri", +save: "registri", +load: "ŝarĝi", +line_abbr: "Ln", +char_abbr: "Sg", +position: "Pozicio", +total: "Sumo", +close_popup: "fermi la ŝprucfenestron", +shortcuts: "Fulmoklavo", +add_tab: "aldoni tabon en la tekston", +remove_tab: "forigi tablon el la teksto", +about_notice: "Noto: la sintaksa kolorigo estas nur prikalkulita por mallongaj tekstoj.", +toggle: "baskuligi la redaktilon", +accesskey: "Fulmoklavo", +tab: "Tab", +shift: "Maj", +ctrl: "Ktrl", +esc: "Esk", +processing: "ŝargante...", +fullscreen: "plenekrane", +syntax_selection: "--Sintakso--", +close_tab: "Fermi la dosieron" +}; ADDED applications/admin/static/edit_area/langs/es.js Index: applications/admin/static/edit_area/langs/es.js ================================================================== --- applications/admin/static/edit_area/langs/es.js +++ applications/admin/static/edit_area/langs/es.js @@ -0,0 +1,48 @@ +editAreaLoader.lang["es"]={ +new_document: "nuevo documento vacío", +search_button: "buscar y reemplazar", +search_command: "buscar siguiente / abrir área de búsqueda", +search: "buscar", +replace: "reemplazar", +replace_command: "reemplazar / abrir área de búsqueda", +find_next: "encontrar siguiente", +replace_all: "reemplazar todos", +reg_exp: "expresiones regulares", +match_case: "coincidir capitalización", +not_found: "no encontrado.", +occurrence_replaced: "ocurrencias reemplazadas.", +search_field_empty: "Campo de búsqueda vacío", +restart_search_at_begin: "Se ha llegado al final del área. Se va a seguir desde el principio.", +move_popup: "mover la ventana de búsqueda", +font_size: "--Tamaño de la fuente--", +go_to_line: "ir a la línea", +go_to_line_prompt: "ir a la línea número:", +undo: "deshacer", +redo: "rehacer", +change_smooth_selection: "activar/desactivar algunas características de visualización (visualización más inteligente pero más carga de CPU)", +highlight: "intercambiar resaltado de sintaxis", +reset_highlight: "reinicializar resaltado (si no esta sincronizado con el texto)", +word_wrap: "toggle word wrapping mode", +help: "acerca", +save: "guardar", +load: "cargar", +line_abbr: "Ln", +char_abbr: "Ch", +position: "Posición", +total: "Total", +close_popup: "recuadro de cierre", +shortcuts: "Atajos", +add_tab: "añadir tabulado al texto", +remove_tab: "borrar tabulado del texto", +about_notice: "Aviso: el resaltado de sintaxis sólo funciona para texto pequeño", +toggle: "Cambiar editor", +accesskey: "Tecla de acceso", +tab: "Tab", +shift: "Mayúsc", +ctrl: "Ctrl", +esc: "Esc", +processing: "Procesando...", +fullscreen: "pantalla completa", +syntax_selection: "--Syntax--", +close_tab: "Close file" +}; ADDED applications/admin/static/edit_area/langs/fi.js Index: applications/admin/static/edit_area/langs/fi.js ================================================================== --- applications/admin/static/edit_area/langs/fi.js +++ applications/admin/static/edit_area/langs/fi.js @@ -0,0 +1,48 @@ +editAreaLoader.lang["fi"]={ +new_document: "uusi tyhjä dokumentti", +search_button: "etsi ja korvaa", +search_command: "etsi seuraava / avaa etsintävalikko", +search: "etsi", +replace: "korvaa", +replace_command: "korvaa / avaa etsintävalikko", +find_next: "etsi seuraava", +replace_all: "korvaa kaikki", +reg_exp: "säännölliset lausekkeet", +match_case: "täsmää kirjainkokoon", +not_found: "ei löytynyt.", +occurrence_replaced: "esiintymää korvattu.", +search_field_empty: "Haettava merkkijono on tyhjä", +restart_search_at_begin: "Alueen loppu saavutettiin. Aloitetaan alusta.", +move_popup: "siirrä etsintävalikkoa", +font_size: "--Fontin koko--", +go_to_line: "siirry riville", +go_to_line_prompt: "mene riville:", +undo: "peruuta", +redo: "tee uudelleen", +change_smooth_selection: "kytke/sammuta joitakin näyttötoimintoja (Älykkäämpi toiminta, mutta suurempi CPU kuormitus)", +highlight: "kytke syntaksikorostus päälle/pois", +reset_highlight: "resetoi syntaksikorostus (jos teksti ei ole synkassa korostuksen kanssa)", +word_wrap: "toggle word wrapping mode", +help: "tietoja", +save: "tallenna", +load: "lataa", +line_abbr: "Rv", +char_abbr: "Pos", +position: "Paikka", +total: "Yhteensä", +close_popup: "sulje valikko", +shortcuts: "Pikatoiminnot", +add_tab: "lisää sisennys tekstiin", +remove_tab: "poista sisennys tekstistä", +about_notice: "Huomautus: syntaksinkorostus toimii vain pienelle tekstille", +toggle: "Kytke editori", +accesskey: "Pikanäppäin", +tab: "Tab", +shift: "Shift", +ctrl: "Ctrl", +esc: "Esc", +processing: "Odota...", +fullscreen: "koko ruutu", +syntax_selection: "--Syntaksi--", +close_tab: "Sulje tiedosto" +}; ADDED applications/admin/static/edit_area/langs/fr.js Index: applications/admin/static/edit_area/langs/fr.js ================================================================== --- applications/admin/static/edit_area/langs/fr.js +++ applications/admin/static/edit_area/langs/fr.js @@ -0,0 +1,48 @@ +editAreaLoader.lang["fr"]={ +new_document: "nouveau document (efface le contenu)", +search_button: "rechercher / remplacer", +search_command: "rechercher suivant / ouvrir la fenêtre de recherche", +search: "rechercher", +replace: "remplacer", +replace_command: "remplacer / ouvrir la fenêtre de recherche", +find_next: "rechercher", +replace_all: "tout remplacer", +reg_exp: "expr. régulière", +match_case: "respecter la casse", +not_found: "pas trouvé.", +occurrence_replaced: "remplacements éffectués.", +search_field_empty: "Le champ de recherche est vide.", +restart_search_at_begin: "Fin du texte atteint, poursuite au début.", +move_popup: "déplacer la fenêtre de recherche", +font_size: "--Taille police--", +go_to_line: "aller à la ligne", +go_to_line_prompt: "aller a la ligne numero:", +undo: "annuler", +redo: "refaire", +change_smooth_selection: "activer/désactiver des fonctions d'affichage (meilleur affichage mais plus de charge processeur)", +highlight: "activer/désactiver la coloration syntaxique", +reset_highlight: "réinitialiser la coloration syntaxique (si désyncronisée du texte)", +word_wrap: "activer/désactiver les retours à la ligne automatiques", +help: "à propos", +save: "sauvegarder", +load: "charger", +line_abbr: "Ln", +char_abbr: "Ch", +position: "Position", +total: "Total", +close_popup: "fermer le popup", +shortcuts: "Racourcis clavier", +add_tab: "ajouter une tabulation dans le texte", +remove_tab: "retirer une tabulation dans le texte", +about_notice: "Note: la coloration syntaxique n'est prévue que pour de courts textes.", +toggle: "basculer l'éditeur", +accesskey: "Accesskey", +tab: "Tab", +shift: "Maj", +ctrl: "Ctrl", +esc: "Esc", +processing: "chargement...", +fullscreen: "plein écran", +syntax_selection: "--Syntaxe--", +close_tab: "Fermer le fichier" +}; ADDED applications/admin/static/edit_area/langs/hr.js Index: applications/admin/static/edit_area/langs/hr.js ================================================================== --- applications/admin/static/edit_area/langs/hr.js +++ applications/admin/static/edit_area/langs/hr.js @@ -0,0 +1,48 @@ +editAreaLoader.lang["hr"]={ +new_document: "Novi dokument", +search_button: "Traži i izmijeni", +search_command: "Traži dalje / Otvori prozor za traženje", +search: "Traži", +replace: "Izmijeni", +replace_command: "Izmijeni / Otvori prozor za traženje", +find_next: "Traži dalje", +replace_all: "Izmjeni sve", +reg_exp: "Regularni izrazi", +match_case: "Bitna vel. slova", +not_found: "nije naðeno.", +occurrence_replaced: "izmjenjenih.", +search_field_empty: "Prazno polje za traženje!", +restart_search_at_begin: "Došao do kraja. Poèeo od poèetka.", +move_popup: "Pomakni prozor", +font_size: "--Velièina teksta--", +go_to_line: "Odi na redak", +go_to_line_prompt: "Odi na redak:", +undo: "Vrati natrag", +redo: "Napravi ponovo", +change_smooth_selection: "Ukljuèi/iskljuèi neke moguænosti prikaza (pametniji prikaz, ali zagušeniji CPU)", +highlight: "Ukljuèi/iskljuèi bojanje sintakse", +reset_highlight: "Ponovi kolorizaciju (ako je nesinkronizirana s tekstom)", +word_wrap: "toggle word wrapping mode", +help: "O edit_area", +save: "Spremi", +load: "Uèitaj", +line_abbr: "Ln", +char_abbr: "Zn", +position: "Pozicija", +total: "Ukupno", +close_popup: "Zatvori prozor", +shortcuts: "Kratice", +add_tab: "Dodaj tabulaciju", +remove_tab: "Makni tabulaciju", +about_notice: "Napomena: koloriziranje sintakse je samo za kratke kodove", +toggle: "Prebaci naèin ureðivanja", +accesskey: "Accesskey", +tab: "Tab", +shift: "Shift", +ctrl: "Ctrl", +esc: "Esc", +processing: "Procesiram...", +fullscreen: "Cijeli prozor", +syntax_selection: "--Syntax--", +close_tab: "Close file" +}; ADDED applications/admin/static/edit_area/langs/it.js Index: applications/admin/static/edit_area/langs/it.js ================================================================== --- applications/admin/static/edit_area/langs/it.js +++ applications/admin/static/edit_area/langs/it.js @@ -0,0 +1,48 @@ +editAreaLoader.lang["it"]={ +new_document: "nuovo documento vuoto", +search_button: "cerca e sostituisci", +search_command: "trova successivo / apri finestra di ricerca", +search: "cerca", +replace: "sostituisci", +replace_command: "sostituisci / apri finestra di ricerca", +find_next: "trova successivo", +replace_all: "sostituisci tutti", +reg_exp: "espressioni regolari", +match_case: "confronta maiuscole/minuscole
", +not_found: "non trovato.", +occurrence_replaced: "occorrenze sostituite.", +search_field_empty: "Campo ricerca vuoto", +restart_search_at_begin: "Fine del testo raggiunta. Ricomincio dall'inizio.", +move_popup: "sposta popup di ricerca", +font_size: "-- Dimensione --", +go_to_line: "vai alla linea", +go_to_line_prompt: "vai alla linea numero:", +undo: "annulla", +redo: "ripeti", +change_smooth_selection: "abilita/disabilita alcune caratteristiche della visualizzazione", +highlight: "abilita/disabilita colorazione della sintassi", +reset_highlight: "aggiorna colorazione (se non sincronizzata)", +word_wrap: "toggle word wrapping mode", +help: "informazioni su...", +save: "salva", +load: "carica", +line_abbr: "Ln", +char_abbr: "Ch", +position: "Posizione", +total: "Totale", +close_popup: "chiudi popup", +shortcuts: "Scorciatoie", +add_tab: "aggiungi tabulazione", +remove_tab: "rimuovi tabulazione", +about_notice: "Avviso: la colorazione della sintassi vale solo con testo piccolo", +toggle: "Abilita/disabilita editor", +accesskey: "Accesskey", +tab: "Tab", +shift: "Shift", +ctrl: "Ctrl", +esc: "Esc", +processing: "In corso...", +fullscreen: "fullscreen", +syntax_selection: "--Syntax--", +close_tab: "Close file" +}; ADDED applications/admin/static/edit_area/langs/ja.js Index: applications/admin/static/edit_area/langs/ja.js ================================================================== --- applications/admin/static/edit_area/langs/ja.js +++ applications/admin/static/edit_area/langs/ja.js @@ -0,0 +1,48 @@ +editAreaLoader.lang["ja"]={ +new_document: "新規作成", +search_button: "検索・置換", +search_command: "次を検索 / 検索窓を表示", +search: "検索", +replace: "置換", +replace_command: "置換 / 置換窓を表示", +find_next: "次を検索", +replace_all: "全置換", +reg_exp: "正規表現", +match_case: "大文字小文字の区別", +not_found: "見つかりません。", +occurrence_replaced: "置換しました。", +search_field_empty: "検索対象文字列が空です。", +restart_search_at_begin: "終端に達しました、始めに戻ります", +move_popup: "検索窓を移動", +font_size: "--フォントサイズ--", +go_to_line: "指定行へ移動", +go_to_line_prompt: "指定行へ移動します:", +undo: "元に戻す", +redo: "やり直し", +change_smooth_selection: "スムース表示の切り替え(CPUを使います)", +highlight: "構文強調表示の切り替え", +reset_highlight: "構文強調表示のリセット", +word_wrap: "toggle word wrapping mode", +help: "ヘルプを表示", +save: "保存", +load: "読み込み", +line_abbr: "行", +char_abbr: "文字", +position: "位置", +total: "合計", +close_popup: "ポップアップを閉じる", +shortcuts: "ショートカット", +add_tab: "タブを挿入する", +remove_tab: "タブを削除する", +about_notice: "注意:構文強調表示は短いテキストでしか有効に機能しません。", +toggle: "テキストエリアとeditAreaの切り替え", +accesskey: "アクセスキー", +tab: "Tab", +shift: "Shift", +ctrl: "Ctrl", +esc: "Esc", +processing: "処理中です...", +fullscreen: "fullscreen", +syntax_selection: "--Syntax--", +close_tab: "Close file" +}; ADDED applications/admin/static/edit_area/langs/mk.js Index: applications/admin/static/edit_area/langs/mk.js ================================================================== --- applications/admin/static/edit_area/langs/mk.js +++ applications/admin/static/edit_area/langs/mk.js @@ -0,0 +1,48 @@ +editAreaLoader.lang["mk"]={ +new_document: "Нов документ", +search_button: "Најди и замени", +search_command: "Барај следно / Отвори нов прозорец за пребарување", +search: "Барај", +replace: "Замени", +replace_command: "Замени / Отвори прозорец за пребарување", +find_next: "најди следно", +replace_all: "Замени ги сите", +reg_exp: "Регуларни изрази", +match_case: "Битна е големината на буквите", +not_found: "не е пронајдено.", +occurrence_replaced: "замени.", +search_field_empty: "Полето за пребарување е празно", +restart_search_at_begin: "Крај на областа. Стартувај од почеток.", +move_popup: "Помести го прозорецот", +font_size: "--Големина на текстот--", +go_to_line: "Оди на линија", +go_to_line_prompt: "Оди на линија со број:", +undo: "Врати", +redo: "Повтори", +change_smooth_selection: "Вклучи/исклучи некои карактеристики за приказ (попаметен приказ, но поголемо оптеретување за процесорот)", +highlight: "Вклучи/исклучи осветлување на синтакса", +reset_highlight: "Ресетирај го осветлувањето на синтакса (доколку е десинхронизиранo со текстот)", +word_wrap: "toggle word wrapping mode", +help: "За", +save: "Зачувај", +load: "Вчитај", +line_abbr: "Лн", +char_abbr: "Зн", +position: "Позиција", +total: "Вкупно", +close_popup: "Затвори го прозорецот", +shortcuts: "Кратенки", +add_tab: "Додај табулација на текстот", +remove_tab: "Отстрани ја табулацијата", +about_notice: "Напомена: Осветлувањето на синтанса е само за краток текст", +toggle: "Смени начин на уредување", +accesskey: "Accesskey", +tab: "Tab", +shift: "Shift", +ctrl: "Ctrl", +esc: "Esc", +processing: "Обработувам...", +fullscreen: "Цел прозорец", +syntax_selection: "--Синтакса--", +close_tab: "Избери датотека" +}; ADDED applications/admin/static/edit_area/langs/nl.js Index: applications/admin/static/edit_area/langs/nl.js ================================================================== --- applications/admin/static/edit_area/langs/nl.js +++ applications/admin/static/edit_area/langs/nl.js @@ -0,0 +1,48 @@ +editAreaLoader.lang["nl"]={ +new_document: "nieuw leeg document", +search_button: "zoek en vervang", +search_command: "zoek volgende / zoekscherm openen", +search: "zoek", +replace: "vervang", +replace_command: "vervang / zoekscherm openen", +find_next: "volgende vinden", +replace_all: "alles vervangen", +reg_exp: "reguliere expressies", +match_case: "hoofdletter gevoelig", +not_found: "niet gevonden.", +occurrence_replaced: "object vervangen.", +search_field_empty: "Zoek veld leeg", +restart_search_at_begin: "Niet meer instanties gevonden, begin opnieuw", +move_popup: "versleep zoek scherm", +font_size: "--Letter grootte--", +go_to_line: "Ga naar regel", +go_to_line_prompt: "Ga naar regel nummer:", +undo: "Ongedaan maken", +redo: "Opnieuw doen", +change_smooth_selection: "zet wat schermopties aan/uit (kan langzamer zijn)", +highlight: "zet syntax highlight aan/uit", +reset_highlight: "reset highlight (indien gedesynchronizeerd)", +word_wrap: "toggle word wrapping mode", +help: "informatie", +save: "opslaan", +load: "laden", +line_abbr: "Ln", +char_abbr: "Ch", +position: "Positie", +total: "Totaal", +close_popup: "Popup sluiten", +shortcuts: "Snelkoppelingen", +add_tab: "voeg tabs toe in tekst", +remove_tab: "verwijder tabs uit tekst", +about_notice: "Notitie: syntax highlight functie is alleen voor kleine tekst", +toggle: "geavanceerde bewerkingsopties", +accesskey: "Accessknop", +tab: "Tab", +shift: "Shift", +ctrl: "Ctrl", +esc: "Esc", +processing: "Verwerken...", +fullscreen: "fullscreen", +syntax_selection: "--Syntax--", +close_tab: "Close file" +}; ADDED applications/admin/static/edit_area/langs/pl.js Index: applications/admin/static/edit_area/langs/pl.js ================================================================== --- applications/admin/static/edit_area/langs/pl.js +++ applications/admin/static/edit_area/langs/pl.js @@ -0,0 +1,48 @@ +editAreaLoader.lang["pl"]={ +new_document: "nowy dokument", +search_button: "znajdź i zamień", +search_command: "znajdź następny", +search: "znajdź", +replace: "zamień", +replace_command: "zamień", +find_next: "następny", +replace_all: "zamień wszystko", +reg_exp: "wyrażenie regularne", +match_case: "uwzględnij wielkość liter
", +not_found: "nie znaleziono.", +occurrence_replaced: "wystąpień zamieniono.", +search_field_empty: "Nie wprowadzono tekstu", +restart_search_at_begin: "Koniec dokumentu. Wyszukiwanie od początku.", +move_popup: "przesuń okienko wyszukiwania", +font_size: "Rozmiar", +go_to_line: "idź do linii", +go_to_line_prompt: "numer linii:", +undo: "cofnij", +redo: "przywróć", +change_smooth_selection: "włącz/wyłącz niektóre opcje wyglądu (zaawansowane opcje wyglądu obciążają procesor)", +highlight: "włącz/wyłącz podświetlanie składni", +reset_highlight: "odśwież podświetlanie składni (jeśli rozsynchronizowało się z tekstem)", +word_wrap: "toggle word wrapping mode", +help: "o programie", +save: "zapisz", +load: "otwórz", +line_abbr: "Ln", +char_abbr: "Zn", +position: "Pozycja", +total: "W sumie", +close_popup: "zamknij okienko", +shortcuts: "Skróty klawiaturowe", +add_tab: "dodaj wcięcie do zaznaczonego tekstu", +remove_tab: "usuń wcięcie", +about_notice: "Uwaga: podświetlanie składni nie jest zalecane dla długich tekstów", +toggle: "Włącz/wyłącz edytor", +accesskey: "Alt+", +tab: "Tab", +shift: "Shift", +ctrl: "Ctrl", +esc: "Esc", +processing: "Przetwarzanie...", +fullscreen: "fullscreen", +syntax_selection: "--Syntax--", +close_tab: "Close file" +}; ADDED applications/admin/static/edit_area/langs/pt.js Index: applications/admin/static/edit_area/langs/pt.js ================================================================== --- applications/admin/static/edit_area/langs/pt.js +++ applications/admin/static/edit_area/langs/pt.js @@ -0,0 +1,48 @@ +editAreaLoader.lang["pt"]={ +new_document: "Novo documento", +search_button: "Localizar e substituir", +search_command: "Localizar próximo", +search: "Localizar", +replace: "Substituir", +replace_command: "Substituir", +find_next: "Localizar", +replace_all: "Subst. tudo", +reg_exp: "Expressões regulares", +match_case: "Diferenciar maiúsculas e minúsculas", +not_found: "Não encontrado.", +occurrence_replaced: "Ocorrências substituidas", +search_field_empty: "Campo localizar vazio.", +restart_search_at_begin: "Fim das ocorrências. Recomeçar do inicio.", +move_popup: "Mover janela", +font_size: "--Tamanho da fonte--", +go_to_line: "Ir para linha", +go_to_line_prompt: "Ir para a linha:", +undo: "Desfazer", +redo: "Refazer", +change_smooth_selection: "Opções visuais", +highlight: "Cores de sintaxe", +reset_highlight: "Resetar cores (se não sincronizado)", +word_wrap: "toggle word wrapping mode", +help: "Sobre", +save: "Salvar", +load: "Carregar", +line_abbr: "Ln", +char_abbr: "Ch", +position: "Posição", +total: "Total", +close_popup: "Fechar", +shortcuts: "Shortcuts", +add_tab: "Adicionar tabulação", +remove_tab: "Remover tabulação", +about_notice: "Atenção: Cores de sintaxe são indicados somente para textos pequenos", +toggle: "Exibir editor", +accesskey: "Accesskey", +tab: "Tab", +shift: "Shift", +ctrl: "Ctrl", +esc: "Esc", +processing: "Processando...", +fullscreen: "fullscreen", +syntax_selection: "--Syntax--", +close_tab: "Close file" +}; ADDED applications/admin/static/edit_area/langs/ru.js Index: applications/admin/static/edit_area/langs/ru.js ================================================================== --- applications/admin/static/edit_area/langs/ru.js +++ applications/admin/static/edit_area/langs/ru.js @@ -0,0 +1,48 @@ +editAreaLoader.lang["ru"]={ +new_document: "новый пустой документ", +search_button: "поиск и замена", +search_command: "искать следующий / открыть панель поиска", +search: "поиск", +replace: "замена", +replace_command: "заменить / открыть панель поиска", +find_next: "найти следующее", +replace_all: "заменить все", +reg_exp: "регулярное выражение", +match_case: "учитывать регистр", +not_found: "не найдено.", +occurrence_replaced: "вхождение заменено.", +search_field_empty: "Поле поиска пустое", +restart_search_at_begin: "Достигнут конец документа. Начинаю с начала.", +move_popup: "переместить окно поиска", +font_size: "--Размер шрифта--", +go_to_line: "перейти к строке", +go_to_line_prompt: "перейти к строке номер:", +undo: "отменить", +redo: "вернуть", +change_smooth_selection: "включить/отключить некоторые функции просмотра (более красиво, но больше использует процессор)", +highlight: "переключить подсветку синтаксиса включена/выключена", +reset_highlight: "восстановить подсветку (если разсинхронизирована от текста)", +word_wrap: "toggle word wrapping mode", +help: "о программе", +save: "сохранить", +load: "загрузить", +line_abbr: "Стр", +char_abbr: "Стлб", +position: "Позиция", +total: "Всего", +close_popup: "закрыть всплывающее окно", +shortcuts: "Горячие клавиши", +add_tab: "добавить табуляцию в текст", +remove_tab: "убрать табуляцию из текста", +about_notice: "Внимание: функция подсветки синтаксиса только для небольших текстов", +toggle: "Переключить редактор", +accesskey: "Горячая клавиша", +tab: "Tab", +shift: "Shift", +ctrl: "Ctrl", +esc: "Esc", +processing: "Обработка...", +fullscreen: "полный экран", +syntax_selection: "--Синтакс--", +close_tab: "Закрыть файл" +}; ADDED applications/admin/static/edit_area/langs/sk.js Index: applications/admin/static/edit_area/langs/sk.js ================================================================== --- applications/admin/static/edit_area/langs/sk.js +++ applications/admin/static/edit_area/langs/sk.js @@ -0,0 +1,48 @@ +editAreaLoader.lang["sk"]={ +new_document: "nový prázdy dokument", +search_button: "vyhľadaj a nahraď", +search_command: "hľadaj ďalsšie / otvor vyhľadávacie pole", +search: "hľadaj", +replace: "nahraď", +replace_command: "nahraď / otvor vyhľadávacie pole", +find_next: "nájdi ďalšie", +replace_all: "nahraď všetko", +reg_exp: "platné výrazy", +match_case: "zhodujúce sa výrazy", +not_found: "nenájdené.", +occurrence_replaced: "výskyty nahradené.", +search_field_empty: "Pole vyhľadávanie je prádzne", +restart_search_at_begin: "End of area reached. Restart at begin.", +move_popup: "presuň vyhľadávacie okno", +font_size: "--Veľkosť textu--", +go_to_line: "prejdi na riadok", +go_to_line_prompt: "prejdi na riadok:", +undo: "krok späť", +redo: "prepracovať", +change_smooth_selection: "povoliť/zamietnúť niektoré zo zobrazených funkcií (účelnejšie zobrazenie vyžaduje väčšie zaťaženie procesora CPU)", +highlight: "prepnúť zvýrazňovanie syntaxe zap/vyp", +reset_highlight: "zrušiť zvýrazňovanie (ak je nesynchronizované s textom)", +word_wrap: "toggle word wrapping mode", +help: "o programe", +save: "uložiť", +load: "načítať", +line_abbr: "Ln", +char_abbr: "Ch", +position: "Pozícia", +total: "Spolu", +close_popup: "zavrieť okno", +shortcuts: "Skratky", +add_tab: "pridať tabulovanie textu", +remove_tab: "odstrániť tabulovanie textu", +about_notice: "Upozornenie: funkcia zvýrazňovania syntaxe je dostupná iba pre malý text", +toggle: "Prepnúť editor", +accesskey: "Accesskey", +tab: "Záložka", +shift: "Shift", +ctrl: "Ctrl", +esc: "Esc", +processing: "Spracúvam...", +fullscreen: "cel=a obrazovka", +syntax_selection: "--Vyber Syntax--", +close_tab: "Close file" +}; ADDED applications/admin/static/edit_area/langs/zh.js Index: applications/admin/static/edit_area/langs/zh.js ================================================================== --- applications/admin/static/edit_area/langs/zh.js +++ applications/admin/static/edit_area/langs/zh.js @@ -0,0 +1,48 @@ +editAreaLoader.lang["zh"]={ +new_document: "新建空白文档", +search_button: "查找与替换", +search_command: "查找下一个 / 打开查找框", +search: "查找", +replace: "替换", +replace_command: "替换 / 打开查找框", +find_next: "查找下一个", +replace_all: "全部替换", +reg_exp: "正则表达式", +match_case: "匹配大小写", +not_found: "未找到.", +occurrence_replaced: "处被替换.", +search_field_empty: "查找框没有内容", +restart_search_at_begin: "已到到文档末尾. 从头重新查找.", +move_popup: "移动查找对话框", +font_size: "--字体大小--", +go_to_line: "转到行", +go_to_line_prompt: "转到行:", +undo: "恢复", +redo: "重做", +change_smooth_selection: "启用/禁止一些显示特性(更好看但更耗费资源)", +highlight: "启用/禁止语法高亮", +reset_highlight: "重置语法高亮(当文本显示不同步时)", +word_wrap: "toggle word wrapping mode", +help: "关于", +save: "保存", +load: "加载", +line_abbr: "行", +char_abbr: "字符", +position: "位置", +total: "总计", +close_popup: "关闭对话框", +shortcuts: "快捷键", +add_tab: "添加制表符(Tab)", +remove_tab: "移除制表符(Tab)", +about_notice: "注意:语法高亮功能仅用于较少内容的文本(文件内容太大会导致浏览器反应慢)", +toggle: "切换编辑器", +accesskey: "快捷键", +tab: "Tab", +shift: "Shift", +ctrl: "Ctrl", +esc: "Esc", +processing: "正在处理中...", +fullscreen: "全屏编辑", +syntax_selection: "--语法--", +close_tab: "关闭文件" +}; ADDED applications/admin/static/edit_area/license.txt Index: applications/admin/static/edit_area/license.txt ================================================================== --- applications/admin/static/edit_area/license.txt +++ applications/admin/static/edit_area/license.txt ADDED applications/admin/static/edit_area/license_apache.txt Index: applications/admin/static/edit_area/license_apache.txt ================================================================== --- applications/admin/static/edit_area/license_apache.txt +++ applications/admin/static/edit_area/license_apache.txt @@ -0,0 +1,7 @@ +Copyright 2008 Christophe Dolivet + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ADDED applications/admin/static/edit_area/license_bsd.txt Index: applications/admin/static/edit_area/license_bsd.txt ================================================================== --- applications/admin/static/edit_area/license_bsd.txt +++ applications/admin/static/edit_area/license_bsd.txt @@ -0,0 +1,10 @@ +Copyright (c) 2008, Christophe Dolivet +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of EditArea nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ADDED applications/admin/static/edit_area/license_lgpl.txt Index: applications/admin/static/edit_area/license_lgpl.txt ================================================================== --- applications/admin/static/edit_area/license_lgpl.txt +++ applications/admin/static/edit_area/license_lgpl.txt @@ -0,0 +1,458 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "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 +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY 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 +LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS ADDED applications/admin/static/edit_area/manage_area.js Index: applications/admin/static/edit_area/manage_area.js ================================================================== --- applications/admin/static/edit_area/manage_area.js +++ applications/admin/static/edit_area/manage_area.js @@ -0,0 +1,623 @@ + EditArea.prototype.focus = function() { + this.textarea.focus(); + this.textareaFocused=true; + }; + + + EditArea.prototype.check_line_selection= function(timer_checkup){ + var changes, infos, new_top, new_width,i; + + var t1=t2=t2_1=t3=tLines=tend= new Date().getTime(); + // l'editeur n'existe plus => on quitte + if(!editAreas[this.id]) + return false; + + if(!this.smooth_selection && !this.do_highlight) + { + //do nothing + } + else if(this.textareaFocused && editAreas[this.id]["displayed"]==true && this.isResizing==false) + { + infos = this.get_selection_infos(); + changes = this.checkTextEvolution( typeof( this.last_selection['full_text'] ) == 'undefined' ? '' : this.last_selection['full_text'], infos['full_text'] ); + + t2= new Date().getTime(); + + // if selection change + if(this.last_selection["line_start"] != infos["line_start"] || this.last_selection["line_nb"] != infos["line_nb"] || infos["full_text"] != this.last_selection["full_text"] || this.reload_highlight || this.last_selection["selectionStart"] != infos["selectionStart"] || this.last_selection["selectionEnd"] != infos["selectionEnd"] || !timer_checkup ) + { + // move and adjust text selection elements + new_top = this.getLinePosTop( infos["line_start"] ); + new_width = Math.max(this.textarea.scrollWidth, this.container.clientWidth -50); + this.selection_field.style.top=this.selection_field_text.style.top=new_top+"px"; + if(!this.settings['word_wrap']){ + this.selection_field.style.width=this.selection_field_text.style.width=this.test_font_size.style.width=new_width+"px"; + } + + // usefull? => _$("cursor_pos").style.top=new_top+"px"; + + if(this.do_highlight==true) + { + // fill selection elements + var curr_text = infos["full_text"].split("\n"); + var content = ""; + //alert("length: "+curr_text.length+ " i: "+ Math.max(0,infos["line_start"]-1)+ " end: "+Math.min(curr_text.length, infos["line_start"]+infos["line_nb"]-1)+ " line: "+infos["line_start"]+" [0]: "+curr_text[0]+" [1]: "+curr_text[1]); + var start = Math.max(0,infos["line_start"]-1); + var end = Math.min(curr_text.length, infos["line_start"]+infos["line_nb"]-1); + + //curr_text[start]= curr_text[start].substr(0,infos["curr_pos"]-1) +"¤_overline_¤"+ curr_text[start].substr(infos["curr_pos"]-1); + for(i=start; i< end; i++){ + content+= curr_text[i]+"\n"; + } + + // add special chars arround selected characters + selLength = infos['selectionEnd'] - infos['selectionStart']; + content = content.substr( 0, infos["curr_pos"] - 1 ) + "\r\r" + content.substr( infos["curr_pos"] - 1, selLength ) + "\r\r" + content.substr( infos["curr_pos"] - 1 + selLength ); + content = ''+ content.replace(/&/g,"&").replace(//g,">").replace("\r\r", '').replace("\r\r", '') +''; + + if( this.isIE || ( this.isOpera && this.isOpera < 9.6 ) ) { + this.selection_field.innerHTML= "
" + content.replace(/^\r?\n/, "
") + "
"; + } else { + this.selection_field.innerHTML= content; + } + this.selection_field_text.innerHTML = this.selection_field.innerHTML; + t2_1 = new Date().getTime(); + // check if we need to update the highlighted background + if(this.reload_highlight || (infos["full_text"] != this.last_text_to_highlight && (this.last_selection["line_start"]!=infos["line_start"] || this.show_line_colors || this.settings['word_wrap'] || this.last_selection["line_nb"]!=infos["line_nb"] || this.last_selection["nb_line"]!=infos["nb_line"]) ) ) + { + this.maj_highlight(infos); + } + } + } + t3= new Date().getTime(); + + // manage line heights + if( this.settings['word_wrap'] && infos["full_text"] != this.last_selection["full_text"]) + { + // refresh only 1 line if text change concern only one line and that the total line number has not changed + if( changes.newText.split("\n").length == 1 && this.last_selection['nb_line'] && infos['nb_line'] == this.last_selection['nb_line'] ) + { + this.fixLinesHeight( infos['full_text'], changes.lineStart, changes.lineStart ); + } + else + { + this.fixLinesHeight( infos['full_text'], changes.lineStart, -1 ); + } + } + + tLines= new Date().getTime(); + // manage bracket finding + if( infos["line_start"] != this.last_selection["line_start"] || infos["curr_pos"] != this.last_selection["curr_pos"] || infos["full_text"].length!=this.last_selection["full_text"].length || this.reload_highlight || !timer_checkup ) + { + // move _cursor_pos + var selec_char= infos["curr_line"].charAt(infos["curr_pos"]-1); + var no_real_move=true; + if(infos["line_nb"]==1 && (this.assocBracket[selec_char] || this.revertAssocBracket[selec_char]) ){ + + no_real_move=false; + //findEndBracket(infos["line_start"], infos["curr_pos"], selec_char); + if(this.findEndBracket(infos, selec_char) === true){ + _$("end_bracket").style.visibility ="visible"; + _$("cursor_pos").style.visibility ="visible"; + _$("cursor_pos").innerHTML = selec_char; + _$("end_bracket").innerHTML = (this.assocBracket[selec_char] || this.revertAssocBracket[selec_char]); + }else{ + _$("end_bracket").style.visibility ="hidden"; + _$("cursor_pos").style.visibility ="hidden"; + } + }else{ + _$("cursor_pos").style.visibility ="hidden"; + _$("end_bracket").style.visibility ="hidden"; + } + //alert("move cursor"); + this.displayToCursorPosition("cursor_pos", infos["line_start"], infos["curr_pos"]-1, infos["curr_line"], no_real_move); + if(infos["line_nb"]==1 && infos["line_start"]!=this.last_selection["line_start"]) + this.scroll_to_view(); + } + this.last_selection=infos; + } + + tend= new Date().getTime(); + //if( (tend-t1) > 7 ) + // console.log( "tps total: "+ (tend-t1) + " tps get_infos: "+ (t2-t1)+ " tps selec: "+ (t2_1-t2)+ " tps highlight: "+ (t3-t2_1) +" tps lines: "+ (tLines-t3) +" tps cursor+lines: "+ (tend-tLines)+" \n" ); + + + if(timer_checkup){ + setTimeout("editArea.check_line_selection(true)", this.check_line_selection_timer); + } + }; + + + EditArea.prototype.get_selection_infos= function(){ + var sel={}, start, end, len, str; + + this.getIESelection(); + start = this.textarea.selectionStart; + end = this.textarea.selectionEnd; + + if( this.last_selection["selectionStart"] == start && this.last_selection["selectionEnd"] == end && this.last_selection["full_text"] == this.textarea.value ) + { + return this.last_selection; + } + + if(this.tabulation!="\t" && this.textarea.value.indexOf("\t")!=-1) + { // can append only after copy/paste + len = this.textarea.value.length; + this.textarea.value = this.replace_tab(this.textarea.value); + start = end = start+(this.textarea.value.length-len); + this.area_select( start, 0 ); + } + + sel["selectionStart"] = start; + sel["selectionEnd"] = end; + sel["full_text"] = this.textarea.value; + sel["line_start"] = 1; + sel["line_nb"] = 1; + sel["curr_pos"] = 0; + sel["curr_line"] = ""; + sel["indexOfCursor"] = 0; + sel["selec_direction"] = this.last_selection["selec_direction"]; + + //return sel; + var splitTab= sel["full_text"].split("\n"); + var nbLine = Math.max(0, splitTab.length); + var nbChar = Math.max(0, sel["full_text"].length - (nbLine - 1)); // (remove \n caracters from the count) + if( sel["full_text"].indexOf("\r") != -1 ) + nbChar = nbChar - ( nbLine - 1 ); // (remove \r caracters from the count) + sel["nb_line"] = nbLine; + sel["nb_char"] = nbChar; + + if(start>0){ + str = sel["full_text"].substr(0,start); + sel["curr_pos"] = start - str.lastIndexOf("\n"); + sel["line_start"] = Math.max(1, str.split("\n").length); + }else{ + sel["curr_pos"]=1; + } + if(end>start){ + sel["line_nb"]=sel["full_text"].substring(start,end).split("\n").length; + } + sel["indexOfCursor"]=start; + sel["curr_line"]=splitTab[Math.max(0,sel["line_start"]-1)]; + + // determine in which direction the selection grow + if(sel["selectionStart"] == this.last_selection["selectionStart"]){ + if(sel["selectionEnd"]>this.last_selection["selectionEnd"]) + sel["selec_direction"]= "down"; + else if(sel["selectionEnd"] == this.last_selection["selectionStart"]) + sel["selec_direction"]= this.last_selection["selec_direction"]; + }else if(sel["selectionStart"] == this.last_selection["selectionEnd"] && sel["selectionEnd"]>this.last_selection["selectionEnd"]){ + sel["selec_direction"]= "down"; + }else{ + sel["selec_direction"]= "up"; + } + + _$("nbLine").innerHTML = nbLine; + _$("nbChar").innerHTML = nbChar; + _$("linePos").innerHTML = sel["line_start"]; + _$("currPos").innerHTML = sel["curr_pos"]; + + return sel; + }; + + // set IE position in Firefox mode (textarea.selectionStart and textarea.selectionEnd) + EditArea.prototype.getIESelection= function(){ + var selectionStart, selectionEnd, range, stored_range; + + if( !this.isIE ) + return false; + + // make it work as nowrap mode (easier for range manipulation with lineHeight) + if( this.settings['word_wrap'] ) + this.textarea.wrap='off'; + + try{ + range = document.selection.createRange(); + stored_range = range.duplicate(); + stored_range.moveToElementText( this.textarea ); + stored_range.setEndPoint( 'EndToEnd', range ); + if( stored_range.parentElement() != this.textarea ) + throw "invalid focus"; + + // the range don't take care of empty lines in the end of the selection + var scrollTop = this.result.scrollTop + document.body.scrollTop; + var relative_top= range.offsetTop - parent.calculeOffsetTop(this.textarea) + scrollTop; + var line_start = Math.round((relative_top / this.lineHeight) +1); + var line_nb = Math.round( range.boundingHeight / this.lineHeight ); + + selectionStart = stored_range.text.length - range.text.length; + selectionStart += ( line_start - this.textarea.value.substr(0, selectionStart).split("\n").length)*2; // count missing empty \r to the selection + selectionStart -= ( line_start - this.textarea.value.substr(0, selectionStart).split("\n").length ) * 2; + + selectionEnd = selectionStart + range.text.length; + selectionEnd += (line_start + line_nb - 1 - this.textarea.value.substr(0, selectionEnd ).split("\n").length)*2; + + this.textarea.selectionStart = selectionStart; + this.textarea.selectionEnd = selectionEnd; + } + catch(e){} + + // restore wrap mode + if( this.settings['word_wrap'] ) + this.textarea.wrap='soft'; + }; + + // select the text for IE (and take care of \r caracters) + EditArea.prototype.setIESelection= function(){ + var a = this.textarea, nbLineStart, nbLineEnd, range; + + if( !this.isIE ) + return false; + + nbLineStart = a.value.substr(0, a.selectionStart).split("\n").length - 1; + nbLineEnd = a.value.substr(0, a.selectionEnd).split("\n").length - 1; + range = document.selection.createRange(); + range.moveToElementText( a ); + range.setEndPoint( 'EndToStart', range ); + + range.moveStart('character', a.selectionStart - nbLineStart); + range.moveEnd('character', a.selectionEnd - nbLineEnd - (a.selectionStart - nbLineStart) ); + range.select(); + }; + + + + EditArea.prototype.checkTextEvolution=function(lastText,newText){ + // ch will contain changes datas + var ch={},baseStep=200, cpt=0, end, step,tStart=new Date().getTime(); + + end = Math.min(newText.length, lastText.length); + step = baseStep; + // find how many chars are similar at the begin of the text + while( cpt=1 ){ + if(lastText.substr(cpt, step) == newText.substr(cpt, step)){ + cpt+= step; + }else{ + step= Math.floor(step/2); + } + } + + ch.posStart = cpt; + ch.lineStart= newText.substr(0, ch.posStart).split("\n").length -1; + + cpt_last = lastText.length; + cpt = newText.length; + step = baseStep; + // find how many chars are similar at the end of the text + while( cpt>=0 && cpt_last>=0 && step>=1 ){ + if(lastText.substr(cpt_last-step, step) == newText.substr(cpt-step, step)){ + cpt-= step; + cpt_last-= step; + }else{ + step= Math.floor(step/2); + } + } + + ch.posNewEnd = cpt; + ch.posLastEnd = cpt_last; + if(ch.posNewEnd<=ch.posStart){ + if(lastText.length < newText.length){ + ch.posNewEnd= ch.posStart + newText.length - lastText.length; + ch.posLastEnd= ch.posStart; + }else{ + ch.posLastEnd= ch.posStart + lastText.length - newText.length; + ch.posNewEnd= ch.posStart; + } + } + ch.newText = newText.substring(ch.posStart, ch.posNewEnd); + ch.lastText = lastText.substring(ch.posStart, ch.posLastEnd); + + ch.lineNewEnd = newText.substr(0, ch.posNewEnd).split("\n").length -1; + ch.lineLastEnd = lastText.substr(0, ch.posLastEnd).split("\n").length -1; + + ch.newTextLine = newText.split("\n").slice(ch.lineStart, ch.lineNewEnd+1).join("\n"); + ch.lastTextLine = lastText.split("\n").slice(ch.lineStart, ch.lineLastEnd+1).join("\n"); + //console.log( ch ); + return ch; + }; + + EditArea.prototype.tab_selection= function(){ + if(this.is_tabbing) + return; + this.is_tabbing=true; + //infos=getSelectionInfos(); + //if( document.selection ){ + this.getIESelection(); + /* Insertion du code de formatage */ + var start = this.textarea.selectionStart; + var end = this.textarea.selectionEnd; + var insText = this.textarea.value.substring(start, end); + + /* Insert tabulation and ajust cursor position */ + var pos_start=start; + var pos_end=end; + if (insText.length == 0) { + // if only one line selected + this.textarea.value = this.textarea.value.substr(0, start) + this.tabulation + this.textarea.value.substr(end); + pos_start = start + this.tabulation.length; + pos_end=pos_start; + } else { + start= Math.max(0, this.textarea.value.substr(0, start).lastIndexOf("\n")+1); + endText=this.textarea.value.substr(end); + startText=this.textarea.value.substr(0, start); + tmp= this.textarea.value.substring(start, end).split("\n"); + insText= this.tabulation+tmp.join("\n"+this.tabulation); + this.textarea.value = startText + insText + endText; + pos_start = start; + pos_end= this.textarea.value.indexOf("\n", startText.length + insText.length); + if(pos_end==-1) + pos_end=this.textarea.value.length; + //pos = start + repdeb.length + insText.length + ; + } + this.textarea.selectionStart = pos_start; + this.textarea.selectionEnd = pos_end; + + //if( document.selection ){ + if(this.isIE) + { + this.setIESelection(); + setTimeout("editArea.is_tabbing=false;", 100); // IE can't accept to make 2 tabulation without a little break between both + } + else + { + this.is_tabbing=false; + } + + }; + + EditArea.prototype.invert_tab_selection= function(){ + var t=this, a=this.textarea; + if(t.is_tabbing) + return; + t.is_tabbing=true; + //infos=getSelectionInfos(); + //if( document.selection ){ + t.getIESelection(); + + var start = a.selectionStart; + var end = a.selectionEnd; + var insText = a.value.substring(start, end); + + /* Tab remove and cursor seleciton adjust */ + var pos_start=start; + var pos_end=end; + if (insText.length == 0) { + if(a.value.substring(start-t.tabulation.length, start)==t.tabulation) + { + a.value = a.value.substr(0, start-t.tabulation.length) + a.value.substr(end); + pos_start = Math.max(0, start-t.tabulation.length); + pos_end = pos_start; + } + /* + a.value = a.value.substr(0, start) + t.tabulation + insText + a.value.substr(end); + pos_start = start + t.tabulation.length; + pos_end=pos_start;*/ + } else { + start = a.value.substr(0, start).lastIndexOf("\n")+1; + endText = a.value.substr(end); + startText = a.value.substr(0, start); + tmp = a.value.substring(start, end).split("\n"); + insText = ""; + for(i=0; i=0; ){ + if(infos["full_text"].charAt(i)==endBracket){ + nbBracketOpen--; + if(nbBracketOpen<=0){ + //i=infos["full_text"].length; + end=i; + break; + } + }else if(infos["full_text"].charAt(i)==bracket) + nbBracketOpen++; + if(normal_order) + i++; + else + i--; + } + + //end=infos["full_text"].indexOf("}", start); + if(end==-1) + return false; + var endLastLine=infos["full_text"].substr(0, end).lastIndexOf("\n"); + if(endLastLine==-1) + line=1; + else + line= infos["full_text"].substr(0, endLastLine).split("\n").length + 1; + + var curPos= end - endLastLine - 1; + var endLineLength = infos["full_text"].substring(end).split("\n")[0].length; + this.displayToCursorPosition("end_bracket", line, curPos, infos["full_text"].substring(endLastLine +1, end + endLineLength)); + return true; + }; + + EditArea.prototype.displayToCursorPosition= function(id, start_line, cur_pos, lineContent, no_real_move){ + var elem,dest,content,posLeft=0,posTop,fixPadding,topOffset,endElem; + + elem = this.test_font_size; + dest = _$(id); + content = ""+lineContent.substr(0, cur_pos).replace(/&/g,"&").replace(/"+lineContent.substr(cur_pos).replace(/&/g,"&").replace(/"; + if( this.isIE || ( this.isOpera && this.isOpera < 9.6 ) ) { + elem.innerHTML= "
" + content.replace(/^\r?\n/, "
") + "
"; + } else { + elem.innerHTML= content; + } + + + endElem = _$('endTestFont'); + topOffset = endElem.offsetTop; + fixPadding = parseInt( this.content_highlight.style.paddingLeft.replace("px", "") ); + posLeft = 45 + endElem.offsetLeft + ( !isNaN( fixPadding ) && topOffset > 0 ? fixPadding : 0 ); + posTop = this.getLinePosTop( start_line ) + topOffset;// + Math.floor( ( endElem.offsetHeight - 1 ) / this.lineHeight ) * this.lineHeight; + + // detect the case where the span start on a line but has no display on it + if( this.isIE && cur_pos > 0 && endElem.offsetLeft == 0 ) + { + posTop += this.lineHeight; + } + if(no_real_move!=true){ // when the cursor is hidden no need to move him + dest.style.top=posTop+"px"; + dest.style.left=posLeft+"px"; + } + // usefull for smarter scroll + dest.cursor_top=posTop; + dest.cursor_left=posLeft; + // _$(id).style.marginLeft=posLeft+"px"; + }; + + EditArea.prototype.getLinePosTop= function(start_line){ + var elem= _$('line_'+ start_line), posTop=0; + if( elem ) + posTop = elem.offsetTop; + else + posTop = this.lineHeight * (start_line-1); + return posTop; + }; + + + // return the dislpayed height of a text (take word-wrap into account) + EditArea.prototype.getTextHeight= function(text){ + var t=this,elem,height; + elem = t.test_font_size; + content = text.replace(/&/g,"&").replace(/") + ""; + } else { + elem.innerHTML= content; + } + height = elem.offsetHeight; + height = Math.max( 1, Math.floor( elem.offsetHeight / this.lineHeight ) ) * this.lineHeight; + return height; + }; + + /** + * Fix line height for the given lines + * @param Integer linestart + * @param Integer lineEnd End line or -1 to cover all lines + */ + EditArea.prototype.fixLinesHeight= function( textValue, lineStart,lineEnd ){ + var aText = textValue.split("\n"); + if( lineEnd == -1 ) + lineEnd = aText.length-1; + for( var i = Math.max(0, lineStart); i <= lineEnd; i++ ) + { + if( elem = _$('line_'+ ( i+1 ) ) ) + { + elem.style.height= typeof( aText[i] ) != "undefined" ? this.getTextHeight( aText[i] )+"px" : this.lineHeight; + } + } + }; + + EditArea.prototype.area_select= function(start, length){ + this.textarea.focus(); + + start = Math.max(0, Math.min(this.textarea.value.length, start)); + end = Math.max(start, Math.min(this.textarea.value.length, start+length)); + + if(this.isIE) + { + this.textarea.selectionStart = start; + this.textarea.selectionEnd = end; + this.setIESelection(); + } + else + { + // Opera bug when moving selection start and selection end + if(this.isOpera && this.isOpera < 9.6 ) + { + this.textarea.setSelectionRange(0, 0); + } + this.textarea.setSelectionRange(start, end); + } + this.check_line_selection(); + }; + + + EditArea.prototype.area_get_selection= function(){ + var text=""; + if( document.selection ){ + var range = document.selection.createRange(); + text=range.text; + }else{ + text= this.textarea.value.substring(this.textarea.selectionStart, this.textarea.selectionEnd); + } + return text; + }; ADDED applications/admin/static/edit_area/plugins/charmap/charmap.js Index: applications/admin/static/edit_area/plugins/charmap/charmap.js ================================================================== --- applications/admin/static/edit_area/plugins/charmap/charmap.js +++ applications/admin/static/edit_area/plugins/charmap/charmap.js @@ -0,0 +1,90 @@ +/** + * Charmap plugin + * by Christophe Dolivet + * v0.1 (2006/09/22) + * + * + * This plugin allow to use a visual keyboard allowing to insert any UTF-8 characters in the text. + * + * - plugin name to add to the plugin list: "charmap" + * - plugin name to add to the toolbar list: "charmap" + * - possible parameters to add to EditAreaLoader.init(): + * "charmap_default": (String) define the name of the default character range displayed on popup display + * (default: "arrows") + * + * + */ + +var EditArea_charmap= { + /** + * Get called once this file is loaded (editArea still not initialized) + * + * @return nothing + */ + init: function(){ + this.default_language="Arrows"; + } + + /** + * Returns the HTML code for a specific control string or false if this plugin doesn't have that control. + * A control can be a button, select list or any other HTML item to present in the EditArea user interface. + * Language variables such as {$lang_somekey} will also be replaced with contents from + * the language packs. + * + * @param {string} ctrl_name: the name of the control to add + * @return HTML code for a specific control or false. + * @type string or boolean + */ + ,get_control_html: function(ctrl_name){ + switch(ctrl_name){ + case "charmap": + // Control id, button img, command + return parent.editAreaLoader.get_button_html('charmap_but', 'charmap.gif', 'charmap_press', false, this.baseURL); + } + return false; + } + /** + * Get called once EditArea is fully loaded and initialised + * + * @return nothing + */ + ,onload: function(){ + if(editArea.settings["charmap_default"] && editArea.settings["charmap_default"].length>0) + this.default_language= editArea.settings["charmap_default"]; + } + + /** + * Is called each time the user touch a keyboard key. + * + * @param (event) e: the keydown event + * @return true - pass to next handler in chain, false - stop chain execution + * @type boolean + */ + ,onkeydown: function(e){ + + } + + /** + * Executes a specific command, this function handles plugin commands. + * + * @param {string} cmd: the name of the command being executed + * @param {unknown} param: the parameter of the command + * @return true - pass to next handler in chain, false - stop chain execution + * @type boolean + */ + ,execCommand: function(cmd, param){ + // Handle commands + switch(cmd){ + case "charmap_press": + win= window.open(this.baseURL+"popup.html", "charmap", "width=500,height=270,scrollbars=yes,resizable=yes"); + win.focus(); + return false; + } + // Pass to next handler in chain + return true; + } + +}; + +// Adds the plugin class to the list of available EditArea plugins +editArea.add_plugin("charmap", EditArea_charmap); ADDED applications/admin/static/edit_area/plugins/charmap/css/charmap.css Index: applications/admin/static/edit_area/plugins/charmap/css/charmap.css ================================================================== --- applications/admin/static/edit_area/plugins/charmap/css/charmap.css +++ applications/admin/static/edit_area/plugins/charmap/css/charmap.css @@ -0,0 +1,64 @@ +body{ + background-color: #F0F0EE; + font: 12px monospace, sans-serif; +} + +select{ + background-color: #F9F9F9; + border: solid 1px #888888; +} + +h1, h2, h3, h4, h5, h6{ + margin: 0; + padding: 0; + color: #2B6FB6; +} + +h1{ + font-size: 1.5em; +} + +div#char_list{ + height: 200px; + overflow: auto; + padding: 1px; + border: 1px solid #0A246A; + background-color: #F9F9F9; + clear: both; + margin-top: 5px; +} + +a.char{ + display: block; + float: left; + width: 20px; + height: 20px; + line-height: 20px; + margin: 1px; + border: solid 1px #888888; + text-align: center; + cursor: pointer; +} + +a.char:hover{ + background-color: #CCCCCC; +} + +.preview{ + border: solid 1px #888888; + width: 50px; + padding: 2px 5px; + height: 35px; + line-height: 35px; + text-align:center; + background-color: #CCCCCC; + font-size: 2em; + float: right; + font-weight: bold; + margin: 0 0 5px 5px; +} + +#preview_code{ + font-size: 1.1em; + width: 70px; +} ADDED applications/admin/static/edit_area/plugins/charmap/images/charmap.gif Index: applications/admin/static/edit_area/plugins/charmap/images/charmap.gif ================================================================== --- applications/admin/static/edit_area/plugins/charmap/images/charmap.gif +++ applications/admin/static/edit_area/plugins/charmap/images/charmap.gif cannot compute difference between binary files ADDED applications/admin/static/edit_area/plugins/charmap/jscripts/map.js Index: applications/admin/static/edit_area/plugins/charmap/jscripts/map.js ================================================================== --- applications/admin/static/edit_area/plugins/charmap/jscripts/map.js +++ applications/admin/static/edit_area/plugins/charmap/jscripts/map.js @@ -0,0 +1,373 @@ +var editArea; + + +/** + * UTF-8 list taken from http://www.utf8-chartable.de/unicode-utf8-table.pl?utf8=dec + */ + + +/* +var char_range_list={ +"Basic Latin":"0021,007F", +"Latin-1 Supplement":"0080,00FF", +"Latin Extended-A":"0100,017F", +"Latin Extended-B":"0180,024F", +"IPA Extensions":"0250,02AF", +"Spacing Modifier Letters":"02B0,02FF", + +"Combining Diacritical Marks":"0300,036F", +"Greek and Coptic":"0370,03FF", +"Cyrillic":"0400,04FF", +"Cyrillic Supplement":"0500,052F", +"Armenian":"0530,058F", +"Hebrew":"0590,05FF", +"Arabic":"0600,06FF", +"Syriac":"0700,074F", +"Arabic Supplement":"0750,077F", + +"Thaana":"0780,07BF", +"Devanagari":"0900,097F", +"Bengali":"0980,09FF", +"Gurmukhi":"0A00,0A7F", +"Gujarati":"0A80,0AFF", +"Oriya":"0B00,0B7F", +"Tamil":"0B80,0BFF", +"Telugu":"0C00,0C7F", +"Kannada":"0C80,0CFF", + +"Malayalam":"0D00,0D7F", +"Sinhala":"0D80,0DFF", +"Thai":"0E00,0E7F", +"Lao":"0E80,0EFF", +"Tibetan":"0F00,0FFF", +"Myanmar":"1000,109F", +"Georgian":"10A0,10FF", +"Hangul Jamo":"1100,11FF", +"Ethiopic":"1200,137F", + +"Ethiopic Supplement":"1380,139F", +"Cherokee":"13A0,13FF", +"Unified Canadian Aboriginal Syllabics":"1400,167F", +"Ogham":"1680,169F", +"Runic":"16A0,16FF", +"Tagalog":"1700,171F", +"Hanunoo":"1720,173F", +"Buhid":"1740,175F", +"Tagbanwa":"1760,177F", + +"Khmer":"1780,17FF", +"Mongolian":"1800,18AF", +"Limbu":"1900,194F", +"Tai Le":"1950,197F", +"New Tai Lue":"1980,19DF", +"Khmer Symbols":"19E0,19FF", +"Buginese":"1A00,1A1F", +"Phonetic Extensions":"1D00,1D7F", +"Phonetic Extensions Supplement":"1D80,1DBF", + +"Combining Diacritical Marks Supplement":"1DC0,1DFF", +"Latin Extended Additional":"1E00,1EFF", +"Greek Extended":"1F00,1FFF", +"General Punctuation":"2000,206F", +"Superscripts and Subscripts":"2070,209F", +"Currency Symbols":"20A0,20CF", +"Combining Diacritical Marks for Symbols":"20D0,20FF", +"Letterlike Symbols":"2100,214F", +"Number Forms":"2150,218F", + +"Arrows":"2190,21FF", +"Mathematical Operators":"2200,22FF", +"Miscellaneous Technical":"2300,23FF", +"Control Pictures":"2400,243F", +"Optical Character Recognition":"2440,245F", +"Enclosed Alphanumerics":"2460,24FF", +"Box Drawing":"2500,257F", +"Block Elements":"2580,259F", +"Geometric Shapes":"25A0,25FF", + +"Miscellaneous Symbols":"2600,26FF", +"Dingbats":"2700,27BF", +"Miscellaneous Mathematical Symbols-A":"27C0,27EF", +"Supplemental Arrows-A":"27F0,27FF", +"Braille Patterns":"2800,28FF", +"Supplemental Arrows-B":"2900,297F", +"Miscellaneous Mathematical Symbols-B":"2980,29FF", +"Supplemental Mathematical Operators":"2A00,2AFF", +"Miscellaneous Symbols and Arrows":"2B00,2BFF", + +"Glagolitic":"2C00,2C5F", +"Coptic":"2C80,2CFF", +"Georgian Supplement":"2D00,2D2F", +"Tifinagh":"2D30,2D7F", +"Ethiopic Extended":"2D80,2DDF", +"Supplemental Punctuation":"2E00,2E7F", +"CJK Radicals Supplement":"2E80,2EFF", +"Kangxi Radicals":"2F00,2FDF", +"Ideographic Description Characters":"2FF0,2FFF", + +"CJK Symbols and Punctuation":"3000,303F", +"Hiragana":"3040,309F", +"Katakana":"30A0,30FF", +"Bopomofo":"3100,312F", +"Hangul Compatibility Jamo":"3130,318F", +"Kanbun":"3190,319F", +"Bopomofo Extended":"31A0,31BF", +"CJK Strokes":"31C0,31EF", +"Katakana Phonetic Extensions":"31F0,31FF", + +"Enclosed CJK Letters and Months":"3200,32FF", +"CJK Compatibility":"3300,33FF", +"CJK Unified Ideographs Extension A":"3400,4DBF", +"Yijing Hexagram Symbols":"4DC0,4DFF", +"CJK Unified Ideographs":"4E00,9FFF", +"Yi Syllables":"A000,A48F", +"Yi Radicals":"A490,A4CF", +"Modifier Tone Letters":"A700,A71F", +"Syloti Nagri":"A800,A82F", + +"Hangul Syllables":"AC00,D7AF", +"High Surrogates":"D800,DB7F", +"High Private Use Surrogates":"DB80,DBFF", +"Low Surrogates":"DC00,DFFF", +"Private Use Area":"E000,F8FF", +"CJK Compatibility Ideographs":"F900,FAFF", +"Alphabetic Presentation Forms":"FB00,FB4F", +"Arabic Presentation Forms-A":"FB50,FDFF", +"Variation Selectors":"FE00,FE0F", + +"Vertical Forms":"FE10,FE1F", +"Combining Half Marks":"FE20,FE2F", +"CJK Compatibility Forms":"FE30,FE4F", +"Small Form Variants":"FE50,FE6F", +"Arabic Presentation Forms-B":"FE70,FEFF", +"Halfwidth and Fullwidth Forms":"FF00,FFEF", +"Specials":"FFF0,FFFF", +"Linear B Syllabary":"10000,1007F", +"Linear B Ideograms":"10080,100FF", + +"Aegean Numbers":"10100,1013F", +"Ancient Greek Numbers":"10140,1018F", +"Old Italic":"10300,1032F", +"Gothic":"10330,1034F", +"Ugaritic":"10380,1039F", +"Old Persian":"103A0,103DF", +"Deseret":"10400,1044F", +"Shavian":"10450,1047F", +"Osmanya":"10480,104AF", + +"Cypriot Syllabary":"10800,1083F", +"Kharoshthi":"10A00,10A5F", +"Byzantine Musical Symbols":"1D000,1D0FF", +"Musical Symbols":"1D100,1D1FF", +"Ancient Greek Musical Notation":"1D200,1D24F", +"Tai Xuan Jing Symbols":"1D300,1D35F", +"Mathematical Alphanumeric Symbols":"1D400,1D7FF", +"CJK Unified Ideographs Extension B":"20000,2A6DF", +"CJK Compatibility Ideographs Supplement":"2F800,2FA1F", +"Tags":"E0000,E007F", +"Variation Selectors Supplement":"E0100,E01EF" +}; +*/ +var char_range_list={ +"Aegean Numbers":"10100,1013F", +"Alphabetic Presentation Forms":"FB00,FB4F", +"Ancient Greek Musical Notation":"1D200,1D24F", +"Ancient Greek Numbers":"10140,1018F", +"Arabic":"0600,06FF", +"Arabic Presentation Forms-A":"FB50,FDFF", +"Arabic Presentation Forms-B":"FE70,FEFF", +"Arabic Supplement":"0750,077F", +"Armenian":"0530,058F", +"Arrows":"2190,21FF", +"Basic Latin":"0020,007F", +"Bengali":"0980,09FF", +"Block Elements":"2580,259F", +"Bopomofo Extended":"31A0,31BF", +"Bopomofo":"3100,312F", +"Box Drawing":"2500,257F", +"Braille Patterns":"2800,28FF", +"Buginese":"1A00,1A1F", +"Buhid":"1740,175F", +"Byzantine Musical Symbols":"1D000,1D0FF", +"CJK Compatibility Forms":"FE30,FE4F", +"CJK Compatibility Ideographs Supplement":"2F800,2FA1F", +"CJK Compatibility Ideographs":"F900,FAFF", +"CJK Compatibility":"3300,33FF", +"CJK Radicals Supplement":"2E80,2EFF", +"CJK Strokes":"31C0,31EF", +"CJK Symbols and Punctuation":"3000,303F", +"CJK Unified Ideographs Extension A":"3400,4DBF", +"CJK Unified Ideographs Extension B":"20000,2A6DF", +"CJK Unified Ideographs":"4E00,9FFF", +"Cherokee":"13A0,13FF", +"Combining Diacritical Marks Supplement":"1DC0,1DFF", +"Combining Diacritical Marks for Symbols":"20D0,20FF", +"Combining Diacritical Marks":"0300,036F", +"Combining Half Marks":"FE20,FE2F", +"Control Pictures":"2400,243F", +"Coptic":"2C80,2CFF", +"Currency Symbols":"20A0,20CF", +"Cypriot Syllabary":"10800,1083F", +"Cyrillic Supplement":"0500,052F", +"Cyrillic":"0400,04FF", +"Deseret":"10400,1044F", +"Devanagari":"0900,097F", +"Dingbats":"2700,27BF", +"Enclosed Alphanumerics":"2460,24FF", +"Enclosed CJK Letters and Months":"3200,32FF", +"Ethiopic Extended":"2D80,2DDF", +"Ethiopic Supplement":"1380,139F", +"Ethiopic":"1200,137F", +"General Punctuation":"2000,206F", +"Geometric Shapes":"25A0,25FF", +"Georgian Supplement":"2D00,2D2F", +"Georgian":"10A0,10FF", +"Glagolitic":"2C00,2C5F", +"Gothic":"10330,1034F", +"Greek Extended":"1F00,1FFF", +"Greek and Coptic":"0370,03FF", +"Gujarati":"0A80,0AFF", +"Gurmukhi":"0A00,0A7F", +"Halfwidth and Fullwidth Forms":"FF00,FFEF", +"Hangul Compatibility Jamo":"3130,318F", +"Hangul Jamo":"1100,11FF", +"Hangul Syllables":"AC00,D7AF", +"Hanunoo":"1720,173F", +"Hebrew":"0590,05FF", +"High Private Use Surrogates":"DB80,DBFF", +"High Surrogates":"D800,DB7F", +"Hiragana":"3040,309F", +"IPA Extensions":"0250,02AF", +"Ideographic Description Characters":"2FF0,2FFF", +"Kanbun":"3190,319F", +"Kangxi Radicals":"2F00,2FDF", +"Kannada":"0C80,0CFF", +"Katakana Phonetic Extensions":"31F0,31FF", +"Katakana":"30A0,30FF", +"Kharoshthi":"10A00,10A5F", +"Khmer Symbols":"19E0,19FF", +"Khmer":"1780,17FF", +"Lao":"0E80,0EFF", +"Latin Extended Additional":"1E00,1EFF", +"Latin Extended-A":"0100,017F", +"Latin Extended-B":"0180,024F", +"Latin-1 Supplement":"0080,00FF", +"Letterlike Symbols":"2100,214F", +"Limbu":"1900,194F", +"Linear B Ideograms":"10080,100FF", +"Linear B Syllabary":"10000,1007F", +"Low Surrogates":"DC00,DFFF", +"Malayalam":"0D00,0D7F", +"Mathematical Alphanumeric Symbols":"1D400,1D7FF", +"Mathematical Operators":"2200,22FF", +"Miscellaneous Mathematical Symbols-A":"27C0,27EF", +"Miscellaneous Mathematical Symbols-B":"2980,29FF", +"Miscellaneous Symbols and Arrows":"2B00,2BFF", +"Miscellaneous Symbols":"2600,26FF", +"Miscellaneous Technical":"2300,23FF", +"Modifier Tone Letters":"A700,A71F", +"Mongolian":"1800,18AF", +"Musical Symbols":"1D100,1D1FF", +"Myanmar":"1000,109F", +"New Tai Lue":"1980,19DF", +"Number Forms":"2150,218F", +"Ogham":"1680,169F", +"Old Italic":"10300,1032F", +"Old Persian":"103A0,103DF", +"Optical Character Recognition":"2440,245F", +"Oriya":"0B00,0B7F", +"Osmanya":"10480,104AF", +"Phonetic Extensions Supplement":"1D80,1DBF", +"Phonetic Extensions":"1D00,1D7F", +"Private Use Area":"E000,F8FF", +"Runic":"16A0,16FF", +"Shavian":"10450,1047F", +"Sinhala":"0D80,0DFF", +"Small Form Variants":"FE50,FE6F", +"Spacing Modifier Letters":"02B0,02FF", +"Specials":"FFF0,FFFF", +"Superscripts and Subscripts":"2070,209F", +"Supplemental Arrows-A":"27F0,27FF", +"Supplemental Arrows-B":"2900,297F", +"Supplemental Mathematical Operators":"2A00,2AFF", +"Supplemental Punctuation":"2E00,2E7F", +"Syloti Nagri":"A800,A82F", +"Syriac":"0700,074F", +"Tagalog":"1700,171F", +"Tagbanwa":"1760,177F", +"Tags":"E0000,E007F", +"Tai Le":"1950,197F", +"Tai Xuan Jing Symbols":"1D300,1D35F", +"Tamil":"0B80,0BFF", +"Telugu":"0C00,0C7F", +"Thaana":"0780,07BF", +"Thai":"0E00,0E7F", +"Tibetan":"0F00,0FFF", +"Tifinagh":"2D30,2D7F", +"Ugaritic":"10380,1039F", +"Unified Canadian Aboriginal Syllabics":"1400,167F", +"Variation Selectors Supplement":"E0100,E01EF", +"Variation Selectors":"FE00,FE0F", +"Vertical Forms":"FE10,FE1F", +"Yi Radicals":"A490,A4CF", +"Yi Syllables":"A000,A48F", +"Yijing Hexagram Symbols":"4DC0,4DFF" +}; + +var insert="charmap_insert"; + +function map_load(){ + editArea=opener.editArea; + // translate the document + insert= editArea.get_translation(insert, "word"); + //alert(document.title); + document.title= editArea.get_translation(document.title, "template"); + document.body.innerHTML= editArea.get_translation(document.body.innerHTML, "template"); + //document.title= editArea.get_translation(document.getElementBytitle, "template"); + + var selected_lang=opener.EditArea_charmap.default_language.toLowerCase(); + var selected=0; + + var select= document.getElementById("select_range") + for(var i in char_range_list){ + if(i.toLowerCase()==selected_lang) + selected=select.options.length; + select.options[select.options.length]=new Option(i, char_range_list[i]); + } + select.options[selected].selected=true; +/* start=0; + end=127; + content=""; + for(var i=start; i"+ String.fromCharCode(i) +""; + } + document.getElementById("char_list").innerHTML= html; + document.getElementById("preview_char").innerHTML=""; +} + +function previewChar(i){ + document.getElementById("preview_char").innerHTML= String.fromCharCode(i); + document.getElementById("preview_code").innerHTML= "&#"+ i +";"; +} + +function insertChar(i){ + opener.parent.editAreaLoader.setSelectedText(editArea.id, String.fromCharCode( i)); + range= opener.parent.editAreaLoader.getSelectionRange(editArea.id); + opener.parent.editAreaLoader.setSelectionRange(editArea.id, range["end"], range["end"]); + window.focus(); +} ADDED applications/admin/static/edit_area/plugins/charmap/langs/bg.js Index: applications/admin/static/edit_area/plugins/charmap/langs/bg.js ================================================================== --- applications/admin/static/edit_area/plugins/charmap/langs/bg.js +++ applications/admin/static/edit_area/plugins/charmap/langs/bg.js @@ -0,0 +1,12 @@ +/* + * Bulgarian translation + * Author: Valentin Hristov + * Company: SOFTKIT Bulgarian + * Site: http://www.softkit-bg.com + */ +editArea.add_lang("bg",{ +charmap_but: "Виртуална клавиатура", +charmap_title: "Виртуална клавиатура", +charmap_choose_block: "избери езиков блок", +charmap_insert:"постави този символ" +}); ADDED applications/admin/static/edit_area/plugins/charmap/langs/cs.js Index: applications/admin/static/edit_area/plugins/charmap/langs/cs.js ================================================================== --- applications/admin/static/edit_area/plugins/charmap/langs/cs.js +++ applications/admin/static/edit_area/plugins/charmap/langs/cs.js @@ -0,0 +1,6 @@ +editArea.add_lang("cs",{ +charmap_but: "Visual keyboard", +charmap_title: "Visual keyboard", +charmap_choose_block: "select language block", +charmap_insert:"insert this character" +}); ADDED applications/admin/static/edit_area/plugins/charmap/langs/de.js Index: applications/admin/static/edit_area/plugins/charmap/langs/de.js ================================================================== --- applications/admin/static/edit_area/plugins/charmap/langs/de.js +++ applications/admin/static/edit_area/plugins/charmap/langs/de.js @@ -0,0 +1,6 @@ +editArea.add_lang("de",{ +charmap_but: "Sonderzeichen", +charmap_title: "Sonderzeichen", +charmap_choose_block: "Bereich auswählen", +charmap_insert: "dieses Zeichen einfügen" +}); ADDED applications/admin/static/edit_area/plugins/charmap/langs/dk.js Index: applications/admin/static/edit_area/plugins/charmap/langs/dk.js ================================================================== --- applications/admin/static/edit_area/plugins/charmap/langs/dk.js +++ applications/admin/static/edit_area/plugins/charmap/langs/dk.js @@ -0,0 +1,6 @@ +editArea.add_lang("dk",{ +charmap_but: "Visual keyboard", +charmap_title: "Visual keyboard", +charmap_choose_block: "select language block", +charmap_insert:"insert this character" +}); ADDED applications/admin/static/edit_area/plugins/charmap/langs/en.js Index: applications/admin/static/edit_area/plugins/charmap/langs/en.js ================================================================== --- applications/admin/static/edit_area/plugins/charmap/langs/en.js +++ applications/admin/static/edit_area/plugins/charmap/langs/en.js @@ -0,0 +1,6 @@ +editArea.add_lang("en",{ +charmap_but: "Visual keyboard", +charmap_title: "Visual keyboard", +charmap_choose_block: "select language block", +charmap_insert:"insert this character" +}); ADDED applications/admin/static/edit_area/plugins/charmap/langs/eo.js Index: applications/admin/static/edit_area/plugins/charmap/langs/eo.js ================================================================== --- applications/admin/static/edit_area/plugins/charmap/langs/eo.js +++ applications/admin/static/edit_area/plugins/charmap/langs/eo.js @@ -0,0 +1,6 @@ +editArea.add_lang("eo",{ +charmap_but: "Ekranklavaro", +charmap_title: "Ekranklavaro", +charmap_choose_block: "Elekto de lingvo", +charmap_insert:"enmeti tiun signaron" +}); ADDED applications/admin/static/edit_area/plugins/charmap/langs/es.js Index: applications/admin/static/edit_area/plugins/charmap/langs/es.js ================================================================== --- applications/admin/static/edit_area/plugins/charmap/langs/es.js +++ applications/admin/static/edit_area/plugins/charmap/langs/es.js @@ -0,0 +1,6 @@ +editArea.add_lang("es",{ +charmap_but: "Visual keyboard", +charmap_title: "Visual keyboard", +charmap_choose_block: "select language block", +charmap_insert:"insert this character" +}); ADDED applications/admin/static/edit_area/plugins/charmap/langs/fr.js Index: applications/admin/static/edit_area/plugins/charmap/langs/fr.js ================================================================== --- applications/admin/static/edit_area/plugins/charmap/langs/fr.js +++ applications/admin/static/edit_area/plugins/charmap/langs/fr.js @@ -0,0 +1,6 @@ +editArea.add_lang("fr",{ +charmap_but: "Clavier visuel", +charmap_title: "Clavier visuel", +charmap_choose_block: "choix du language", +charmap_insert:"insérer ce caractère" +}); ADDED applications/admin/static/edit_area/plugins/charmap/langs/hr.js Index: applications/admin/static/edit_area/plugins/charmap/langs/hr.js ================================================================== --- applications/admin/static/edit_area/plugins/charmap/langs/hr.js +++ applications/admin/static/edit_area/plugins/charmap/langs/hr.js @@ -0,0 +1,6 @@ +editArea.add_lang("hr",{ +charmap_but: "Virtualna tipkovnica", +charmap_title: "Virtualna tipkovnica", +charmap_choose_block: "Odaberi blok s jezikom", +charmap_insert:"Ubaci taj znak" +}); ADDED applications/admin/static/edit_area/plugins/charmap/langs/it.js Index: applications/admin/static/edit_area/plugins/charmap/langs/it.js ================================================================== --- applications/admin/static/edit_area/plugins/charmap/langs/it.js +++ applications/admin/static/edit_area/plugins/charmap/langs/it.js @@ -0,0 +1,6 @@ +editArea.add_lang("it",{ +charmap_but: "Tastiera visuale", +charmap_title: "Tastiera visuale", +charmap_choose_block: "seleziona blocco", +charmap_insert:"inserisci questo carattere" +}); ADDED applications/admin/static/edit_area/plugins/charmap/langs/ja.js Index: applications/admin/static/edit_area/plugins/charmap/langs/ja.js ================================================================== --- applications/admin/static/edit_area/plugins/charmap/langs/ja.js +++ applications/admin/static/edit_area/plugins/charmap/langs/ja.js @@ -0,0 +1,6 @@ +editArea.add_lang("ja",{ +charmap_but: "Visual keyboard", +charmap_title: "Visual keyboard", +charmap_choose_block: "select language block", +charmap_insert:"insert this character" +}); ADDED applications/admin/static/edit_area/plugins/charmap/langs/mk.js Index: applications/admin/static/edit_area/plugins/charmap/langs/mk.js ================================================================== --- applications/admin/static/edit_area/plugins/charmap/langs/mk.js +++ applications/admin/static/edit_area/plugins/charmap/langs/mk.js @@ -0,0 +1,6 @@ +editArea.add_lang("mkn",{ +charmap_but: "Visual keyboard", +charmap_title: "Visual keyboard", +charmap_choose_block: "select language block", +charmap_insert:"insert this character" +}); ADDED applications/admin/static/edit_area/plugins/charmap/langs/nl.js Index: applications/admin/static/edit_area/plugins/charmap/langs/nl.js ================================================================== --- applications/admin/static/edit_area/plugins/charmap/langs/nl.js +++ applications/admin/static/edit_area/plugins/charmap/langs/nl.js @@ -0,0 +1,6 @@ +editArea.add_lang("nl",{ +charmap_but: "Visueel toetsenbord", +charmap_title: "Visueel toetsenbord", +charmap_choose_block: "Kies een taal blok", +charmap_insert:"Voeg dit symbool in" +}); ADDED applications/admin/static/edit_area/plugins/charmap/langs/pl.js Index: applications/admin/static/edit_area/plugins/charmap/langs/pl.js ================================================================== --- applications/admin/static/edit_area/plugins/charmap/langs/pl.js +++ applications/admin/static/edit_area/plugins/charmap/langs/pl.js @@ -0,0 +1,6 @@ +editArea.add_lang("pl",{ +charmap_but: "Klawiatura ekranowa", +charmap_title: "Klawiatura ekranowa", +charmap_choose_block: "wybierz grupę znaków", +charmap_insert:"wstaw ten znak" +}); ADDED applications/admin/static/edit_area/plugins/charmap/langs/pt.js Index: applications/admin/static/edit_area/plugins/charmap/langs/pt.js ================================================================== --- applications/admin/static/edit_area/plugins/charmap/langs/pt.js +++ applications/admin/static/edit_area/plugins/charmap/langs/pt.js @@ -0,0 +1,6 @@ +editArea.add_lang("pt",{ +charmap_but: "Visual keyboard", +charmap_title: "Visual keyboard", +charmap_choose_block: "select language block", +charmap_insert:"insert this character" +}); ADDED applications/admin/static/edit_area/plugins/charmap/langs/ru.js Index: applications/admin/static/edit_area/plugins/charmap/langs/ru.js ================================================================== --- applications/admin/static/edit_area/plugins/charmap/langs/ru.js +++ applications/admin/static/edit_area/plugins/charmap/langs/ru.js @@ -0,0 +1,6 @@ +editArea.add_lang("ru",{ +charmap_but: "Визуальная клавиатура", +charmap_title: "Визуальная клавиатура", +charmap_choose_block: "выбрать языковой блок", +charmap_insert:"вставить этот символ" +}); ADDED applications/admin/static/edit_area/plugins/charmap/langs/sk.js Index: applications/admin/static/edit_area/plugins/charmap/langs/sk.js ================================================================== --- applications/admin/static/edit_area/plugins/charmap/langs/sk.js +++ applications/admin/static/edit_area/plugins/charmap/langs/sk.js @@ -0,0 +1,6 @@ +editArea.add_lang("sk",{ +charmap_but: "Vizuálna klávesnica", +charmap_title: "Vizuálna klávesnica", +charmap_choose_block: "vyber jazykový blok", +charmap_insert: "vlož tento znak" +}); ADDED applications/admin/static/edit_area/plugins/charmap/langs/zh.js Index: applications/admin/static/edit_area/plugins/charmap/langs/zh.js ================================================================== --- applications/admin/static/edit_area/plugins/charmap/langs/zh.js +++ applications/admin/static/edit_area/plugins/charmap/langs/zh.js @@ -0,0 +1,6 @@ +editArea.add_lang("zh",{ +charmap_but: "软键盘", +charmap_title: "软键盘", +charmap_choose_block: "选择一个语言块", +charmap_insert:"插入此字符" +}); ADDED applications/admin/static/edit_area/plugins/charmap/popup.html Index: applications/admin/static/edit_area/plugins/charmap/popup.html ================================================================== --- applications/admin/static/edit_area/plugins/charmap/popup.html +++ applications/admin/static/edit_area/plugins/charmap/popup.html @@ -0,0 +1,24 @@ + + + + +{$charmap_title} + + + + + +
+
+

{$charmap_title}:

+ +
+ +
+ + + + + ADDED applications/admin/static/edit_area/plugins/test/css/test.css Index: applications/admin/static/edit_area/plugins/test/css/test.css ================================================================== --- applications/admin/static/edit_area/plugins/test/css/test.css +++ applications/admin/static/edit_area/plugins/test/css/test.css @@ -0,0 +1,3 @@ +select#test_select{ + background-color: #FF0000; +} ADDED applications/admin/static/edit_area/plugins/test/images/test.gif Index: applications/admin/static/edit_area/plugins/test/images/test.gif ================================================================== --- applications/admin/static/edit_area/plugins/test/images/test.gif +++ applications/admin/static/edit_area/plugins/test/images/test.gif cannot compute difference between binary files ADDED applications/admin/static/edit_area/plugins/test/langs/bg.js Index: applications/admin/static/edit_area/plugins/test/langs/bg.js ================================================================== --- applications/admin/static/edit_area/plugins/test/langs/bg.js +++ applications/admin/static/edit_area/plugins/test/langs/bg.js @@ -0,0 +1,10 @@ +/* + * Bulgarian translation + * Author: Valentin Hristov + * Company: SOFTKIT Bulgarian + * Site: http://www.softkit-bg.com + */ +editArea.add_lang("bg",{ +test_select: "избери таг", +test_but: "тествай копието" +}); ADDED applications/admin/static/edit_area/plugins/test/langs/cs.js Index: applications/admin/static/edit_area/plugins/test/langs/cs.js ================================================================== --- applications/admin/static/edit_area/plugins/test/langs/cs.js +++ applications/admin/static/edit_area/plugins/test/langs/cs.js @@ -0,0 +1,4 @@ +editArea.add_lang("cs",{ +test_select: "select tag", +test_but: "test button" +}); ADDED applications/admin/static/edit_area/plugins/test/langs/de.js Index: applications/admin/static/edit_area/plugins/test/langs/de.js ================================================================== --- applications/admin/static/edit_area/plugins/test/langs/de.js +++ applications/admin/static/edit_area/plugins/test/langs/de.js @@ -0,0 +1,4 @@ +editArea.add_lang("de",{ +test_select: "Tag auswählen", +test_but: "Test Button" +}); ADDED applications/admin/static/edit_area/plugins/test/langs/dk.js Index: applications/admin/static/edit_area/plugins/test/langs/dk.js ================================================================== --- applications/admin/static/edit_area/plugins/test/langs/dk.js +++ applications/admin/static/edit_area/plugins/test/langs/dk.js @@ -0,0 +1,4 @@ +editArea.add_lang("dk",{ +test_select: "select tag", +test_but: "test button" +}); ADDED applications/admin/static/edit_area/plugins/test/langs/en.js Index: applications/admin/static/edit_area/plugins/test/langs/en.js ================================================================== --- applications/admin/static/edit_area/plugins/test/langs/en.js +++ applications/admin/static/edit_area/plugins/test/langs/en.js @@ -0,0 +1,4 @@ +editArea.add_lang("en",{ +test_select: "select tag", +test_but: "test button" +}); ADDED applications/admin/static/edit_area/plugins/test/langs/eo.js Index: applications/admin/static/edit_area/plugins/test/langs/eo.js ================================================================== --- applications/admin/static/edit_area/plugins/test/langs/eo.js +++ applications/admin/static/edit_area/plugins/test/langs/eo.js @@ -0,0 +1,4 @@ +editArea.add_lang("eo",{ +test_select:"elekto de marko", +test_but: "provo-butono" +}); ADDED applications/admin/static/edit_area/plugins/test/langs/es.js Index: applications/admin/static/edit_area/plugins/test/langs/es.js ================================================================== --- applications/admin/static/edit_area/plugins/test/langs/es.js +++ applications/admin/static/edit_area/plugins/test/langs/es.js @@ -0,0 +1,4 @@ +editArea.add_lang("es",{ +test_select: "select tag", +test_but: "test button" +}); ADDED applications/admin/static/edit_area/plugins/test/langs/fr.js Index: applications/admin/static/edit_area/plugins/test/langs/fr.js ================================================================== --- applications/admin/static/edit_area/plugins/test/langs/fr.js +++ applications/admin/static/edit_area/plugins/test/langs/fr.js @@ -0,0 +1,4 @@ +editArea.add_lang("fr",{ +test_select:"choix balise", +test_but: "bouton de test" +}); ADDED applications/admin/static/edit_area/plugins/test/langs/hr.js Index: applications/admin/static/edit_area/plugins/test/langs/hr.js ================================================================== --- applications/admin/static/edit_area/plugins/test/langs/hr.js +++ applications/admin/static/edit_area/plugins/test/langs/hr.js @@ -0,0 +1,4 @@ +editArea.add_lang("hr",{ +test_select: "Odaberi tag", +test_but: "Probna tipka" +}); ADDED applications/admin/static/edit_area/plugins/test/langs/it.js Index: applications/admin/static/edit_area/plugins/test/langs/it.js ================================================================== --- applications/admin/static/edit_area/plugins/test/langs/it.js +++ applications/admin/static/edit_area/plugins/test/langs/it.js @@ -0,0 +1,4 @@ +editArea.add_lang("it",{ +test_select: "seleziona tag", +test_but: "pulsante di test" +}); ADDED applications/admin/static/edit_area/plugins/test/langs/ja.js Index: applications/admin/static/edit_area/plugins/test/langs/ja.js ================================================================== --- applications/admin/static/edit_area/plugins/test/langs/ja.js +++ applications/admin/static/edit_area/plugins/test/langs/ja.js @@ -0,0 +1,4 @@ +editArea.add_lang("ja",{ +test_select: "select tag", +test_but: "test button" +}); ADDED applications/admin/static/edit_area/plugins/test/langs/mk.js Index: applications/admin/static/edit_area/plugins/test/langs/mk.js ================================================================== --- applications/admin/static/edit_area/plugins/test/langs/mk.js +++ applications/admin/static/edit_area/plugins/test/langs/mk.js @@ -0,0 +1,4 @@ +editArea.add_lang("mk",{ +test_select: "select tag", +test_but: "test button" +}); ADDED applications/admin/static/edit_area/plugins/test/langs/nl.js Index: applications/admin/static/edit_area/plugins/test/langs/nl.js ================================================================== --- applications/admin/static/edit_area/plugins/test/langs/nl.js +++ applications/admin/static/edit_area/plugins/test/langs/nl.js @@ -0,0 +1,4 @@ +editArea.add_lang("nl",{ +test_select: "select tag", +test_but: "test button" +}); ADDED applications/admin/static/edit_area/plugins/test/langs/pl.js Index: applications/admin/static/edit_area/plugins/test/langs/pl.js ================================================================== --- applications/admin/static/edit_area/plugins/test/langs/pl.js +++ applications/admin/static/edit_area/plugins/test/langs/pl.js @@ -0,0 +1,4 @@ +editArea.add_lang("pl",{ +test_select: "wybierz tag", +test_but: "test" +}); ADDED applications/admin/static/edit_area/plugins/test/langs/pt.js Index: applications/admin/static/edit_area/plugins/test/langs/pt.js ================================================================== --- applications/admin/static/edit_area/plugins/test/langs/pt.js +++ applications/admin/static/edit_area/plugins/test/langs/pt.js @@ -0,0 +1,4 @@ +editArea.add_lang("pt",{ +test_select: "select tag", +test_but: "test button" +}); ADDED applications/admin/static/edit_area/plugins/test/langs/ru.js Index: applications/admin/static/edit_area/plugins/test/langs/ru.js ================================================================== --- applications/admin/static/edit_area/plugins/test/langs/ru.js +++ applications/admin/static/edit_area/plugins/test/langs/ru.js @@ -0,0 +1,4 @@ +editArea.add_lang("ru",{ +test_select: "выбрать тэг", +test_but: "тестировать кнопку" +}); ADDED applications/admin/static/edit_area/plugins/test/langs/sk.js Index: applications/admin/static/edit_area/plugins/test/langs/sk.js ================================================================== --- applications/admin/static/edit_area/plugins/test/langs/sk.js +++ applications/admin/static/edit_area/plugins/test/langs/sk.js @@ -0,0 +1,4 @@ +editArea.add_lang("sk",{ +test_select: "vyber tag", +test_but: "testovacie tlačidlo" +}); ADDED applications/admin/static/edit_area/plugins/test/langs/zh.js Index: applications/admin/static/edit_area/plugins/test/langs/zh.js ================================================================== --- applications/admin/static/edit_area/plugins/test/langs/zh.js +++ applications/admin/static/edit_area/plugins/test/langs/zh.js @@ -0,0 +1,4 @@ +editArea.add_lang("zh",{ +test_select: "选择标签", +test_but: "测试按钮" +}); ADDED applications/admin/static/edit_area/plugins/test/test.js Index: applications/admin/static/edit_area/plugins/test/test.js ================================================================== --- applications/admin/static/edit_area/plugins/test/test.js +++ applications/admin/static/edit_area/plugins/test/test.js @@ -0,0 +1,110 @@ +/** + * Plugin designed for test prupose. It add a button (that manage an alert) and a select (that allow to insert tags) in the toolbar. + * This plugin also disable the "f" key in the editarea, and load a CSS and a JS file + */ +var EditArea_test= { + /** + * Get called once this file is loaded (editArea still not initialized) + * + * @return nothing + */ + init: function(){ + // alert("test init: "+ this._someInternalFunction(2, 3)); + editArea.load_css(this.baseURL+"css/test.css"); + editArea.load_script(this.baseURL+"test2.js"); + } + /** + * Returns the HTML code for a specific control string or false if this plugin doesn't have that control. + * A control can be a button, select list or any other HTML item to present in the EditArea user interface. + * Language variables such as {$lang_somekey} will also be replaced with contents from + * the language packs. + * + * @param {string} ctrl_name: the name of the control to add + * @return HTML code for a specific control or false. + * @type string or boolean + */ + ,get_control_html: function(ctrl_name){ + switch(ctrl_name){ + case "test_but": + // Control id, button img, command + return parent.editAreaLoader.get_button_html('test_but', 'test.gif', 'test_cmd', false, this.baseURL); + case "test_select": + html= ""; + return html; + } + return false; + } + /** + * Get called once EditArea is fully loaded and initialised + * + * @return nothing + */ + ,onload: function(){ + alert("test load"); + } + + /** + * Is called each time the user touch a keyboard key. + * + * @param (event) e: the keydown event + * @return true - pass to next handler in chain, false - stop chain execution + * @type boolean + */ + ,onkeydown: function(e){ + var str= String.fromCharCode(e.keyCode); + // desactivate the "f" character + if(str.toLowerCase()=="f"){ + return true; + } + return false; + } + + /** + * Executes a specific command, this function handles plugin commands. + * + * @param {string} cmd: the name of the command being executed + * @param {unknown} param: the parameter of the command + * @return true - pass to next handler in chain, false - stop chain execution + * @type boolean + */ + ,execCommand: function(cmd, param){ + // Handle commands + switch(cmd){ + case "test_select_change": + var val= document.getElementById("test_select").value; + if(val!=-1) + parent.editAreaLoader.insertTags(editArea.id, "<"+val+">", ""); + document.getElementById("test_select").options[0].selected=true; + return false; + case "test_cmd": + alert("user clicked on test_cmd"); + return false; + } + // Pass to next handler in chain + return true; + } + + /** + * This is just an internal plugin method, prefix all internal methods with a _ character. + * The prefix is needed so they doesn't collide with future EditArea callback functions. + * + * @param {string} a Some arg1. + * @param {string} b Some arg2. + * @return Some return. + * @type unknown + */ + ,_someInternalFunction : function(a, b) { + return a+b; + } +}; + +// Adds the plugin class to the list of available EditArea plugins +editArea.add_plugin("test", EditArea_test); ADDED applications/admin/static/edit_area/plugins/test/test2.js Index: applications/admin/static/edit_area/plugins/test/test2.js ================================================================== --- applications/admin/static/edit_area/plugins/test/test2.js +++ applications/admin/static/edit_area/plugins/test/test2.js @@ -0,0 +1,1 @@ +alert("test2.js is loaded from test plugin"); ADDED applications/admin/static/edit_area/plugins/zencoding/core.js Index: applications/admin/static/edit_area/plugins/zencoding/core.js ================================================================== --- applications/admin/static/edit_area/plugins/zencoding/core.js +++ applications/admin/static/edit_area/plugins/zencoding/core.js @@ -0,0 +1,4 @@ +var zen_settings={"variables":{"lang":"en","locale":"en-US","charset":"UTF-8","profile":"xhtml","indentation":"\t"},"css":{"snippets":{"@i":"@import url(|);","@m":"@media print {\n\t|\n}","@f":"@font-face {\n\tfont-family:|;\n\tsrc:url(|);\n}","!":"!important","pos":"position:|;","pos:s":"position:static;","pos:a":"position:absolute;","pos:r":"position:relative;","pos:f":"position:fixed;","t":"top:|;","t:a":"top:auto;","r":"right:|;","r:a":"right:auto;","b":"bottom:|;","b:a":"bottom:auto;","l":"left:|;","l:a":"left:auto;","z":"z-index:|;","z:a":"z-index:auto;","fl":"float:|;","fl:n":"float:none;","fl:l":"float:left;","fl:r":"float:right;","cl":"clear:|;","cl:n":"clear:none;","cl:l":"clear:left;","cl:r":"clear:right;","cl:b":"clear:both;","d":"display:|;","d:n":"display:none;","d:b":"display:block;","d:ib":"display:inline;","d:li":"display:list-item;","d:ri":"display:run-in;","d:cp":"display:compact;","d:tb":"display:table;","d:itb":"display:inline-table;","d:tbcp":"display:table-caption;","d:tbcl":"display:table-column;","d:tbclg":"display:table-column-group;","d:tbhg":"display:table-header-group;","d:tbfg":"display:table-footer-group;","d:tbr":"display:table-row;","d:tbrg":"display:table-row-group;","d:tbc":"display:table-cell;","d:rb":"display:ruby;","d:rbb":"display:ruby-base;","d:rbbg":"display:ruby-base-group;","d:rbt":"display:ruby-text;","d:rbtg":"display:ruby-text-group;","v":"visibility:|;","v:v":"visibility:visible;","v:h":"visibility:hidden;","v:c":"visibility:collapse;","ov":"overflow:|;","ov:v":"overflow:visible;","ov:h":"overflow:hidden;","ov:s":"overflow:scroll;","ov:a":"overflow:auto;","ovx":"overflow-x:|;","ovx:v":"overflow-x:visible;","ovx:h":"overflow-x:hidden;","ovx:s":"overflow-x:scroll;","ovx:a":"overflow-x:auto;","ovy":"overflow-y:|;","ovy:v":"overflow-y:visible;","ovy:h":"overflow-y:hidden;","ovy:s":"overflow-y:scroll;","ovy:a":"overflow-y:auto;","ovs":"overflow-style:|;","ovs:a":"overflow-style:auto;","ovs:s":"overflow-style:scrollbar;","ovs:p":"overflow-style:panner;","ovs:m":"overflow-style:move;","ovs:mq":"overflow-style:marquee;","zoo":"zoom:1;","cp":"clip:|;","cp:a":"clip:auto;","cp:r":"clip:rect(|);","bxz":"box-sizing:|;","bxz:cb":"box-sizing:content-box;","bxz:bb":"box-sizing:border-box;","bxsh":"box-shadow:|;","bxsh:n":"box-shadow:none;","bxsh:w":"-webkit-box-shadow:0 0 0 #000;","bxsh:m":"-moz-box-shadow:0 0 0 0 #000;","m":"margin:|;","m:a":"margin:auto;","m:0":"margin:0;","m:2":"margin:0 0;","m:3":"margin:0 0 0;","m:4":"margin:0 0 0 0;","mt":"margin-top:|;","mt:a":"margin-top:auto;","mr":"margin-right:|;","mr:a":"margin-right:auto;","mb":"margin-bottom:|;","mb:a":"margin-bottom:auto;","ml":"margin-left:|;","ml:a":"margin-left:auto;","p":"padding:|;","p:0":"padding:0;","p:2":"padding:0 0;","p:3":"padding:0 0 0;","p:4":"padding:0 0 0 0;","pt":"padding-top:|;","pr":"padding-right:|;","pb":"padding-bottom:|;","pl":"padding-left:|;","w":"width:|;","w:a":"width:auto;","h":"height:|;","h:a":"height:auto;","maw":"max-width:|;","maw:n":"max-width:none;","mah":"max-height:|;","mah:n":"max-height:none;","miw":"min-width:|;","mih":"min-height:|;","o":"outline:|;","o:n":"outline:none;","oo":"outline-offset:|;","ow":"outline-width:|;","os":"outline-style:|;","oc":"outline-color:#000;","oc:i":"outline-color:invert;","bd":"border:|;","bd+":"border:1px solid #000;","bd:n":"border:none;","bdbk":"border-break:|;","bdbk:c":"border-break:close;","bdcl":"border-collapse:|;","bdcl:c":"border-collapse:collapse;","bdcl:s":"border-collapse:separate;","bdc":"border-color:#000;","bdi":"border-image:url(|);","bdi:n":"border-image:none;","bdi:w":"-webkit-border-image:url(|) 0 0 0 0 stretch stretch;","bdi:m":"-moz-border-image:url(|) 0 0 0 0 stretch stretch;","bdti":"border-top-image:url(|);","bdti:n":"border-top-image:none;","bdri":"border-right-image:url(|);","bdri:n":"border-right-image:none;","bdbi":"border-bottom-image:url(|);","bdbi:n":"border-bottom-image:none;","bdli":"border-left-image:url(|);","bdli:n":"border-left-image:none;","bdci":"border-corner-image:url(|);","bdci:n":"border-corner-image:none;","bdci:c":"border-corner-image:continue;","bdtli":"border-top-left-image:url(|);","bdtli:n":"border-top-left-image:none;","bdtli:c":"border-top-left-image:continue;","bdtri":"border-top-right-image:url(|);","bdtri:n":"border-top-right-image:none;","bdtri:c":"border-top-right-image:continue;","bdbri":"border-bottom-right-image:url(|);","bdbri:n":"border-bottom-right-image:none;","bdbri:c":"border-bottom-right-image:continue;","bdbli":"border-bottom-left-image:url(|);","bdbli:n":"border-bottom-left-image:none;","bdbli:c":"border-bottom-left-image:continue;","bdf":"border-fit:|;","bdf:c":"border-fit:clip;","bdf:r":"border-fit:repeat;","bdf:sc":"border-fit:scale;","bdf:st":"border-fit:stretch;","bdf:ow":"border-fit:overwrite;","bdf:of":"border-fit:overflow;","bdf:sp":"border-fit:space;","bdl":"border-length:|;","bdl:a":"border-length:auto;","bdsp":"border-spacing:|;","bds":"border-style:|;","bds:n":"border-style:none;","bds:h":"border-style:hidden;","bds:dt":"border-style:dotted;","bds:ds":"border-style:dashed;","bds:s":"border-style:solid;","bds:db":"border-style:double;","bds:dtds":"border-style:dot-dash;","bds:dtdtds":"border-style:dot-dot-dash;","bds:w":"border-style:wave;","bds:g":"border-style:groove;","bds:r":"border-style:ridge;","bds:i":"border-style:inset;","bds:o":"border-style:outset;","bdw":"border-width:|;","bdt":"border-top:|;","bdt+":"border-top:1px solid #000;","bdt:n":"border-top:none;","bdtw":"border-top-width:|;","bdts":"border-top-style:|;","bdts:n":"border-top-style:none;","bdtc":"border-top-color:#000;","bdr":"border-right:|;","bdr+":"border-right:1px solid #000;","bdr:n":"border-right:none;","bdrw":"border-right-width:|;","bdrs":"border-right-style:|;","bdrs:n":"border-right-style:none;","bdrc":"border-right-color:#000;","bdb":"border-bottom:|;","bdb+":"border-bottom:1px solid #000;","bdb:n":"border-bottom:none;","bdbw":"border-bottom-width:|;","bdbs":"border-bottom-style:|;","bdbs:n":"border-bottom-style:none;","bdbc":"border-bottom-color:#000;","bdl":"border-left:|;","bdl+":"border-left:1px solid #000;","bdl:n":"border-left:none;","bdlw":"border-left-width:|;","bdls":"border-left-style:|;","bdls:n":"border-left-style:none;","bdlc":"border-left-color:#000;","bdrs":"border-radius:|;","bdtrrs":"border-top-right-radius:|;","bdtlrs":"border-top-left-radius:|;","bdbrrs":"border-bottom-right-radius:|;","bdblrs":"border-bottom-left-radius:|;","bg":"background:|;","bg+":"background:#FFF url(|) 0 0 no-repeat;","bg:n":"background:none;","bg:ie":"filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='|x.png');","bgc":"background-color:#FFF;","bgi":"background-image:url(|);","bgi:n":"background-image:none;","bgr":"background-repeat:|;","bgr:n":"background-repeat:no-repeat;","bgr:x":"background-repeat:repeat-x;","bgr:y":"background-repeat:repeat-y;","bga":"background-attachment:|;","bga:f":"background-attachment:fixed;","bga:s":"background-attachment:scroll;","bgp":"background-position:0 0;","bgpx":"background-position-x:|;","bgpy":"background-position-y:|;","bgbk":"background-break:|;","bgbk:bb":"background-break:bounding-box;","bgbk:eb":"background-break:each-box;","bgbk:c":"background-break:continuous;","bgcp":"background-clip:|;","bgcp:bb":"background-clip:border-box;","bgcp:pb":"background-clip:padding-box;","bgcp:cb":"background-clip:content-box;","bgcp:nc":"background-clip:no-clip;","bgo":"background-origin:|;","bgo:pb":"background-origin:padding-box;","bgo:bb":"background-origin:border-box;","bgo:cb":"background-origin:content-box;","bgz":"background-size:|;","bgz:a":"background-size:auto;","bgz:ct":"background-size:contain;","bgz:cv":"background-size:cover;","c":"color:#000;","tbl":"table-layout:|;","tbl:a":"table-layout:auto;","tbl:f":"table-layout:fixed;","cps":"caption-side:|;","cps:t":"caption-side:top;","cps:b":"caption-side:bottom;","ec":"empty-cells:|;","ec:s":"empty-cells:show;","ec:h":"empty-cells:hide;","lis":"list-style:|;","lis:n":"list-style:none;","lisp":"list-style-position:|;","lisp:i":"list-style-position:inside;","lisp:o":"list-style-position:outside;","list":"list-style-type:|;","list:n":"list-style-type:none;","list:d":"list-style-type:disc;","list:c":"list-style-type:circle;","list:s":"list-style-type:square;","list:dc":"list-style-type:decimal;","list:dclz":"list-style-type:decimal-leading-zero;","list:lr":"list-style-type:lower-roman;","list:ur":"list-style-type:upper-roman;","lisi":"list-style-image:|;","lisi:n":"list-style-image:none;","q":"quotes:|;","q:n":"quotes:none;","q:ru":"quotes:'\00AB' '\00BB' '\201E' '\201C';","q:en":"quotes:'\201C' '\201D' '\2018' '\2019';","ct":"content:|;","ct:n":"content:normal;","ct:oq":"content:open-quote;","ct:noq":"content:no-open-quote;","ct:cq":"content:close-quote;","ct:ncq":"content:no-close-quote;","ct:a":"content:attr(|);","ct:c":"content:counter(|);","ct:cs":"content:counters(|);","coi":"counter-increment:|;","cor":"counter-reset:|;","va":"vertical-align:|;","va:sup":"vertical-align:super;","va:t":"vertical-align:top;","va:tt":"vertical-align:text-top;","va:m":"vertical-align:middle;","va:bl":"vertical-align:baseline;","va:b":"vertical-align:bottom;","va:tb":"vertical-align:text-bottom;","va:sub":"vertical-align:sub;","ta":"text-align:|;","ta:l":"text-align:left;","ta:c":"text-align:center;","ta:r":"text-align:right;","tal":"text-align-last:|;","tal:a":"text-align-last:auto;","tal:l":"text-align-last:left;","tal:c":"text-align-last:center;","tal:r":"text-align-last:right;","td":"text-decoration:|;","td:n":"text-decoration:none;","td:u":"text-decoration:underline;","td:o":"text-decoration:overline;","td:l":"text-decoration:line-through;","te":"text-emphasis:|;","te:n":"text-emphasis:none;","te:ac":"text-emphasis:accent;","te:dt":"text-emphasis:dot;","te:c":"text-emphasis:circle;","te:ds":"text-emphasis:disc;","te:b":"text-emphasis:before;","te:a":"text-emphasis:after;","th":"text-height:|;","th:a":"text-height:auto;","th:f":"text-height:font-size;","th:t":"text-height:text-size;","th:m":"text-height:max-size;","ti":"text-indent:|;","ti:-":"text-indent:-9999px;","tj":"text-justify:|;","tj:a":"text-justify:auto;","tj:iw":"text-justify:inter-word;","tj:ii":"text-justify:inter-ideograph;","tj:ic":"text-justify:inter-cluster;","tj:d":"text-justify:distribute;","tj:k":"text-justify:kashida;","tj:t":"text-justify:tibetan;","to":"text-outline:|;","to+":"text-outline:0 0 #000;","to:n":"text-outline:none;","tr":"text-replace:|;","tr:n":"text-replace:none;","tt":"text-transform:|;","tt:n":"text-transform:none;","tt:c":"text-transform:capitalize;","tt:u":"text-transform:uppercase;","tt:l":"text-transform:lowercase;","tw":"text-wrap:|;","tw:n":"text-wrap:normal;","tw:no":"text-wrap:none;","tw:u":"text-wrap:unrestricted;","tw:s":"text-wrap:suppress;","tsh":"text-shadow:|;","tsh+":"text-shadow:0 0 0 #000;","tsh:n":"text-shadow:none;","lh":"line-height:|;","whs":"white-space:|;","whs:n":"white-space:normal;","whs:p":"white-space:pre;","whs:nw":"white-space:nowrap;","whs:pw":"white-space:pre-wrap;","whs:pl":"white-space:pre-line;","whsc":"white-space-collapse:|;","whsc:n":"white-space-collapse:normal;","whsc:k":"white-space-collapse:keep-all;","whsc:l":"white-space-collapse:loose;","whsc:bs":"white-space-collapse:break-strict;","whsc:ba":"white-space-collapse:break-all;","wob":"word-break:|;","wob:n":"word-break:normal;","wob:k":"word-break:keep-all;","wob:l":"word-break:loose;","wob:bs":"word-break:break-strict;","wob:ba":"word-break:break-all;","wos":"word-spacing:|;","wow":"word-wrap:|;","wow:nm":"word-wrap:normal;","wow:n":"word-wrap:none;","wow:u":"word-wrap:unrestricted;","wow:s":"word-wrap:suppress;","lts":"letter-spacing:|;","f":"font:|;","f+":"font:1em Arial,sans-serif;","fw":"font-weight:|;","fw:n":"font-weight:normal;","fw:b":"font-weight:bold;","fw:br":"font-weight:bolder;","fw:lr":"font-weight:lighter;","fs":"font-style:|;","fs:n":"font-style:normal;","fs:i":"font-style:italic;","fs:o":"font-style:oblique;","fv":"font-variant:|;","fv:n":"font-variant:normal;","fv:sc":"font-variant:small-caps;","fz":"font-size:|;","fza":"font-size-adjust:|;","fza:n":"font-size-adjust:none;","ff":"font-family:|;","ff:s":"font-family:serif;","ff:ss":"font-family:sans-serif;","ff:c":"font-family:cursive;","ff:f":"font-family:fantasy;","ff:m":"font-family:monospace;","fef":"font-effect:|;","fef:n":"font-effect:none;","fef:eg":"font-effect:engrave;","fef:eb":"font-effect:emboss;","fef:o":"font-effect:outline;","fem":"font-emphasize:|;","femp":"font-emphasize-position:|;","femp:b":"font-emphasize-position:before;","femp:a":"font-emphasize-position:after;","fems":"font-emphasize-style:|;","fems:n":"font-emphasize-style:none;","fems:ac":"font-emphasize-style:accent;","fems:dt":"font-emphasize-style:dot;","fems:c":"font-emphasize-style:circle;","fems:ds":"font-emphasize-style:disc;","fsm":"font-smooth:|;","fsm:a":"font-smooth:auto;","fsm:n":"font-smooth:never;","fsm:aw":"font-smooth:always;","fst":"font-stretch:|;","fst:n":"font-stretch:normal;","fst:uc":"font-stretch:ultra-condensed;","fst:ec":"font-stretch:extra-condensed;","fst:c":"font-stretch:condensed;","fst:sc":"font-stretch:semi-condensed;","fst:se":"font-stretch:semi-expanded;","fst:e":"font-stretch:expanded;","fst:ee":"font-stretch:extra-expanded;","fst:ue":"font-stretch:ultra-expanded;","op":"opacity:|;","op:ie":"filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=100);","op:ms":"-ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=100)';","rz":"resize:|;","rz:n":"resize:none;","rz:b":"resize:both;","rz:h":"resize:horizontal;","rz:v":"resize:vertical;","cur":"cursor:|;","cur:a":"cursor:auto;","cur:d":"cursor:default;","cur:c":"cursor:crosshair;","cur:ha":"cursor:hand;","cur:he":"cursor:help;","cur:m":"cursor:move;","cur:p":"cursor:pointer;","cur:t":"cursor:text;","pgbb":"page-break-before:|;","pgbb:au":"page-break-before:auto;","pgbb:al":"page-break-before:always;","pgbb:l":"page-break-before:left;","pgbb:r":"page-break-before:right;","pgbi":"page-break-inside:|;","pgbi:au":"page-break-inside:auto;","pgbi:av":"page-break-inside:avoid;","pgba":"page-break-after:|;","pgba:au":"page-break-after:auto;","pgba:al":"page-break-after:always;","pgba:l":"page-break-after:left;","pgba:r":"page-break-after:right;","orp":"orphans:|;","wid":"widows:|;"}},"html":{"snippets":{"cc:ie6":"","cc:ie":"","cc:noie":"\n\t${child}|\n","html:4t":'\n'+'\n'+"\n"+" \n"+' \n'+"\n"+"\n\t${child}|\n\n"+"","html:4s":'\n'+'\n'+"\n"+" \n"+' \n'+"\n"+"\n\t${child}|\n\n"+"","html:xt":'\n'+'\n'+"\n"+" \n"+' \n'+"\n"+"\n\t${child}|\n\n"+"","html:xs":'\n'+'\n'+"\n"+" \n"+' \n'+"\n"+"\n\t${child}|\n\n"+"","html:xxs":'\n'+'\n'+"\n"+" \n"+' \n'+"\n"+"\n\t${child}|\n\n"+"","html:5":"\n"+'\n'+"\n"+" \n"+' \n'+"\n"+"\n\t${child}|\n\n"+""},"abbreviations":{"a":'',"a:link":'',"a:mail":'',"abbr":'',"acronym":'',"base":'',"bdo":'',"bdo:r":'',"bdo:l":'',"link:css":'',"link:print":'',"link:favicon":'',"link:touch":'',"link:rss":'',"link:atom":'',"meta:utf":'',"meta:win":'',"meta:compat":'',"style":'',"script":' + +{{if request.function=='index':}} +

{{=T("Available databases and tables")}}

+ {{if not databases:}}{{=T("No databases in this application")}}{{pass}} + {{for db in sorted(databases):}} + {{for table in databases[db].tables:}} + {{qry='%s.%s.id>0'%(db,table)}} + {{tbl=databases[db][table]}} + {{if hasattr(tbl,'_primarykey'):}} + {{if tbl._primarykey:}} + {{firstkey=tbl[tbl._primarykey[0]]}} + {{if firstkey.type in ['string','text']:}} + {{qry='%s.%s.%s!=""'%(db,table,firstkey.name)}} + {{else:}} + {{qry='%s.%s.%s>0'%(db,table,firstkey.name)}} + {{pass}} + {{else:}} + {{qry=''}} + {{pass}} + {{pass}} +

{{=A("%s.%s" % (db,table),_href=URL('select',args=[db],vars=dict(query=qry)))}} +

+ [ {{=A(str(T('insert new'))+' '+table,_href=URL('insert',args=[db,table]))}} ] +

+ {{pass}} + {{pass}} + +{{elif request.function=='select':}} +

{{=XML(str(T("database %s select"))%A(request.args[0],_href=URL('index'))) }} +

+ {{if table:}} + [ {{=A(str(T('insert new %s'))%table,_href=URL('insert',args=[request.args[0],table]))}} ]

+

{{=T("Rows in table")}}


+ {{else:}} +

{{=T("Rows selected")}}


+ {{pass}} + {{=form}} +

{{=T('The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.')}}
+ {{=T('Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.')}}
+ {{=T('"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN')}}

+

+

{{=nrows}} {{=T("selected")}}

+ {{if start>0:}}[ {{=A(T('previous 100 rows'),_href=URL('select',args=request.args[0],vars=dict(start=start-100)))}} ]{{pass}} + {{if stop + {{linkto=URL('update',args=request.args[0])}} + {{upload=URL('download',args=request.args[0])}} + {{=SQLTABLE(rows,linkto,upload,orderby=True,_class='sortable')}} + + {{pass}} +

{{=T("Import/Export")}}


+ [ {{=T("export as csv file")}} ] + {{if table:}} + {{=FORM(str(T('or import from csv file'))+" ",INPUT(_type='file',_name='csvfile'),INPUT(_type='hidden',_value=table,_name='table'),INPUT(_type='submit',_value='import'))}} + {{pass}} + + +{{elif request.function=='insert':}} +

{{=T("database")}} {{=A(request.args[0],_href=URL('index'))}} + {{if hasattr(table,'_primarykey'):}} + {{fieldname=table._primarykey[0]}} + {{dbname=request.args[0]}} + {{tablename=request.args[1]}} + {{cond = table[fieldname].type in ['string','text'] and '!=""' or '>0'}} + {{=T("table")}} {{=A(tablename,_href=URL('select',args=dbname,vars=dict(query='%s.%s.%s%s'%(dbname,tablename,fieldname,cond))))}} + {{else:}} + {{=T("table")}} {{=A(request.args[1],_href=URL('select',args=request.args[0],vars=dict(query='%s.%s.id>0'%tuple(request.args[:2]))))}} + {{pass}} +

+

{{=T("New Record")}}


+ {{=form}} + + + +{{elif request.function=='update':}} +

{{=T("database")}} {{=A(request.args[0],_href=URL('index'))}} + {{if hasattr(table,'_primarykey'):}} + {{fieldname=request.vars.keys()[0]}} + {{dbname=request.args[0]}} + {{tablename=request.args[1]}} + {{cond = table[fieldname].type in ['string','text'] and '!=""' or '>0'}} + {{=T("table")}} {{=A(tablename,_href=URL('select',args=dbname,vars=dict(query='%s.%s.%s%s'%(dbname,tablename,fieldname,cond))))}} + {{=T("record")}} {{=A('%s=%s'%request.vars.items()[0],_href=URL('update',args=request.args[:2],vars=request.vars))}} + {{else:}} + {{=T("table")}} {{=A(request.args[1],_href=URL('select',args=request.args[0],vars=dict(query='%s.%s.id>0'%tuple(request.args[:2]))))}} + {{=T("record id")}} {{=A(request.args[2],_href=URL('update',args=request.args[:3]))}} + {{pass}} +

+

{{=T("Edit current record")}}



{{=form}} + + + +{{elif request.function=='state':}} +

{{=T("Internal State")}}

+

{{=T("Current request")}}

+ {{=BEAUTIFY(request)}} +

{{=T("Current response")}}

+ {{=BEAUTIFY(response)}} +

{{=T("Current session")}}

+ {{=BEAUTIFY(session)}} + + +{{elif request.function == 'ccache':}} +

Cache

+
+
+
+ Statistics +
+
+

Overview

+

+ Hit Ratio: + {{=total['ratio']}}% + ({{=total['hits']}} hits + and {{=total['misses']}} misses) +

+

+ Size of cache: + {{=total['objects']}} items, + {{=total['bytes']}} bytes + {{if total['bytes'] > 524287:}} + ({{="%.0d" % (total['bytes'] / 1048576)}} MB) + {{pass}} +

+

+ Cache contains items up to + {{="%02d" % total['oldest'][0]}} hours + {{="%02d" % total['oldest'][1]}} minutes + {{="%02d" % total['oldest'][2]}} seconds old. +

+

RAM

+

+ Hit Ratio: + {{=ram['ratio']}}% + ({{=ram['hits']}} hits + and {{=ram['misses']}} misses) +

+

+ Size of cache: + {{=ram['objects']}} items, + {{=ram['bytes']}} bytes + {{if ram['bytes'] > 524287:}} + ({{=ram['bytes'] / 1048576}} MB) + {{pass}} +

+

+ RAM contains items up to + {{="%02d" % ram['oldest'][0]}} hours + {{="%02d" % ram['oldest'][1]}} minutes + {{="%02d" % ram['oldest'][2]}} seconds old. +

+

DISK

+

+ Hit Ratio: + {{=disk['ratio']}}% + ({{=disk['hits']}} hits + and {{=disk['misses']}} misses) +

+

+ Size of cache: + {{=disk['objects']}} items, + {{=disk['bytes']}} bytes + {{if disk['bytes'] > 524287:}} + ({{=disk['bytes'] / 1048576}} MB) + {{pass}} +

+

+ DISK contains items up to + {{="%02d" % disk['oldest'][0]}} hours + {{="%02d" % disk['oldest'][1]}} minutes + {{="%02d" % disk['oldest'][2]}} seconds old. +

+
+ +
+ Manage Cache +
+
+

+ {{=form}} +

+
+
+
+
+{{pass}} ADDED applications/admin/views/debug/index.html Index: applications/admin/views/debug/index.html ================================================================== --- applications/admin/views/debug/index.html +++ applications/admin/views/debug/index.html @@ -0,0 +1,159 @@ +{{extend 'layout.html'}} + +{{block sectionclass}}shell{{end}} + + +
+ + +
+
+
>>>
+
+ + Type PDB debugger command in here and hit Return (Enter) to execute it. +
+
+
+
+ +
+
    +
  • Using the shell may lock the database to other users of this app.
  • +
+
+ + ADDED applications/admin/views/default/about.html Index: applications/admin/views/default/about.html ================================================================== --- applications/admin/views/default/about.html +++ applications/admin/views/default/about.html @@ -0,0 +1,12 @@ +{{extend 'layout.html'}} + +{{block sectionclass}}about{{end}} + +

{{=T("About application")}} "{{=app}}"

+

{{=T("About")}} {{=app}}

+

{{=button(URL('edit/%s/ABOUT' % (app)), T('edit'))}}

+
{{=about}}
+

{{=T('License for')}} {{=app}}

+

{{=button(URL('edit/%s/LICENSE' % (app)), T('edit'))}}

+
{{=license}}
+ ADDED applications/admin/views/default/amy_ajax.html Index: applications/admin/views/default/amy_ajax.html ================================================================== --- applications/admin/views/default/amy_ajax.html +++ applications/admin/views/default/amy_ajax.html @@ -0,0 +1,76 @@ + + + +{{if request.args[1]=="views":}} + +{{else:}} + +{{pass}} + + + ADDED applications/admin/views/default/change_password.html Index: applications/admin/views/default/change_password.html ================================================================== --- applications/admin/views/default/change_password.html +++ applications/admin/views/default/change_password.html @@ -0,0 +1,9 @@ +{{extend 'layout.html'}} + +{{block sectionclass}}change_password{{end}} + +

Change Admin Password

+ +
+ {{=form}} +
ADDED applications/admin/views/default/delete.html Index: applications/admin/views/default/delete.html ================================================================== --- applications/admin/views/default/delete.html +++ applications/admin/views/default/delete.html @@ -0,0 +1,8 @@ +{{extend 'layout.html'}} + +{{block sectionclass}}delete{{end}} + +
+

{{=T('Are you sure you want to delete file "%s"?', filename)}}

+

{{=FORM(INPUT(_type='submit',_name='nodelete',_value=T('Abort')),INPUT(_type='hidden',_name='sender',_value=sender), _class="inline")}}{{=FORM(INPUT(_type='submit',_name='delete',_value=T('Delete')),INPUT(_type='hidden',_name='sender',_value=sender), _class="inline")}}

+
ADDED applications/admin/views/default/delete_plugin.html Index: applications/admin/views/default/delete_plugin.html ================================================================== --- applications/admin/views/default/delete_plugin.html +++ applications/admin/views/default/delete_plugin.html @@ -0,0 +1,9 @@ +{{extend 'layout.html'}} + +{{block sectionclass}}delete_plugin{{end}} + +
+

{{=T('Are you sure you want to delete plugin "%s"?', plugin)}}

+

{{=FORM(INPUT(_type='submit',_name='nodelete',_value=T('NO')))}}

+

{{=FORM(INPUT(_type='submit',_name='delete',_value=T('YES')))}}

+
ADDED applications/admin/views/default/design.html Index: applications/admin/views/default/design.html ================================================================== --- applications/admin/views/default/design.html +++ applications/admin/views/default/design.html @@ -0,0 +1,313 @@ +{{extend 'layout.html'}} +{{ +def all(items): + return reduce(lambda a,b:a and b,items,True) +def peekfile(path,file): + return A(file.replace('\\\\','/'),_href=URL('peek', args=(app, path, file))) +def editfile(path,file): + return A(SPAN(T('edit')),_class='button editbutton',_href=URL('edit', args=(app, path, file))) +def testfile(path,file): + return A(TAG[''](IMG(_src=URL('static', 'images/test_icon.png'), _alt=T('test')), SPAN(T("Run tests in this file (to run all files, you may also use the button labelled 'test')"))), _class='icon test tooltip',_href=URL('test', args=(app, file))) +def editlanguagefile(path,file): + return A(SPAN(T('edit')),_class='button editbutton',_href=URL('edit_language', args=(app, path, file))) +def file_upload_form(location): + form=FORM(T("upload file:")," ", + INPUT(_type="file",_name="file")," ",T("and rename it:")," ", + INPUT(_type="text",_name="filename",requires=IS_NOT_EMPTY), + INPUT(_type="hidden",_name="location",_value=location), + INPUT(_type="hidden",_name="sender",_value=URL('design',args=app)), + INPUT(_type="submit",_value=T("upload")),_action=URL('upload_file')) + return form +def file_create_form(location): + form=FORM(T("create file with filename:")," ", + INPUT(_type="text",_name="filename",requires=IS_NOT_EMPTY), + INPUT(_type="hidden",_name="location",_value=location), + INPUT(_type="hidden",_name="sender",_value=URL('design',args=app)), + INPUT(_type="submit",_value=T("create")),_action=URL('create_file')) + return form +def upload_plugin_form(app): + form=FORM(T("upload plugin file:")," ", + INPUT(_type="file",_name="pluginfile"), + INPUT(_type="submit",_value=T("upload"))) + return form +def deletefile(arglist): + return A(TAG[''](IMG(_src=URL('static', 'images/delete_icon.png')), SPAN(T('Delete this file (you will be asked to confirm deletion)'))), _class='icon delete tooltip', _href=URL('delete',args=arglist,vars=dict(sender=request.function+'/'+app))) +}} + +{{block sectionclass}}design{{end}} + +

{{=T("Edit application")}} "{{=A(app,_href=URL(app,'default','index'),_target="_blank")}}"

+ + +
+

+ {{=searchbox('search')}} + {{=T("collapse/expand all")}} + + {{=button('#models', T("models"))}} + {{=button('#controllers', T("controllers"))}} + {{=button('#views', T("views"))}} + {{=button('#languages', T("languages"))}} + {{=button('#static', T("static"))}} + {{=button('#modules', T("modules"))}} + {{=button('#plugins', T("plugins"))}} + +

+
+ + + +

+ {{=T("Models")}} + {{=helpicon()}} {{=T("The data representation, define database tables and sets")}} +

+
+ {{if not models:}}

{{=T("There are no models")}}

{{else:}} +
+ {{=button(URL(a=app,c='appadmin',f='index'), T('database administration'))}} + {{if os.access(os.path.join(request.folder,'..',app,'databases','sql.log'),os.R_OK):}} + {{=button(URL('peek/%s/databases/sql.log'%app), 'sql.log')}} + {{pass}} +
+ {{pass}} + +
    + {{for m in models:}} +
  • + + {{=editfile('models',m)}} + {{=deletefile([app, 'models', m])}} + + + {{=peekfile('models',m)}} + + + {{if len(defines[m]):}}{{=T("defines tables")}} {{pass}}{{=XML(', '.join([B(table).xml() for table in defines[m]]))}} + +
  • + {{pass}} +
+
{{=file_create_form('%s/models/' % app)}}
+
+ + +{{ +controller_functions=[] +for c in controllers: controller_functions+=[c[:-3]+'/%s.html'%x for x in functions[c]] +}} + + + +

+ {{=T("Controllers")}} + {{=helpicon()}} {{=T("The application logic, each URL path is mapped in one exposed function in the controller")}} +

+
+ {{if not controllers:}}

{{=T("There are no controllers")}}

{{else:}} +
+ {{=button(URL(r=request,c='shell',f='index',args=app), T("shell"))}} + {{=button(URL('test',args=app), T("test"))}} + {{=button(URL('edit',args=[app,'cron','crontab']), T("crontab"))}} +
+ {{pass}} +
    + {{for c in controllers:}} +
  • + + {{=editfile('controllers',c)}} + {{=deletefile([app, 'controllers', c])}} + {{=testfile('controllers',c)}} + + + {{=peekfile('controllers',c)}} + + + {{if functions[c]:}}{{=T("exposes")}} {{pass}}{{=XML(', '.join([A(f,_href=URL(a=app,c=c[:-3],f=f)).xml() for f in functions[c]]))}} + +
  • + {{pass}} +
+
{{=file_create_form('%s/controllers/' % app)}}
+
+ + + +

+ {{=T("Views")}} + {{=helpicon()}} {{=T("The presentations layer, views are also known as templates")}} +

+
+
+ {{=button(LAYOUTS_APP, T("download layouts"))}} +
+ {{if not views:}}

{{=T("There are no views")}}

{{pass}} +
    + {{for c in views:}} +
  • + + {{=editfile('views',c)}} + {{=deletefile([app, 'views', c])}} + + + {{=peekfile('views',c)}} + + + {{if extend.has_key(c):}}{{=T("extends")}} {{=extend[c]}} {{pass}} + {{if include[c]:}}{{=T("includes")}} {{pass}}{{=XML(', '.join([B(f).xml() for f in include[c]]))}} + +
  • + {{pass}} +
+
{{=file_create_form('%s/views/' % app)}}
+
+ + + +

+ {{=T("Languages")}} + {{=helpicon()}} {{=T("Translation strings for the application")}} +

+
+
+ {{=button(URL('update_languages/'+app), T('update all languages'))}} +
+ {{if not languages:}}

{{=T("There are no translators, only default language is supported")}}

{{pass}} +
    + {{for file in languages:}} +
  • + + {{=editlanguagefile('languages',file)}} + {{=deletefile([app, 'languages', file])}} + + + {{=peekfile('languages',file)}} + +
  • + {{pass}} +
+
{{=file_create_form('%s/languages/' % app)}}{{=T('(something like "it-it")')}}
+
+ + + +

+ {{=T("Static files")}} + {{=helpicon()}} {{=T("These files are served without processing, your images go here")}} +

+
+
+
+ {{if not statics:}}

{{=T("There are no static files")}}

{{pass}} +
    + {{ + path=[] + for file in statics+['']: + items=file.split('/') + file_path=items[:-1] + filename=items[-1] + while path!=file_path: + if len(file_path)>=len(path) and all([v==file_path[k] for k,v in enumerate(path)]): + path.append(file_path[len(path)]) + thispath='static__'+'__'.join(path) + }} +
  • + {{=path[-1]}}/ +
  • + {{ + pass + pass + if filename: + }}
  • + + {{=editfile('static',file)}} {{=deletefile([app,'static',file])}} + + + {{=filename}} + +
  • {{ + pass + pass + }} + {{pass}} +
+
{{=file_create_form('%s/static/' % app)}} + {{=file_upload_form('%s/static/' % app)}}
+
+ + + +

+ {{=T("Modules")}} + {{=helpicon()}} {{=T("Additional code for your application")}} +

+
+
+
+ {{if not modules:}}

{{=T("There are no modules")}}

{{pass}} +
    + {{for m in modules:}} +
  • + + {{=editfile('modules',m)}} + {{if m!='__init__.py':}}{{=deletefile([app, 'modules', m])}}{{pass}} + + + {{=peekfile('modules',m)}} + +
  • + {{pass}} +
+
{{=file_create_form('%s/modules/' % app)}} + {{=file_upload_form('%s/modules/' % app)}}
+
+ + + +

+ {{=T("Plugins")}} + {{=helpicon()}} {{=T("To create a plugin, name a file/folder plugin_[name]")}} +

+
+
+ {{=button(PLUGINS_APP, T('download plugins'))}} +
+
+
+ {{if plugins:}} +
    + {{for plugin in plugins:}} +
  • + {{=A('plugin_%s' % plugin, _class='file', _href=URL('plugin', args=[app, plugin]))}} +
  • + {{pass}} +
+ {{else:}} +

{{=T('There are no plugins')}}

+ {{pass}} +
{{=upload_plugin_form(app)}}
+
+ + + ADDED applications/admin/views/default/downgrade_web2py.html Index: applications/admin/views/default/downgrade_web2py.html ================================================================== --- applications/admin/views/default/downgrade_web2py.html +++ applications/admin/views/default/downgrade_web2py.html @@ -0,0 +1,13 @@ +{{extend 'layout.html'}} + +{{block sectionclass}}upgrade{{end}} + +

{{=T('web2py downgrade')}}

+ +

{{=T('ATTENTION:')}} {{=T('This is an experimental feature and it needs more testing. If you decide to downgrade you do it at your own risk')}}
+{{=T('If start the downgrade, be patient, it may take a while to rollback')}}

+ +
+{{=FORM(INPUT(_type='submit',_name='nodowngrade',_value=T('Cancel')), _class='inline')}} +{{=FORM(INPUT(_type='submit',_name='downgrade',_value=T('Downgrade')), _class='inline')}} +
ADDED applications/admin/views/default/edit.html Index: applications/admin/views/default/edit.html ================================================================== --- applications/admin/views/default/edit.html +++ applications/admin/views/default/edit.html @@ -0,0 +1,92 @@ +{{extend 'layout.html'}} + +{{ + def shortcut(combo, description): + return XML('
  • %s %s
  • ' % (combo, description)) +}} + +{{if TEXT_EDITOR == 'amy':}} +{{include 'default/amy_ajax.html'}} +{{else:}} + + + +{{pass}} + +{{block sectionclass}}edit{{end}} + +

    {{=T('Editing file "%s"',filename)}}

    + +{{if functions:}} +

    + + {{=B(T('exposes:'))}}{{=XML(', '.join([A(f,_href=URL(a=app,c=controller,f=f)).xml() for f in functions]))}} + + {{if editviewlinks:}}
    + {{=B(T('edit views:'))}} + {{=XML(', '.join([v.xml() for v in editviewlinks]))}} + {{pass}} +

    +{{pass}} + +

    + {{=button(URL('design',args=request.args[0]), T('back'))}} + {{if edit_controller:}} + {{=button(edit_controller, T('edit controller'))}} + {{pass}} + {{if view_link:}} + {{=button(view_link, T('try view'))}} + {{pass}} + {{if request.args[1]=='models':}} + {{=T('online designer')}} + {{pass}} + {{=T('docs')}} +

    + +
    +
    + + {{=IMG(_src=URL('static', 'images/save_icon.png'), _alt=T('Save'))}} + + {{=T('Saved file hash:')}} + + {{=T('Last saved on:')}} + {{if TEXT_EDITOR=='amy':}} + + {{else:}} + + {{pass}} + {{=T('currently saved or')}} + {{=T('to previous version.')}} +
    +
    +
    + +{{if filetype=='html':}} +
    +

    Key bindings for ZenCoding Plugin

    +
      + {{=shortcut('Ctrl+S', 'Save via Ajax')}} + {{=shortcut('Ctrl+,', 'Expand Abbreviation')}} + {{=shortcut('Ctrl+M', 'Match Pair')}} + {{=shortcut('Ctrl+H', 'Wrap with Abbreviation')}} + {{=shortcut('Shift+Ctrl+M', 'Merge Lines')}} + {{=shortcut('Ctrl+Shift+←', 'Previous Edit Point')}} + {{=shortcut('Ctrl+Shift+→', 'Next Edit Point')}} + {{=shortcut('Ctrl+Shift+↑', 'Go to Matching Pair')}} +
    +
    +{{else:}} +
    +

    Key bindings

    +
      + {{=shortcut('Ctrl+S', 'Save via Ajax')}} +
    +
    +{{pass}} ADDED applications/admin/views/default/edit_language.html Index: applications/admin/views/default/edit_language.html ================================================================== --- applications/admin/views/default/edit_language.html +++ applications/admin/views/default/edit_language.html @@ -0,0 +1,18 @@ +{{extend 'layout.html'}} + + +{{block sectionclass}}edit_language{{end}} + +

    {{=T('Editing Language file')}} "{{=filename}}"

    + +
    + {{=form}} +
    + ADDED applications/admin/views/default/index.html Index: applications/admin/views/default/index.html ================================================================== --- applications/admin/views/default/index.html +++ applications/admin/views/default/index.html @@ -0,0 +1,21 @@ +{{extend 'layout.html'}} + + +{{block sectionclass}}login{{end}} + +

    web2py™ {{=T('Web Framework')}}

    +

    {{=T('Login to the Administrative Interface')}}

    +

    {{=T('ATTENTION: Login requires a secure (HTTPS) connection or running on localhost.')}}

    +
    +
    +
    + + + +
    {{=T('Administrator Password:')}}
    +
    +
    ADDED applications/admin/views/default/peek.html Index: applications/admin/views/default/peek.html ================================================================== --- applications/admin/views/default/peek.html +++ applications/admin/views/default/peek.html @@ -0,0 +1,16 @@ +{{extend 'layout.html'}} + +{{block sectionclass}}peek{{end}} + +

    {{=T("Peeking at file")}} "{{=filename}}"

    + +

    +{{=button(URL('design',args=request.args[0]), T('back'))}} +{{=button(URL('edit',args=request.args), T('edit'))}} +

    + +{{ +if filename[-3:]=='.py': language='python' +else: language='html' +}} +{{=CODE(data,language=language,link='/examples/global/vars/')}} ADDED applications/admin/views/default/plugin.html Index: applications/admin/views/default/plugin.html ================================================================== --- applications/admin/views/default/plugin.html +++ applications/admin/views/default/plugin.html @@ -0,0 +1,221 @@ +{{extend 'layout.html'}} +{{ +import os +def all(items): + return reduce(lambda a,b:a and b,items,True) +def peekfile(path,file): + return A(file.replace('\\\\','/'),_href=URL('peek', args=(app, path, file))) +def editfile(path,file): + return A(SPAN(T('edit')),_class='button editbutton',_href=URL('edit', args=(app, path, file))) +def testfile(path,file): + return A(TAG[''](IMG(_src=URL('static', 'images/test_icon.png'), _alt=T('test')), SPAN(T("Run tests in this file"))), _class='icon test tooltip',_href=URL('test', args=(app, file))) +def editlanguagefile(path,file): + return A(SPAN(T('edit')),_class='button editbutton',_href=URL('edit_language', args=(app, path, file))) +def file_upload_form(location): + form=FORM(T("upload file:")," ", + INPUT(_type="file",_name="file")," ",T("and rename it:")," ", + INPUT(_type="text",_name="filename",requires=IS_NOT_EMPTY), + INPUT(_type="hidden",_name="location",_value=location), + INPUT(_type="hidden",_name="sender",_value=URL('design/'+app)), + INPUT(_type="submit",_value=T("submit")),_action=URL('upload_file')) + return form +def file_create_form(location): + form=FORM(T("create file with filename:")," ", + INPUT(_type="text",_name="filename",requires=IS_NOT_EMPTY), + INPUT(_type="hidden",_name="location",_value=location), + INPUT(_type="hidden",_name="sender",_value=URL('design/'+app)), + INPUT(_type="submit",_value=T("submit")),_action=URL('create_file')) + return form +def upload_plugin_form(app): + form=FORM(T("upload plugin file:")," ", + INPUT(_type="file",_name="pluginfile"), + INPUT(_type="submit",_value=T("submit"))) + return form +def deletefile(arglist): + return A(TAG[''](IMG(_src=URL('static', 'images/delete_icon.png')), SPAN(T('Delete this file (you will be asked to confirm deletion)'))), _class='icon delete tooltip', _href=URL('delete',args=arglist,vars=dict(sender=request.function+'/'+app))) +}} + +{{block sectionclass}}plugin{{end}} + +

    + {{=T('Plugin "%s" in application', request.args(1))}} "{{=app}}" +

    +
    + {{=T("collapse/expand all")}} + + {{=button("#models", T("models"))}} + {{=button("#controllers", T("controllers"))}} + {{=button("#views", T("views"))}} + {{=button("#static", T("static"))}} + {{=button("#modules", T("modules"))}} + + + {{=sp_button(URL('design',args=app), T("back"))}} + {{=sp_button(URL('delete_plugin',args=request.args), T("delete plugin"))}} + {{=sp_button(URL('pack_plugin',args=request.args), T("pack plugin"))}} + +
    + + + +

    + {{=T("Models")}} +

    +
    + {{if not models:}} +

    {{=T("There are no models")}}

    + {{pass}} +
    +
    +
      + {{for m in models:}} +
    • + + {{=editfile('models',m)}} + {{=deletefile([app, 'models', m])}} + + + {{=peekfile('models',m)}} + + + {{if len(defines[m]):}}{{=T("defines tables")}} {{pass}}{{=XML(', '.join([B(table).xml() for table in defines[m]]))}} + +
    • + {{pass}} +
    +
    + + +{{ +controller_functions=[] +for c in controllers: controller_functions+=[c[:-3]+'/%s.html'%x for x in functions[c]] +}} + + + +

    + {{=T("Controllers")}} +

    +
    + {{if not controllers:}} +

    {{=T("There are no controllers")}}

    + {{pass}} +
    +
    +
      + {{for c in controllers:}} +
    • + + {{=editfile('controllers',c)}} + {{=deletefile([app,'controllers',c])}} + {{=testfile('controllers',c)}} + + + {{=peekfile('controllers',c)}} + + + {{if functions[c]:}}{{=T("exposes")}} {{pass}}{{=XML(', '.join([A(f,_href=URL(a=app,c=c[:-3],f=f)).xml() for f in functions[c]]))}} + +
    • + {{pass}} +
    +
    + + + +

    + {{=T("Views")}} +

    +
    + {{if not views:}} +

    {{=T("There are no views")}}

    + {{pass}} +
    +
    +
      + {{for c in views:}} +
    • + + {{=editfile('views',c)}} + {{=deletefile([app,'views',c])}} + + + {{=peekfile('views',c)}} + + + {{if extend.has_key(c):}}{{=T("extends")}} {{=extend[c]}} {{pass}} + {{if include[c]:}}{{=T("includes")}} {{pass}}{{=XML(', '.join([B(f).xml() for f in include[c]]))}} + +
    • + {{pass}} +
    +
    + + + +

    + {{=T("Static files")}} +

    +
    + {{if not statics:}}

    {{=T("There are no static files")}}

    {{pass}} +
      + {{ + path=[] + for file in statics+['']: + items=file.split('/') + file_path=items[:-1] + filename=items[-1] + while path!=file_path: + if len(file_path)>=len(path) and all([v==file_path[k] for k,v in enumerate(path)]): + path.append(file_path[len(path)]) + thispath='static__'+'__'.join(path) + }} +
    • + {{=path[-1]}}/ +
    • + {{ + pass + pass + if filename: + }}
    • + + {{=editfile('static',file)}} {{=deletefile([app,'static',file])}} + + + {{=filename}} + +
    • {{ + pass + pass + }} + {{pass}} +
    +
    + + + +

    + {{=T("Modules")}} +

    +
    + {{if not modules:}} +

    {{=T("There are no modules")}}

    + {{pass}} +
    +
    +
      + {{for m in modules:}} +
    • + {{=editfile('modules',m)}} + {{if m!='__init__.py':}}{{=T("delete")}}{{pass}} + {{=peekfile('modules',m)}} +
    • + {{pass}} +
    +
    + + ADDED applications/admin/views/default/resolve.html Index: applications/admin/views/default/resolve.html ================================================================== --- applications/admin/views/default/resolve.html +++ applications/admin/views/default/resolve.html @@ -0,0 +1,24 @@ +{{extend 'layout.html'}} + +{{block sectionclass}}resolve{{end}} + +

    {{=T('Resolve Conflict file')}} "{{=filename}}"

    + + + +
    + + + +
    + +
    +
    + {{=diff}} +
    +
    +
    ADDED applications/admin/views/default/site.html Index: applications/admin/views/default/site.html ================================================================== --- applications/admin/views/default/site.html +++ applications/admin/views/default/site.html @@ -0,0 +1,156 @@ +{{extend 'layout.html'}} +{{import os, glob}} + +{{block sectionclass}}site{{end}} + +
    +
    +

    {{=T("Installed applications")}}

    +
      + {{for a in apps:}} +
    • + {{if a==request.application:}} +

      {{=a}} ({{=T('currently running')}})

      +

      + {{else:}} +

      {{=A(a,_href=URL(a,'default','index'))}}

      + {{if MULTI_USER_MODE and db.app(name=a):}}(created by {{="%(first_name)s %(last_name)s" % db.auth_user[db.app(name=a).owner]}}){{pass}} +

      + {{if not os.path.exists('applications/%s/compiled' % a):}} + {{=sp_button(URL('design',args=a), T("edit"))}} + {{else:}} + {{=button(URL(a,'appadmin','index'), T("appadmin"))}} + {{pass}} + {{=button(URL('about',args=a), T("about"))}} + {{pass}} + {{=button(URL('errors',args=a), T("errors"))}} + {{=button(URL('cleanup',args=a), T("clean"))}} + {{=button(URL('pack',args=a), T("pack all"))}} + {{if not os.path.exists('applications/%s/compiled' % a):}} + {{=button(URL('compile_app',args=a), T("compile"))}} + {{else:}} + {{=button(URL('pack',args=(a, 'compiled')), T("pack compiled"))}} + {{if glob.glob('applications/%s/controllers/*.py' % a):}} + {{=button(URL('remove_compiled_app',args=a), T("remove compiled"))}} + {{pass}} + {{pass}} + {{if a!=request.application:}} + {{=button(URL('uninstall',args=a), T("uninstall"))}} + {{pass}} +

      +
    • + {{pass}} +
    +
    +
    + + ADDED applications/admin/views/default/test.html Index: applications/admin/views/default/test.html ================================================================== --- applications/admin/views/default/test.html +++ applications/admin/views/default/test.html @@ -0,0 +1,20 @@ +{{extend 'layout.html'}} + +{{block sectionclass}}test{{end}} + +

    {{=T('Testing application')}} "{{=app}}"

    + +{{for controller in sorted(controllers):}} +
    +

    Testing controller "{{=controller}}"... please wait!

    +
    + + +{{pass}} + +

    {{=T("""If the report above contains a ticket number it indicates a failure in executing the controller, before any attempt to execute the doctests. This is usually due to an indentation error or an error outside function code. +A green title indicates that all tests (if defined) passed. In this case test results are not shown.""")}}

    +

    {{=T('Functions with no doctests will result in [passed] tests.')}}

    +

    {{=T('ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.')}}

    ADDED applications/admin/views/default/ticket.html Index: applications/admin/views/default/ticket.html ================================================================== --- applications/admin/views/default/ticket.html +++ applications/admin/views/default/ticket.html @@ -0,0 +1,132 @@ +{{extend 'layout.html'}} + +{{block sectionclass}}ticket{{end}} + +

    {{=T('Error ticket')}} for "{{=app}}"

    +

    {{=T('Ticket ID')}}

    +

    {{=ticket}}

    +{{if output:}}

    {{=output}}

    {{pass}} +

    {{=T('Version')}}

    + + + + + + + + + + + +
    web2py™{{=myversion}}
    Python{{=snapshot.get('pyver','')}}
    +

    {{=T('Traceback')}}

    +
    +{{=traceback}} +
    + +{{if snapshot:}} +{{try:}} + + + +

    + {{=T('Error snapshot')}} + {{=helpicon()}} {{=T('Detailed traceback description')}} +

    + + + +
    + +

    + {{=snapshot['etype']}}({{=snapshot['evalue']}}) +

    +

    + {{=T('inspect attributes')}} +

    +
    +
    +
    {{=T("Exception instance attributes")}}
    + + + {{for k,v in snapshot['exception'].items():}} + + + + + {{pass}} + +
    {{=k}}{{=v}}
    +
    +
    +
    + + +
    +

    {{=T('Frames')}}

    +
      + {{for i, frame in enumerate(snapshot['frames']):}} +
    • + {{is_hidden = (i != len(snapshot['frames'])-1 and 'hide' or 'inspect')}} +
      +

      + File {{="%s in %s at line %s" % (frame['file'], frame['func'], frame['lnum'])}} + {{=T("code")}} + {{=T("arguments")}} + {{=T("variables")}} +

      +
      +
      Function argument list
      +

      {{=frame['call']}}

      +
      +
      +
      Code listing
      + {{if frame['lines']:}} +
      {{=CODE('\n'.join([x[1] for x in sorted(frame['lines'].items(),key=lambda x: x[0])]), 
      +                    language='python', link=None, counter=min(frame['lines'].keys()), highlight_line=frame['lnum'])}}
      + {{pass}} +
      +
      +
      Variables
      + + + {{for k,v in frame['dump'].items():}} + + + + + {{pass}} + +
      {{=k}}{{=v}}
      +
      +
      +
    • + {{pass}} +
    +
    + + +
    +

    Context

    +

    + {{=T('locals')}} + {{=T('request')}} + {{=T('session')}} + {{=T('response')}} +

    +
    locals
    {{=BEAUTIFY(snapshot['locals'])}}
    +
    request
    {{=BEAUTIFY(snapshot['request'])}}
    +
    session
    {{=BEAUTIFY(snapshot['session'])}}
    +
    response
    {{=BEAUTIFY(snapshot['response'])}}
    +
    +{{except Exception, e:}} + + {{import traceback;tb=traceback.format_exc().replace("\n","\\n") }} + +{{pass}} +{{pass}} + +
    +

    In file: {{=layer}}

    + {{=CODE(code.replace('\r',''),language='python',link='/examples/global/vars/')}} +
    ADDED applications/admin/views/default/uninstall.html Index: applications/admin/views/default/uninstall.html ================================================================== --- applications/admin/views/default/uninstall.html +++ applications/admin/views/default/uninstall.html @@ -0,0 +1,9 @@ +{{extend 'layout.html'}} + +
    +

    {{=T('Are you sure you want to uninstall application "%s"?', app)}}

    + + + +
    {{=FORM(INPUT(_type='submit',_name='nodelete',_value=T('Abort')))}}{{=FORM(INPUT(_type='submit',_name='delete',_value=T('Uninstall')))}}
    +
    ADDED applications/admin/views/default/upgrade_web2py.html Index: applications/admin/views/default/upgrade_web2py.html ================================================================== --- applications/admin/views/default/upgrade_web2py.html +++ applications/admin/views/default/upgrade_web2py.html @@ -0,0 +1,13 @@ +{{extend 'layout.html'}} + +{{block sectionclass}}upgrade{{end}} + +

    {{=T('web2py upgrade')}}

    + +

    {{=T('ATTENTION:')}} {{=T('This is an experimental feature and it needs more testing. If you decide to upgrade you do it at your own risk')}}
    +{{=T('If start the upgrade, be patient, it may take a while to download')}}

    + +
    +{{=FORM(INPUT(_type='submit',_name='noupgrade',_value=T('Cancel')), _class='inline')}} +{{=FORM(INPUT(_type='submit',_name='upgrade',_value=T('Upgrade')), _class='inline')}} +
    ADDED applications/admin/views/default/user.html Index: applications/admin/views/default/user.html ================================================================== --- applications/admin/views/default/user.html +++ applications/admin/views/default/user.html @@ -0,0 +1,19 @@ +{{extend 'layout.html'}} +

    {{=T( request.args(0).replace('_',' ').capitalize() )}}

    +
    +{{=form}} +{{if request.args(0)=='login':}} +{{if not 'register' in auth.settings.actions_disabled:}} +
    register +{{pass}} +{{if not 'request_reset_password' in auth.settings.actions_disabled:}} +
    lost password +{{pass}} +{{pass}} +
    + + ADDED applications/admin/views/gae/deploy.html Index: applications/admin/views/gae/deploy.html ================================================================== --- applications/admin/views/gae/deploy.html +++ applications/admin/views/gae/deploy.html @@ -0,0 +1,30 @@ +{{extend 'layout.html'}} + + + + + +

    Google App Engine Deployment Interface

    + +

    {{=T("This page can upload your application to the Google App Engine computing cloud. Mind that you must first create indexes locally and this is done by installing the Google appserver and running the app locally with it once, or there will be errors when selecting records. Attention: deployment may take long time, depending on the network speed. Attention: it will overwrite your app.yaml. DO NOT SUBMIT TWICE.")}}

    + +{{if command:}} +

    Command

    + +{{=CODE(command)}} +

    GAE Output

    +
    
    +{{else:}}
    +

    Deployment form

    +
    +{{=form}} +
    +{{pass}} ADDED applications/admin/views/generic.html Index: applications/admin/views/generic.html ================================================================== --- applications/admin/views/generic.html +++ applications/admin/views/generic.html @@ -0,0 +1,18 @@ +{{extend 'layout.html'}} +{{""" + +You should not modify this file. +It is used as default when a view is not provided for your controllers + +"""}} + +{{=BEAUTIFY(response._vars)}} + + + + + + + + + ADDED applications/admin/views/layout.html Index: applications/admin/views/layout.html ================================================================== --- applications/admin/views/layout.html +++ applications/admin/views/layout.html @@ -0,0 +1,44 @@ + + + + + + {{=response.title or URL()}} + {{response.files.append(URL('static','css/styles.css'))}} + {{include 'web2py_ajax.html'}} + + + + +
    +
    +
    {{=response.flash or ''}}
    + {{include}} +
    +
    + + + ADDED applications/admin/views/mercurial/commit.html Index: applications/admin/views/mercurial/commit.html ================================================================== --- applications/admin/views/mercurial/commit.html +++ applications/admin/views/mercurial/commit.html @@ -0,0 +1,33 @@ +{{extend 'layout.html'}} +{{import time}} + +

    Mercurial Version Control System Interface
    +for application "{{=request.args[0]}}"

    + +

    Commit form

    +{{=form}} + + +{{if repo['.'].rev()>=0:}} +

    Last Revision

    + + + + + + +
    Revision:{{=repo['.'].rev()}}
    Node:{{=repo['.']}}
    Created by:{{=repo['.'].user()}}
    Created on:{{=time.ctime(repo['.'].date()[0])}}
    Description:{{=repo['.'].description()}}
    + +

    Past revisions

    +
    + {{=changes}} +
    + +{{if files:}} +

    Committed files

    +
    +{{=files}} +
    +{{pass}} + +{{pass}} ADDED applications/admin/views/mercurial/revision.html Index: applications/admin/views/mercurial/revision.html ================================================================== --- applications/admin/views/mercurial/revision.html +++ applications/admin/views/mercurial/revision.html @@ -0,0 +1,17 @@ +{{extend 'layout.html'}} + +

    Revision {{=rev}}

    + +{{=form}} + +

    +

    Changelog

    + +{{=desc}} + +

    +

    Files added

    + +{{=TABLE(*[TR(f) for f in files])}} + + ADDED applications/admin/views/shell/index.html Index: applications/admin/views/shell/index.html ================================================================== --- applications/admin/views/shell/index.html +++ applications/admin/views/shell/index.html @@ -0,0 +1,162 @@ +{{extend 'layout.html'}} + +{{block sectionclass}}shell{{end}} + + +
    + + +
    +
    +
    >>>
    +
    + + Type some Python code in here and hit Return (Enter) to execute it. +
    +
    +
    +
    + +
    +
      +
    • Using the shell may lock the database to other users of this app.
    • +
    • Each db statement is automatically committed.
    • +
    • Creating new tables dynamically is not allowed.
    • +
    • Models are automatically imported in the shell.
    • +
    +
    + + ADDED applications/admin/views/toolbar/index.html Index: applications/admin/views/toolbar/index.html ================================================================== --- applications/admin/views/toolbar/index.html +++ applications/admin/views/toolbar/index.html @@ -0,0 +1,17 @@ + + + {{response.files.append(URL('static','js/jquery.js'))}} + {{include 'web2py_ajax.html'}} + + +
    + URL: {{=URL(app,'default','index')}} + + +
    + + + ADDED applications/admin/views/web2py_ajax.html Index: applications/admin/views/web2py_ajax.html ================================================================== --- applications/admin/views/web2py_ajax.html +++ applications/admin/views/web2py_ajax.html @@ -0,0 +1,27 @@ +{{ +response.files.insert(0,URL('static','js/jquery.js')) +response.files.insert(1,URL('static','css/calendar.css')) +response.files.insert(2,URL('static','js/calendar.js')) +for _item in response.meta or []:}} + {{ +pass +for _k,_file in enumerate(response.files or []): + if _file in response.files[:_k]: + continue + _file0=_file.lower().split('?')[0] + if _file0.endswith('.css'):}} + {{ + elif _file0.endswith('.js'):}} + {{ + pass +pass +}} + + + + ADDED applications/admin/views/wizard/generated.html Index: applications/admin/views/wizard/generated.html ================================================================== --- applications/admin/views/wizard/generated.html +++ applications/admin/views/wizard/generated.html @@ -0,0 +1,13 @@ +{{extend 'layout.html'}} + +{{block sectionclass}}generated{{end}} + +Open new app in new window +Back to wizard +Admin design page +{{if have_mercurial:}} +Admin versioning page +{{pass}} +Database administration +

    + ADDED applications/admin/views/wizard/step.html Index: applications/admin/views/wizard/step.html ================================================================== --- applications/admin/views/wizard/step.html +++ applications/admin/views/wizard/step.html @@ -0,0 +1,140 @@ +{{extend 'layout.html'}} + +{{block sectionclass}}step{{end}} + +

    {{=T('New Application Wizard')}}

    + +
    +{{if request.function=='index':}} +

    {{=T('Start a new app')}}

    +{{else:}} +
    +

    {{=T('Basics')}}

    +

    {{=button(URL('index'), T('restart'))}}

    +

    App Name: {{=session.app['name']}}

    +
    +
    +

    Current settings

    +

    {{=button(URL('step1'), T('edit'))}}

    +
      + {{for key,value in session.app['params']:}} +
    • {{=key}}: {{=value}}
    • + {{pass}} +
    +
    +
    +

    Tables

    +

    {{=button(URL('step2'), T('edit all'))}}

    +
      + {{for i,table in enumerate(session.app['tables']):}} +
    • {{=button(URL('step3',args=i), T('edit'))}} {{=table}}
    • + {{pass}} +
    +
    +
    +

    Pages

    +

    {{=button(URL('step4'), T('edit all'))}}

    +
      + {{for i,page in enumerate(session.app['pages']):}} +
    • {{=button(URL('step5',args=i), T('edit'))}} {{=page}}
    • + {{pass}} +
    +
    +
    +

    {{=T('Generate')}}

    +

    {{=button(URL('step6'), T('go!'))}}

    +
    +{{pass}} +
    + +
    + + + + {{if 'step' in request.function:}} +

    {{=T('Step')}} {{=step}}

    + {{if request.function!='step1':}} + {{=button(URL('step' + str(int(request.function[-1])-1)), T('back'))}} + {{pass}} + {{else:}} +

    {{=T('Begin')}}

    + {{pass}} + {{if request.function in ('step1','step2','step3','step4','step5'):}} + {{=button(URL('step6'), T('skip to generate'))}} + {{pass}} +

    + {{if request.function in ('step1','step6'):}} + + {{pass}} +
    + {{=form}} +
    + + + +
    +

    Instructions

    +
    + {{if request.function=='index':}} +
      +
    • Insert the name of a new app.
    • +
    • If the app exists and was created with the wizard, you will be able to edit it, but any manual editing to the app will be lost.
    • +
    + {{elif request.function=='step1':}} +
      +
    • This Wizard will help you build a new web2py app.
    • +
    • You can create an app with a name that already exists.
    • +
    • If you do not have an email server set email server to "logging".
    • +
    • If you want to use Janrain Engage for login: 1) Sign up for a Janrain Engage account; 2) Register you hostname, domain, and obtain an api key; 3) Set Login Config above to "domain:api_key".
    • +
    • ATTENTION: you can use the wizard to download plugins BUT we cannot guarantee the stability or backward compatibility of plugins. Moreover plugins may conflict with each other. Anyway, we do recommend installing plugin "wiki" with adds CMS like capabilities to your app.
    • +
    + {{elif request.function=='step2':}} +
      +
    • List the names of table that you need.
    • +
    • If you do not need authentication remove the table "auth_user".
    • +
    • Press enter to create a new input row.
    • +
    • Empty rows are ignored.
    • +
    • Other tables for role based access control will be created automatically, and do not need to be listed.
    • +
    • You will be able to add fields later.
    • +
    + {{elif request.function=='step3':}} +
      +
    • List the fields for this table (do not include an id field, it is automatic), for example "name unique" or "birthday date" or "image upload" or "comments multiple" or "description wiki required"
    • +
    • The first keyword(s) for each entry will be used to generate a name for the table field and its label. You can use spaces an other unicode characters.
    • +
    • Keywords "string" (default), "text", "integer", "boolean", "float", "double", "file", "date", "time", "datetime", "file", "upload" will be used to determine the field type and they will not be made part of the field name. +
    • For a reference field use a field name, followed by the name of the referenced table.
    • +
    • Other special keywords are "required", "notnull" or "notempty", "unique". They map into equivalent validators but (at this time) should only be used with string and text types.
    • +
    • The keywords "html" and "wiki" force a text type and set a representation for the field value as sanitized HTML and MARKMIN resepectively.
    • +
    • string, integer and reference fields can be "multiple", i.e. multiple values will be allowed
    • +
    • For the "auth_user" table do not add attributes to the default fields (username, first_name, last_name, password and email). They are handled automatically.
    • +
    • Some fields will be added automatically upon creation and handled automatically: "created_by", "created_on", "modified_by", "modified_on", "active" (only active fields can be selected).
    • +
    • For every table "table" another table "table_archive" is created and it contains the previous versions of each record. This is only accessible via appadmin or programmatically.
    • +
    + {{elif request.function=='step4':}} +
      +
    • List the names of the pages you want to create.
    • +
    • Some pages are listed automatically because they expose Create/Read/Update/Delete for each tables you have created.
    • +
    • All pages, except "error" and those with name starting in underscore willbe listed in the menu. You will be able to edit the menu later.
    • +
    • You should have page "index", the starting point of your app, and page "error", where web2py will redirect to in case of error.
    • +
    + {{elif request.function=='step5':}} +
      +
    • Use the markmin syntax to add text to your pages.
    • +
    + {{elif request.function=='step6':}} +
      +
    • Almost done. Click on the button above to create your new app.
    • +
    • Once done you will be able to edit it as any normal web2py app.
    • +
    + {{pass}} +
    +
    +
    + ADDED applications/examples/ABOUT Index: applications/examples/ABOUT ================================================================== --- applications/examples/ABOUT +++ applications/examples/ABOUT @@ -0,0 +1,6 @@ +web2py is an open source full-stack framework for agile development +of secure database-driven web-based applications, written and programmable in +Python. + +Created by Massimo Di Pierro + ADDED applications/examples/LICENSE Index: applications/examples/LICENSE ================================================================== --- applications/examples/LICENSE +++ applications/examples/LICENSE @@ -0,0 +1,137 @@ +## Web2py License + +Web2py is Licensed under the LGPL license version 3 +(http://www.gnu.org/licenses/lgpl.html) + +Copyrighted (c) by Massimo Di Pierro (2007-2011) + +### On Commercial Redistribution + +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 +But you must: +- make clear in the documentation that your application uses web2py +- release any modification of the web2py libraries under the LGPLv3 license + +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 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 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) + +### Licenses for third party contributed software + +web2py contains third party software under the gluon/contrib/ folder. +Each file/module in contrib is distributed with web2py under its original license. +Here we list some of them. + +#### gluon.contrib.simplejson LICENSE + +Copyright (c) 2006 Bob Ippolito - Permission is hereby granted, free of charge, +to any person obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, distribute, +sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +#### gluon.contrib.rss2.py (originally PyRSS2Gen) LICENSE + +This is copyright (c) by Dalke Scientific Software, LLC and released under the +BSD license. See the file LICENSE in the distribution or + for details. + +#### gluon.contrib.markdown (markdown2) LICENSE + +MIT License from from + +#### gluon.contrib.feedparser LICENSE + +Copyright (c) 2002-2005, Mark Pilgrim + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +* Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +#### gluon.wsgiserver.py LICENSE (borrowed from cherrypy) + +Copyright (c) 2004, CherryPy Team (team@cherrypy.org) +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the CherryPy Team nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#### gluon.contrib.pam LICENSE + +Copyright (C) 2007-2009 Chris AtLee Licensed under the MIT license + +#### gluon.contrib.shell LICENSE + +Copyright (C) by Google inc. Apache 2.0 Lincense + +#### The javascript licenses are in the code itself + ADDED applications/examples/__init__.py Index: applications/examples/__init__.py ================================================================== --- applications/examples/__init__.py +++ applications/examples/__init__.py ADDED applications/examples/controllers/ajax_examples.py Index: applications/examples/controllers/ajax_examples.py ================================================================== --- applications/examples/controllers/ajax_examples.py +++ applications/examples/controllers/ajax_examples.py @@ -0,0 +1,26 @@ + + + + +def index(): + return dict() + + +def data(): + if not session.m or len(session.m) == 10: + session.m = [] + if request.vars.q: + session.m.append(request.vars.q) + session.m.sort() + return TABLE(*[TR(v) for v in session.m]).xml() + + +def flash(): + response.flash = 'this text should appear!' + return dict() + + +def fade(): + return dict() + + ADDED applications/examples/controllers/appadmin.py Index: applications/examples/controllers/appadmin.py ================================================================== --- applications/examples/controllers/appadmin.py +++ applications/examples/controllers/appadmin.py @@ -0,0 +1,408 @@ +# -*- coding: utf-8 -*- + +# ########################################################## +# ## make sure administrator is on localhost +# ########################################################### + +import os +import socket +import datetime +import copy +import gluon.contenttype +import gluon.fileutils + +# ## critical --- make a copy of the environment + +global_env = copy.copy(globals()) +global_env['datetime'] = datetime + +http_host = request.env.http_host.split(':')[0] +remote_addr = request.env.remote_addr +try: + hosts = (http_host, socket.gethostname(), + socket.gethostbyname(http_host), + '::1','127.0.0.1','::ffff:127.0.0.1') +except: + hosts = (http_host, ) + +if request.env.http_x_forwarded_for or request.env.wsgi_url_scheme\ + in ['https', 'HTTPS']: + session.secure() +elif (remote_addr not in hosts) and (remote_addr != "127.0.0.1"): + raise HTTP(200, T('appadmin is disabled because insecure channel')) + +if (request.application=='admin' and not session.authorized) or \ + (request.application!='admin' and not gluon.fileutils.check_credentials(request)): + redirect(URL('admin', 'default', 'index')) + +ignore_rw = True +response.view = 'appadmin.html' +response.menu = [[T('design'), False, URL('admin', 'default', 'design', + args=[request.application])], [T('db'), False, + URL('index')], [T('state'), False, + URL('state')], [T('cache'), False, + URL('ccache')]] + +# ########################################################## +# ## auxiliary functions +# ########################################################### + + +def get_databases(request): + dbs = {} + for (key, value) in global_env.items(): + cond = False + try: + cond = isinstance(value, GQLDB) + except: + cond = isinstance(value, SQLDB) + if cond: + dbs[key] = value + return dbs + + +databases = get_databases(None) + + +def eval_in_global_env(text): + exec ('_ret=%s' % text, {}, global_env) + return global_env['_ret'] + + +def get_database(request): + if request.args and request.args[0] in databases: + return eval_in_global_env(request.args[0]) + else: + session.flash = T('invalid request') + redirect(URL('index')) + + +def get_table(request): + db = get_database(request) + if len(request.args) > 1 and request.args[1] in db.tables: + return (db, request.args[1]) + else: + session.flash = T('invalid request') + redirect(URL('index')) + + +def get_query(request): + try: + return eval_in_global_env(request.vars.query) + except Exception: + return None + + +def query_by_table_type(tablename,db,request=request): + keyed = hasattr(db[tablename],'_primarykey') + if keyed: + firstkey = db[tablename][db[tablename]._primarykey[0]] + cond = '>0' + if firstkey.type in ['string', 'text']: + cond = '!=""' + qry = '%s.%s.%s%s' % (request.args[0], request.args[1], firstkey.name, cond) + else: + qry = '%s.%s.id>0' % tuple(request.args[:2]) + return qry + + + +# ########################################################## +# ## list all databases and tables +# ########################################################### + + +def index(): + return dict(databases=databases) + + +# ########################################################## +# ## insert a new record +# ########################################################### + + +def insert(): + (db, table) = get_table(request) + form = SQLFORM(db[table], ignore_rw=ignore_rw) + if form.accepts(request.vars, session): + response.flash = T('new record inserted') + return dict(form=form,table=db[table]) + + +# ########################################################## +# ## list all records in table and insert new record +# ########################################################### + + +def download(): + import os + db = get_database(request) + return response.download(request,db) + +def csv(): + import gluon.contenttype + response.headers['Content-Type'] = \ + gluon.contenttype.contenttype('.csv') + db = get_database(request) + query = get_query(request) + if not query: + return None + response.headers['Content-disposition'] = 'attachment; filename=%s_%s.csv'\ + % tuple(request.vars.query.split('.')[:2]) + return str(db(query).select()) + + +def import_csv(table, file): + table.import_from_csv_file(file) + +def select(): + import re + db = get_database(request) + dbname = request.args[0] + regex = re.compile('(?P\w+)\.(?P\w+)=(?P\d+)') + if len(request.args)>1 and hasattr(db[request.args[1]],'_primarykey'): + regex = re.compile('(?P
    \w+)\.(?P\w+)=(?P.+)') + if request.vars.query: + match = regex.match(request.vars.query) + if match: + request.vars.query = '%s.%s.%s==%s' % (request.args[0], + match.group('table'), match.group('field'), + match.group('value')) + else: + request.vars.query = session.last_query + query = get_query(request) + if request.vars.start: + start = int(request.vars.start) + else: + start = 0 + nrows = 0 + stop = start + 100 + table = None + rows = [] + orderby = request.vars.orderby + if orderby: + orderby = dbname + '.' + orderby + if orderby == session.last_orderby: + if orderby[0] == '~': + orderby = orderby[1:] + else: + orderby = '~' + orderby + session.last_orderby = orderby + session.last_query = request.vars.query + form = FORM(TABLE(TR(T('Query:'), '', INPUT(_style='width:400px', + _name='query', _value=request.vars.query or '', + requires=IS_NOT_EMPTY(error_message=T("Cannot be empty")))), TR(T('Update:'), + INPUT(_name='update_check', _type='checkbox', + value=False), INPUT(_style='width:400px', + _name='update_fields', _value=request.vars.update_fields + or '')), TR(T('Delete:'), INPUT(_name='delete_check', + _class='delete', _type='checkbox', value=False), ''), + TR('', '', INPUT(_type='submit', _value='submit'))), + _action=URL(r=request,args=request.args)) + if request.vars.csvfile != None: + try: + import_csv(db[request.vars.table], + request.vars.csvfile.file) + response.flash = T('data uploaded') + except Exception, e: + response.flash = DIV(T('unable to parse csv file'),PRE(str(e))) + if form.accepts(request.vars, formname=None): +# regex = re.compile(request.args[0] + '\.(?P
    \w+)\.id\>0') + regex = re.compile(request.args[0] + '\.(?P
    \w+)\..+') + + match = regex.match(form.vars.query.strip()) + if match: + table = match.group('table') + try: + nrows = db(query).count() + if form.vars.update_check and form.vars.update_fields: + db(query).update(**eval_in_global_env('dict(%s)' + % form.vars.update_fields)) + response.flash = T('%s rows updated', nrows) + elif form.vars.delete_check: + db(query).delete() + response.flash = T('%s rows deleted', nrows) + nrows = db(query).count() + if orderby: + rows = db(query).select(limitby=(start, stop), + orderby=eval_in_global_env(orderby)) + else: + rows = db(query).select(limitby=(start, stop)) + except Exception, e: + (rows, nrows) = ([], 0) + response.flash = DIV(T('Invalid Query'),PRE(str(e))) + return dict( + form=form, + table=table, + start=start, + stop=stop, + nrows=nrows, + rows=rows, + query=request.vars.query, + ) + + +# ########################################################## +# ## edit delete one record +# ########################################################### + + +def update(): + (db, table) = get_table(request) + keyed = hasattr(db[table],'_primarykey') + record = None + if keyed: + key = [f for f in request.vars if f in db[table]._primarykey] + if key: + record = db(db[table][key[0]] == request.vars[key[0]]).select().first() + else: + record = db(db[table].id == request.args(2)).select().first() + + if not record: + qry = query_by_table_type(table, db) + session.flash = T('record does not exist') + redirect(URL('select', args=request.args[:1], + vars=dict(query=qry))) + + if keyed: + for k in db[table]._primarykey: + db[table][k].writable=False + + form = SQLFORM(db[table], record, deletable=True, delete_label=T('Check to delete'), + ignore_rw=ignore_rw and not keyed, + linkto=URL('select', + args=request.args[:1]), upload=URL(r=request, + f='download', args=request.args[:1])) + + if form.accepts(request.vars, session): + session.flash = T('done!') + qry = query_by_table_type(table, db) + redirect(URL('select', args=request.args[:1], + vars=dict(query=qry))) + return dict(form=form,table=db[table]) + + +# ########################################################## +# ## get global variables +# ########################################################### + + +def state(): + return dict() + +def ccache(): + form = FORM( + P(TAG.BUTTON("Clear CACHE?", _type="submit", _name="yes", _value="yes")), + P(TAG.BUTTON("Clear RAM", _type="submit", _name="ram", _value="ram")), + P(TAG.BUTTON("Clear DISK", _type="submit", _name="disk", _value="disk")), + ) + + if form.accepts(request.vars, session): + clear_ram = False + clear_disk = False + session.flash = "" + if request.vars.yes: + clear_ram = clear_disk = True + if request.vars.ram: + clear_ram = True + if request.vars.disk: + clear_disk = True + + if clear_ram: + cache.ram.clear() + session.flash += "Ram Cleared " + if clear_disk: + cache.disk.clear() + session.flash += "Disk Cleared" + + redirect(URL(r=request)) + + try: + from guppy import hpy; hp=hpy() + except ImportError: + hp = False + + import shelve, os, copy, time, math + from gluon import portalocker + + ram = { + 'bytes': 0, + 'objects': 0, + 'hits': 0, + 'misses': 0, + 'ratio': 0, + 'oldest': time.time() + } + disk = copy.copy(ram) + total = copy.copy(ram) + + for key, value in cache.ram.storage.items(): + if isinstance(value, dict): + ram['hits'] = value['hit_total'] - value['misses'] + ram['misses'] = value['misses'] + try: + ram['ratio'] = ram['hits'] * 100 / value['hit_total'] + except (KeyError, ZeroDivisionError): + ram['ratio'] = 0 + else: + if hp: + ram['bytes'] += hp.iso(value[1]).size + ram['objects'] += hp.iso(value[1]).count + + if value[0] < ram['oldest']: + ram['oldest'] = value[0] + + locker = open(os.path.join(request.folder, + 'cache/cache.lock'), 'a') + portalocker.lock(locker, portalocker.LOCK_EX) + disk_storage = shelve.open(os.path.join(request.folder, 'cache/cache.shelve')) + try: + for key, value in disk_storage.items(): + if isinstance(value, dict): + disk['hits'] = value['hit_total'] - value['misses'] + disk['misses'] = value['misses'] + try: + disk['ratio'] = disk['hits'] * 100 / value['hit_total'] + except (KeyError, ZeroDivisionError): + disk['ratio'] = 0 + else: + if hp: + disk['bytes'] += hp.iso(value[1]).size + disk['objects'] += hp.iso(value[1]).count + if value[0] < disk['oldest']: + disk['oldest'] = value[0] + finally: + portalocker.unlock(locker) + locker.close() + disk_storage.close() + + total['bytes'] = ram['bytes'] + disk['bytes'] + total['objects'] = ram['objects'] + disk['objects'] + total['hits'] = ram['hits'] + disk['hits'] + total['misses'] = ram['misses'] + disk['misses'] + try: + total['ratio'] = total['hits'] * 100 / (total['hits'] + total['misses']) + except (KeyError, ZeroDivisionError): + total['ratio'] = 0 + + if disk['oldest'] < ram['oldest']: + total['oldest'] = disk['oldest'] + else: + total['oldest'] = ram['oldest'] + + def GetInHMS(seconds): + hours = math.floor(seconds / 3600) + seconds -= hours * 3600 + minutes = math.floor(seconds / 60) + seconds -= minutes * 60 + seconds = math.floor(seconds) + + return (hours, minutes, seconds) + + ram['oldest'] = GetInHMS(time.time() - ram['oldest']) + disk['oldest'] = GetInHMS(time.time() - disk['oldest']) + total['oldest'] = GetInHMS(time.time() - total['oldest']) + + return dict(form=form, total=total, + ram=ram, disk=disk) + ADDED applications/examples/controllers/database_examples.py Index: applications/examples/controllers/database_examples.py ================================================================== --- applications/examples/controllers/database_examples.py +++ applications/examples/controllers/database_examples.py @@ -0,0 +1,152 @@ +from gluon.fileutils import read_file + +response.menu = [['Register User', False, URL(r=request, + f='register_user')], ['Register Dog', False, + URL('register_dog')], ['Register Product' + , False, URL('register_product')], + ['Buy product', False, URL('buy')]] + + +def register_user(): + """ simple user registration form with validation and database.insert() + also lists all records currently in the table""" + + # ## create an insert form from the table + + form = SQLFORM(db.users) + + # ## if form correct perform the insert + + if form.accepts(request.vars, session): + response.flash = 'new record inserted' + + # ## and get a list of all users + + records = SQLTABLE(db().select(db.users.ALL)) + return dict(form=form, records=records) + + +def register_dog(): + """ simple user registration form with validation and database.insert() + also lists all records currently in the table""" + + form = SQLFORM(db.dogs) + if form.accepts(request.vars, session): + response.flash = 'new record inserted' + download = URL('download') # to see the picture + records = SQLTABLE(db().select(db.dogs.ALL), upload=download) + return dict(form=form, records=records) + + +def register_product(): + """ simple user registration form with validation and database.insert() + also lists all records currently in the table""" + + form = SQLFORM(db.products) + if form.accepts(request.vars, session): + response.flash = 'new record inserted' + records = SQLTABLE(db().select(db.products.ALL)) + return dict(form=form, records=records) + + +def buy(): + """ uses a form to query who is buying what. validates form and + updates existing record or inserts new record in purchases """ + + buyerRecords = db().select(db.users.ALL) + buyerOptions = [] + for row in buyerRecords: + buyerOptions.append(OPTION(row.name, _value=row.id)) + + productRecords = db().select(db.products.ALL) + productOptions = [] + for row in productRecords: + productOptions.append(OPTION(row.name, _value=row.id)) + + form = FORM(TABLE( + TR('Buyer id:', + SELECT(buyerOptions,_name='buyer_id')), + TR('Product id:', + SELECT(productOptions,_name='product_id')), + TR('Quantity:', + INPUT(_type='text', _name='quantity', + requires=IS_INT_IN_RANGE(1, 100))), + TR('', + INPUT(_type='submit', _value='Order')) + )) + if form.accepts(request.vars, session, keepvalues=True): + + # ## check if user is in the database + + if len(db(db.users.id == form.vars.buyer_id).select()) == 0: + form.errors.buyer_id = 'buyer not in database' + + # ## check if product is the database + + if len(db(db.products.id == form.vars.product_id).select())\ + == 0: + form.errors.product_id = 'product not in database' + + # ## if no errors + + if len(form.errors) == 0: + + # ## get a list of same purchases by same user + + purchases = db((db.purchases.buyer_id == form.vars.buyer_id) + & (db.purchases.product_id + == form.vars.product_id)).select() + + # ## if list contains a record, update that record + + if len(purchases) > 0: + purchases[0].update_record(quantity=purchases[0].quantity + + form.vars.quantity) + else: + + # ## or insert a new record in table + db.purchases.insert(buyer_id=form.vars.buyer_id, + product_id=form.vars.product_id, + quantity=form.vars.quantity) + response.flash = 'product purchased!' + if len(form.errors): + response.flash = 'invalid values in form!' + + # ## now get a list of all purchases + + # quick fix to make it runnable on gae + if purchased: + records = db(purchased).select(db.users.name, + db.purchases.quantity, + db.products.name) + else: + records = db().select(db.purchases.ALL) + return dict(form=form, records=SQLTABLE(records), vars=form.vars, + vars2=request.vars) + + +def delete_purchased(): + """ delete all records in purchases """ + + db(db.purchases.id > 0).delete() + redirect(URL('buy')) + + +def reset_purchased(): + """ set quantity=0 for all records in purchases """ + + db(db.purchases.id > 0).update(quantity=0) + redirect(URL('buy')) + + +def download(): + """ used to download uploaded files """ + + import gluon.contenttype + app = request.application + filename = request.args[0] + response.headers['Content-Type'] = \ + gluon.contenttype.contenttype(filename) + return read_file('applications/%s/uploads/%s' % (app, filename), 'rb') + + ADDED applications/examples/controllers/default.py Index: applications/examples/controllers/default.py ================================================================== --- applications/examples/controllers/default.py +++ applications/examples/controllers/default.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- + +from gluon.fileutils import read_file + +response.title = T('web2py Web Framework') +response.keywords = T('web2py, Python, Web Framework') +response.description = T('web2py Web Framework') + +session.forget() + +@cache('index') +def index(): + return response.render() + +@cache('what') +def what(): + return response.render() + +@cache('download') +def download(): + return response.render() + +@cache('who') +def who(): + return response.render() + +@cache('support') +def support(): + return response.render() + +@cache('documentation') +def documentation(): + return response.render() + +@cache('usergroups') +def usergroups(): + return response.render() + +def contact(): + redirect(URL('default','usergroups')) + +@cache('videos') +def videos(): + return response.render() + +def security(): + redirect('http://www.web2py.com/book/default/chapter/01#security') + +def api(): + redirect('http://web2py.com/book/default/chapter/04#API') + +@cache('license') +def license(): + import os + filename = os.path.join(request.env.gluon_parent, 'LICENSE') + return response.render(dict(license=MARKMIN(read_file(filename)))) + +def version(): + return request.env.web2py_version + +@cache('examples') +def examples(): + return response.render() + +@cache('changelog') +def changelog(): + import os + filename = os.path.join(request.env.gluon_parent, 'README') + return response.render(dict(changelog=MARKMIN(read_file(filename)))) ADDED applications/examples/controllers/form_examples.py Index: applications/examples/controllers/form_examples.py ================================================================== --- applications/examples/controllers/form_examples.py +++ applications/examples/controllers/form_examples.py @@ -0,0 +1,27 @@ + + + +def form(): + """ a simple entry form with various types of objects """ + + form = FORM(TABLE( + TR('Your name:', INPUT(_type='text', _name='name', + requires=IS_NOT_EMPTY())), + TR('Your email:', INPUT(_type='text', _name='email', + requires=IS_EMAIL())), + TR('Admin', INPUT(_type='checkbox', _name='admin')), + TR('Sure?', SELECT('yes', 'no', _name='sure', + requires=IS_IN_SET(['yes', 'no']))), + TR('Profile', TEXTAREA(_name='profile', + value='write something here')), + TR('', INPUT(_type='submit', _value='SUBMIT')), + )) + if form.accepts(request.vars, session): + response.flash = 'form accepted' + elif form.errors: + response.flash = 'form is invalid' + else: + response.flash = 'please fill the form' + return dict(form=form, vars=form.vars) + + ADDED applications/examples/controllers/global.py Index: applications/examples/controllers/global.py ================================================================== --- applications/examples/controllers/global.py +++ applications/examples/controllers/global.py @@ -0,0 +1,81 @@ + +session.forget() + +response.menu = [['home', False, '/%s/default/index' + % request.application], ['docs', True, + '/%s/global/vars' % request.application]] + + +def vars(): + """the running controller function!""" + + if not request.args: + ( + doc, + keys, + t, + c, + d, + value, + ) = ( + 'Global variables', + globals(), + None, + None, + (), + None, + ) + (title, args) = ('globals()', '') + elif len(request.args) < 3: + args = '.'.join(request.args) + try: + doc = eval(args + '.__doc__') + except: + doc = 'no documentation' + try: + keys = eval('dir(%s)' % args) + except: + keys = [] + t = eval('type(%s)' % args) + try: + c = eval('%s.__class__' % args) + except: + c = None + try: + d = eval('%s.__bases__' % args) + except: + d = None + title = args + args += '.' + else: + raise HTTP(400) + attributes = {} + for key in keys: + a = args + key + if eval('isinstance(%s,SQLDB)' % a) or a == 'vars': + continue + try: + doc1 = eval(a + '.__doc__') + except: + doc1 = 'no documentation' + t1 = eval('type(%s)' % a) + try: + c1 = eval('%s.__class__' % a) + except: + c1 = None + try: + d1 = eval('%s.__bases__' % a) + except: + d1 = () + attributes[a] = (doc1, t1, c1, d1) + return dict( + title=title, + args=args, + t=t, + c=c, + d=d, + doc=doc, + attributes=attributes, + ) + + ADDED applications/examples/controllers/layout_examples.py Index: applications/examples/controllers/layout_examples.py ================================================================== --- applications/examples/controllers/layout_examples.py +++ applications/examples/controllers/layout_examples.py @@ -0,0 +1,27 @@ + + + +def civilized(): + response.menu = [['civilized', True, URL('civilized' + )], ['slick', False, URL('slick')], + ['basic', False, URL('basic')]] + response.flash = 'you clicked on civilized' + return dict(message='you clicked on civilized') + + +def slick(): + response.menu = [['civilized', False, URL('civilized' + )], ['slick', True, URL('slick')], + ['basic', False, URL('basic')]] + response.flash = 'you clicked on slick' + return dict(message='you clicked on slick') + + +def basic(): + response.menu = [['civilized', False, URL('civilized' + )], ['slick', False, URL('slick')], + ['basic', True, URL('basic')]] + response.flash = 'you clicked on basic' + return dict(message='you clicked on basic') + + ADDED applications/examples/controllers/session_examples.py Index: applications/examples/controllers/session_examples.py ================================================================== --- applications/examples/controllers/session_examples.py +++ applications/examples/controllers/session_examples.py @@ -0,0 +1,12 @@ + + + +def counter(): + """ every time you reload, it increases the session.counter """ + + if not session.counter: + session.counter = 0 + session.counter += 1 + return dict(counter=session.counter) + + ADDED applications/examples/controllers/simple_examples.py Index: applications/examples/controllers/simple_examples.py ================================================================== --- applications/examples/controllers/simple_examples.py +++ applications/examples/controllers/simple_examples.py @@ -0,0 +1,140 @@ +def hello1(): + """ simple page without template """ + + return 'Hello World' + + +def hello2(): + """ simple page without template but with internationalization """ + + return T('Hello World') + + +def hello3(): + """ page rendered by template simple_examples/index3.html or generic.html""" + + return dict(message='Hello World') + + +def hello4(): + """ page rendered by template simple_examples/index3.html or generic.html""" + + response.view = 'simple_examples/hello3.html' + return dict(message=T('Hello World')) + + +def hello5(): + """ generates full page in controller """ + + return HTML(BODY(H1(T('Hello World'), _style='color: red;'))).xml() # .xml to serialize + + +def hello6(): + """ page rendered with a flash""" + + response.flash = 'Hello World in a flash!' + return dict(message=T('Hello World')) + + +def status(): + """ page that shows internal status""" + response.view = 'generic.html' + return dict(request=request, session=session, response=response) + + +def redirectme(): + """ redirects to /{{=request.application}}/{{=request.controller}}/hello3 """ + + redirect(URL('hello3')) + + +def raisehttp(): + """ returns an HTTP 400 ERROR page """ + + raise HTTP(400, 'internal error') + + +def raiseexception(): + """ generates an exeption, logs the event and returns a ticket number """ + + 1 / 0 + return 'oops' + + +def servejs(): + """ serves a js document """ + + import gluon.contenttype + response.headers['Content-Type'] = \ + gluon.contenttype.contenttype('.js') + return 'alert("This is a Javascript document, it is not supposed to run!");' + + +def makejson(): + import gluon.contrib.simplejson as sj + return sj.dumps(['foo', {'bar': ('baz', None, 1.0, 2)}]) + + +def makertf(): + import gluon.contrib.pyrtf as q + doc = q.Document() + section = q.Section() + doc.Sections.append(section) + section.append('Section Title') + section.append('web2py is great. ' * 100) + response.headers['Content-Type'] = 'text/rtf' + return q.dumps(doc) + + +def rss_aggregator(): + import datetime + import gluon.contrib.rss2 as rss2 + import gluon.contrib.feedparser as feedparser + d = feedparser.parse('http://rss.slashdot.org/Slashdot/slashdot/to') + + rss = rss2.RSS2(title=d.channel.title, link=d.channel.link, + description=d.channel.description, + lastBuildDate=datetime.datetime.now(), + items=[rss2.RSSItem(title=entry.title, + link=entry.link, description=entry.description, + pubDate=datetime.datetime.now()) for entry in + d.entries]) + response.headers['Content-Type'] = 'application/rss+xml' + return rss2.dumps(rss) + + + +def ajaxwiki(): + default=""" +# section + +## subsection + +### sub subsection + +- **bold** text +- ''italic'' +- [[link http://google.com]] + +`` +def index: return 'hello world' +`` + +----------- +Quoted text +----------- + +--------- +0 | 0 | 1 +0 | 2 | 0 +3 | 0 | 0 +--------- +""" + form = FORM(TEXTAREA(_id='text',_name='text',value=default), + INPUT(_type='button', + _value='markmin', + _onclick="ajax('ajaxwiki_onclick',['text'],'html')")) + return dict(form=form, html=DIV(_id='html')) + +def ajaxwiki_onclick(): + return MARKMIN(request.vars.text).xml() ADDED applications/examples/controllers/spreadsheet.py Index: applications/examples/controllers/spreadsheet.py ================================================================== --- applications/examples/controllers/spreadsheet.py +++ applications/examples/controllers/spreadsheet.py @@ -0,0 +1,9 @@ +from gluon.contrib.spreadsheet import Sheet + +def callback(): + return cache.ram('sheet1',lambda:None,None).process(request) + +def index(): + sheet = cache.ram('sheet1',lambda:Sheet(10,10,URL('callback')),0) + #sheet.cell('r0c3',value='=r0c0+r0c1+r0c2',readonly=True) + return dict(sheet=sheet) ADDED applications/examples/controllers/template_examples.py Index: applications/examples/controllers/template_examples.py ================================================================== --- applications/examples/controllers/template_examples.py +++ applications/examples/controllers/template_examples.py @@ -0,0 +1,35 @@ + + + +def variables(): + return dict(a=10, b=20) + + +def test_for(): + return dict() + + +def test_if(): + return dict() + + +def test_try(): + return dict() + + +def test_def(): + return dict() + + +def escape(): + return dict(message='

    text is scaped

    ') + + +def xml(): + return dict(message=XML('

    text is not escaped

    ')) + + +def beautify(): + return dict(message=BEAUTIFY(request)) + + ADDED applications/examples/models/db.py Index: applications/examples/models/db.py ================================================================== --- applications/examples/models/db.py +++ applications/examples/models/db.py @@ -0,0 +1,69 @@ +######################################################################### +## This scaffolding model makes your app work on Google App Engine too +######################################################################### + +if request.controller.endswith('_examples'): response.generic_patterns.append('*') + +from gluon.settings import settings + +# if running on Google App Engine +if settings.web2py_runtime_gae: + from gluon.contrib.gql import * + # connect to Google BigTable + db = DAL('gae') + # and store sessions there + session.connect(request, response, db=db) +else: + # if not, use SQLite or other DB + db = DAL('sqlite://storage.sqlite') + +db.define_table( + 'users', + Field('name'), + Field('email') + ) + +# ONE (users) TO MANY (dogs) + +db.define_table( + 'dogs', + Field('owner_id', db.users), + Field('name'), + Field('type'), + Field('vaccinated', 'boolean', default=False), + Field('picture', 'upload', default=''), + ) + +db.define_table( + 'products', + Field('name'), + Field('description', 'text') + ) + +# MANY (users) TO MANY (purchases) + +db.define_table( + 'purchases', + Field('buyer_id', db.users), + Field('product_id', db.products), + Field('quantity', 'integer') + ) + +# if running on Google App Engine +if settings.web2py_runtime_gae: + # quick hack to skip the join + purchased = None +else: + # use a joined view + purchased = (db.users.id == db.purchases.buyer_id) & (db.products.id + == db.purchases.product_id) + +db.users.name.requires = IS_NOT_EMPTY() +db.users.email.requires = [IS_EMAIL(), IS_NOT_IN_DB(db, 'users.email')] +db.dogs.owner_id.requires = IS_IN_DB(db, 'users.id', 'users.name') +db.dogs.name.requires = IS_NOT_EMPTY() +db.dogs.type.requires = IS_IN_SET(['small', 'medium', 'large']) +db.purchases.buyer_id.requires = IS_IN_DB(db, 'users.id', 'users.name') +db.purchases.product_id.requires = IS_IN_DB(db, 'products.id', + 'products.name') +db.purchases.quantity.requires = IS_INT_IN_RANGE(0, 10) ADDED applications/examples/models/feeds_reader.py Index: applications/examples/models/feeds_reader.py ================================================================== --- applications/examples/models/feeds_reader.py +++ applications/examples/models/feeds_reader.py @@ -0,0 +1,48 @@ + +def group_feed_reader(group,mode='div',counter='5'): + """parse group feeds""" + + url = "http://groups.google.com/group/%s/feed/rss_v2_0_topics.xml?num=%s" %\ + (group,counter) + from gluon.contrib import feedparser + g = feedparser.parse(url) + + if mode == 'div': + html = XML(TAG.BLOCKQUOTE(UL(*[LI(A(entry['title']+' - ' +\ + entry['author'][entry['author'].rfind('('):],\ + _href=entry['link'],_target='_blank'))\ + for entry in g['entries'] ]),\ + _class="boxInfo",\ + _style="padding-bottom:5px;")) + + else: + html = XML(UL(*[LI(A(entry['title']+' - ' +\ + entry['author'][entry['author'].rfind('('):],\ + _href=entry['link'],_target='_blank'))\ + for entry in g['entries'] ])) + + return html + + +def code_feed_reader(project,mode='div'): + """parse code feeds""" + + url = "http://code.google.com/feeds/p/%s/hgchanges/basic" % project + from gluon.contrib import feedparser + g = feedparser.parse(url) + if mode == 'div': + html = XML(DIV(UL(*[LI(A(entry['title'],_href=entry['link'],\ + _target='_blank'))\ + for entry in g['entries'][0:5]]),\ + _class="boxInfo",\ + _style="padding-bottom:5px;")) + else: + html = XML(UL(*[LI(A(entry['title'],_href=entry['link'],\ + _target='_blank'))\ + for entry in g['entries'][0:5]])) + + + return html + + + ADDED applications/examples/models/markmin.py Index: applications/examples/models/markmin.py ================================================================== --- applications/examples/models/markmin.py +++ applications/examples/models/markmin.py @@ -0,0 +1,37 @@ +import gluon.template + +markmin_dict = dict(template=lambda \ + code:gluon.template.render(code,context=globals()), + sup=lambda \ + code:'%s'%code, + br=lambda n:'
    '*int(n), + groupdates=lambda group:group_feed_reader(group), + ) + +def get_content(b=None,\ + c=request.controller,\ + f=request.function,\ + l='en',\ + format='markmin'): + """Gets and renders the file in + /private/content////. + """ + + def openfile(): + import os + path = os.path.join(request.folder,'private','content',l,c,f,b+'.'+format) + return open(path) + + try: + openedfile = openfile() + except Exception, IOError: + l='en' + openedfile = openfile() + + if format == 'markmin': + html = MARKMIN(str(T(openedfile.read())),markmin_dict) + else: + html = str(T(openedfile.read())) + openedfile.close() + + return html ADDED applications/examples/models/menu.py Index: applications/examples/models/menu.py ================================================================== --- applications/examples/models/menu.py +++ applications/examples/models/menu.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- + +######################################################################### +## Changes the menu active item +######################################################################### +def toggle_menuclass(cssclass='pressed',menuid='headermenu'): + """This function changes the menu class to put pressed appearance""" + + positions = dict( + index='', + what='-108px -115px', + download='-211px -115px', + who='-315px -115px', + support='-418px -115px', + documentation='-520px -115px' + ) + + + if request.function in positions.keys(): + jscript = """ + + """ % dict(cssclass=cssclass, + menuid=menuid, + function=request.function, + cssposition=positions[request.function] + ) + + return XML(jscript) + else: + return '' ADDED applications/examples/private/content/en/default/documentation/community.markmin Index: applications/examples/private/content/en/default/documentation/community.markmin ================================================================== --- applications/examples/private/content/en/default/documentation/community.markmin +++ applications/examples/private/content/en/default/documentation/community.markmin @@ -0,0 +1,9 @@ +### Community sources + +#### Sites +- [[web2pyslices (recipes) http://web2pyslices.com popup]] +- [[web2py utils documentation http://packages.python.org/web2py_utils/ popup]] +- [[web2pybrasil http://www.web2pybrasil.com.br popup]] +- Apostila em português [[Download http://dl.dropbox.com/u/830444/apostila_web2py_basico.pdf popup]] e [[Online http://web2pybrasil.appspot.com/init/plugin_wiki/page/curso-web2py-000 popup]] +- [[Sitio en español http://www.web2py.com.ar popup]] +- [[Documentación en español http://www.web2py.com.ar/examples/default/docs popup]] ADDED applications/examples/private/content/en/default/documentation/main.markmin Index: applications/examples/private/content/en/default/documentation/main.markmin ================================================================== --- applications/examples/private/content/en/default/documentation/main.markmin +++ applications/examples/private/content/en/default/documentation/main.markmin @@ -0,0 +1,4 @@ +## web2py``TM``:sup Documentation + +Here is listed knows sources of documentation on web2py +you can submit your own source [[here contact]] ADDED applications/examples/private/content/en/default/documentation/more.markmin Index: applications/examples/private/content/en/default/documentation/more.markmin ================================================================== --- applications/examples/private/content/en/default/documentation/more.markmin +++ applications/examples/private/content/en/default/documentation/more.markmin @@ -0,0 +1,10 @@ +### More resources + +- [[videos http://www.web2py.com/examples/default/videos/ popup]] +- [[Quick Examples http://web2py.com/examples/default/examples popup]] +- [[FAQ http://www.web2py.com/AlterEgo popup]] +- [[Appliances http://web2py.com/appliances popup]] +- [[User Voice http://web2py.uservoice.com/ popup]] +- [[web2py utils http://packages.python.org/web2py_utils/ popup]] +- [[Layouts http://web2py.com/layouts popup]] +- [[Plugins http://web2py.com/plugins popup]] ADDED applications/examples/private/content/en/default/documentation/official.markmin Index: applications/examples/private/content/en/default/documentation/official.markmin ================================================================== --- applications/examples/private/content/en/default/documentation/official.markmin +++ applications/examples/private/content/en/default/documentation/official.markmin @@ -0,0 +1,8 @@ +### Official documentation + +#### Books +- [[**web2py full/free online book (english)** http://web2py.com/book popup]] +- [[web2py book (spanish) http://www.latinuxpress.com/books/drafts/web2py/ popup]] +- [[buy e-book/printed version http://stores.lulu.com/web2py popup]] +- [[Epydoc http://www.web2py.com/examples/static/epydoc/index.html popup]] +- [[API http://web2py.com/book/default/chapter/04#API popup]] ADDED applications/examples/private/content/en/default/index/maincontent.markmin Index: applications/examples/private/content/en/default/index/maincontent.markmin ================================================================== --- applications/examples/private/content/en/default/index/maincontent.markmin +++ applications/examples/private/content/en/default/index/maincontent.markmin @@ -0,0 +1,2 @@ +## web2py``TM``:sup Web Framework +Free open source full-stack framework for rapid development of fast, scalable, [[secure http://www.web2py.com/book/default/chapter/01#security popup]] and portable database-driven web-based applications. Written and programmable in [[Python http://www.python.org popup]]. [[LGPLv3 License http://www.gnu.org/licenses/lgpl.html]] ADDED applications/examples/private/content/en/default/index/whyweb2py.markmin Index: applications/examples/private/content/en/default/index/whyweb2py.markmin ================================================================== --- applications/examples/private/content/en/default/index/whyweb2py.markmin +++ applications/examples/private/content/en/default/index/whyweb2py.markmin @@ -0,0 +1,12 @@ +### Why web2py? + +- **Created by a community of professionals** and University professors in Computer Science and Software Engineering. +- **Always backward compatible.** We have not broken backward compatibility since version 1.0 in 2007, and we pledge not to break it in the future. +- **Easy to run.** It requires no installation and no configuration. +- **Runs on** Windows, Mac, Unix/Linux, Google App Engine, Amazon EC2, and almost any web hosting via Python 2.5/2.6/2.7, or Java with Jython. +- **Runs with** Apache, Lighttpd, Cherokee and almost any other web server via CGI, FastCGI, WSGI, mod_proxy, and/or mod_python. It can embed third party WSGI apps and middleware. +- **Talks to** SQLite, PostgreSQL, MySQL, MSSQL, FireBird, Oracle, IBM DB2, Informix, Ingres, and Google App Engine. +- **Secure** [[It prevents the most common types of vulnerabilities http://web2py.com/examples/default/security]] including Cross Site Scripting, Injection Flaws, and Malicious File Execution. +- **Enforces good Software Engineering practices** (Model-View-Controller design, Server-side form validation, postbacks) that make the code more readable, scalable, and maintainable. +- **Speaks multiple protocols** HTML/XML, RSS/ATOM, RTF, PDF, JSON, AJAX, XML-RPC, CSV, REST, WIKI, Flash/AMF, and Linked Data (RDF). +- **Includes** a SSL-enabled and streaming-capable web server, a relational database, a web-based integrated development environment and web-based management interface, a Database Abstraction Layer that writes SQL for you in real time, internationalization support, multiple authentication methods, role based access control, an error logging and ticketing system, multiple caching methods for scalability, the jQuery library for AJAX and effects. [[Read more... http://web2py.com/examples/default/what]] ADDED applications/examples/private/content/en/default/usergroups/grouplist.markmin Index: applications/examples/private/content/en/default/usergroups/grouplist.markmin ================================================================== --- applications/examples/private/content/en/default/usergroups/grouplist.markmin +++ applications/examples/private/content/en/default/usergroups/grouplist.markmin @@ -0,0 +1,42 @@ +# web2py usergroups + +You can submit a new group to be listed [[here contact]] +## International Group (English) + +Main group managed by Massimo Di Pierro + +- [[http://groups.google.com/group/web2py/ http://groups.google.com/group/web2py/ popup]] + +``web2py``:groupdates + +## Brazilian Group + +Portuguese speakers group + +- [[http://groups.google.com/group/web2py-users-brazil http://groups.google.com/group/web2py-users-brazil popup]] + +``web2py-users-brazil``:groupdates + +## Spanish Group + +Spanish speakers group + +- [[http://groups.google.com/group/web2py-usuarios http://groups.google.com/group/web2py-usuarios popup]] + +``web2py-usuarios``:groupdates + +## Japanese Group + +Japanese speakers group + +- [[http://groups.google.com/group/web2py-japan http://groups.google.com/group/web2py-japan popup]] + +``web2py-japan``:groupdates + +## French Group + +French speakers group + +- [[http://groups.google.com/group/web2py-fr http://groups.google.com/group/web2py-fr popup]] + +``web2py-fr``:groupdates ADDED applications/examples/static/artwork.tar.gz Index: applications/examples/static/artwork.tar.gz ================================================================== --- applications/examples/static/artwork.tar.gz +++ applications/examples/static/artwork.tar.gz cannot compute difference between binary files ADDED applications/examples/static/bg.gif Index: applications/examples/static/bg.gif ================================================================== --- applications/examples/static/bg.gif +++ applications/examples/static/bg.gif cannot compute difference between binary files ADDED applications/examples/static/bg.png Index: applications/examples/static/bg.png ================================================================== --- applications/examples/static/bg.png +++ applications/examples/static/bg.png cannot compute difference between binary files ADDED applications/examples/static/css/artwork.css Index: applications/examples/static/css/artwork.css ================================================================== --- applications/examples/static/css/artwork.css +++ applications/examples/static/css/artwork.css @@ -0,0 +1,141 @@ + +/*---------------------------------- ARTWORK E STICKERS -----------------------------------------*/ +/*logo*/ +.logosDow{ + border-width: 1px; + border-style: solid; + border-color:#CCC; + border-radius:3px; + background-color:#FFF; + -webkit-border-radius: 3px; + -moz-border-radius:3px; + margin:20px auto; + background: -moz-linear-gradient(top, #fbfbfb, #f1f1f1) repeat-X; + background: -webkit-gradient(linear, left top, left bottom, from(#fbfbfb), to(#f1f1f1)) repeat-X; + -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#666, endColorstr=#FFFFFFFF)"; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#fbfbfb, endColorstr=#f1f1f1); + border-width: 1px; + border-style: solid; + border-color:#CCC; + border-radius:3px; + background-color:#FFF; + -webkit-border-radius: 3px; + -moz-border-radius:3px; + width:100%; + + padding:10px 10px 0 10px; + padding-bottom:0 + } +.WH1{ + height:190px; + } +.WH2{ + height:90px; + + } +.logosDow span{ + margin:8px 15px; + float:left; + width:700px; height:20px; + color:#555555; + font:bold 18px/30px Arial,Helvetica,sans-serif; + letter-spacing:-1px; +} +.box-A{ + margin:10px; + float:left; + border-width: 1px; + border-style: solid; + border-color:#CCC; + border-radius:3px; + background-color:#FFF; + -webkit-border-radius: 3px; + -moz-border-radius:3px; + width:250px; height:130px; +} +a.box-A span{ + display:none; + position:relative; + top:-55px; + left:-10px; + width:235px; + height:50px; + background-image:url(../img/tipDownloads2.png); + background-position:center; + background-repeat:no-repeat; +} +a.box-A:hover span{ + display:block; +} +.logoDow1{ + background-image:url(../img/logo3Tones.png); + background-repeat:no-repeat; + background-position:-10px -155px; +} +.logoDow2{ + background-image:url(../img/logo3Tones.png); + background-repeat:no-repeat; + background-position:-10px 0px; +} +.logoDow3{ + background-image:url(../img/logo3Tones.png); + background-repeat:no-repeat; + background-position:-10px -312px; +} +/*fim logo*/ +/*Stick*/ +.stikImage{ + float:left; + width:100px; + height:50px; + margin-left:15px; + background-repeat:no-repeat; + background-position:center; + + } +.stikimage1{ + background-image:url(../img/Stickers1.png); + background-position:center; +} +.stikimage2{ + background-image:url(../img/Stickers2.png); + background-position:center; +} +.stikimage3{ + background-image:url(../img/Stickers3.png); + background-position:center; +} +.stikimage4{ + background-image:url(../img/Stickers8.png); + background-position:center; +} +.stikimage5{ + background-image:url(../img/Stickers5.png); + background-position:center; +} +.stikimage6{ + background-image:url(../img/Stickers6.png); + background-position:center; +} +.stikimage7{ + background-image:url(../img/Stickers7.png); + background-position:center; +} +a.stikImage span{ + display:none; + position:relative; + top:-50px; + left:-50px; + width:180px; + height:50px; + background-image:url(../img/tipDownloads.png); + background-position:center; + background-repeat:no-repeat; +} +a.stikImage:hover span{ + display:block; +} +/*fim do Stick*/ + +/*------------------------------ FIM ARTWORK E STICKERS -----------------------------------------*/ + ADDED applications/examples/static/css/calendar.css Index: applications/examples/static/css/calendar.css ================================================================== --- applications/examples/static/css/calendar.css +++ applications/examples/static/css/calendar.css @@ -0,0 +1,4 @@ +.calendar{z-index:99;position:relative;display:none;border-top:2px solid #fff;border-right:2px solid #000;border-bottom:2px solid #000;border-left:2px solid #fff;font-size:11px;color:#000;cursor:default;background:#d4d0c8;font-family:tahoma,verdana,sans-serif;}.calendar table{border-top:1px solid #000;border-right:1px solid #fff;border-bottom:1px solid #fff;border-left:1px solid #000;font-size:11px;color:#000;cursor:default;background:#d4d0c8;font-family:tahoma,verdana,sans-serif;}.calendar .button{text-align:center;padding:1px;border-top:1px solid #fff;border-right:1px solid #000;border-bottom:1px solid #000;border-left:1px solid #fff;}.calendar .nav{background:transparent}.calendar thead .title{font-weight:bold;padding:1px;border:1px solid #000;background:#848078;color:#fff;text-align:center;}.calendar thead .name{border-bottom:1px solid #000;padding:2px;text-align:center;background:#f4f0e8;}.calendar thead .weekend{color:#f00;}.calendar thead .hilite{border-top:2px solid #fff;border-right:2px solid #000;border-bottom:2px solid #000;border-left:2px solid #fff;padding:0;background-color:#e4e0d8;}.calendar thead .active{padding:2px 0 0 2px;border-top:1px solid #000;border-right:1px solid #fff;border-bottom:1px solid #fff;border-left:1px solid #000;background-color:#c4c0b8;}.calendar tbody .day{width:2em;text-align:right;padding:2px 4px 2px 2px;}.calendar tbody .day.othermonth{font-size:80%;color:#aaa;}.calendar tbody .day.othermonth.oweekend{color:#faa;}.calendar table .wn{padding:2px 3px 2px 2px;border-right:1px solid #000;background:#f4f0e8;}.calendar tbody .rowhilite td{background:#e4e0d8;}.calendar tbody .rowhilite td.wn{background:#d4d0c8;}.calendar tbody td.hilite{padding:1px 3px 1px 1px;border-top:1px solid #fff;border-right:1px solid #000;border-bottom:1px solid #000;border-left:1px solid #fff;}.calendar tbody td.active{padding:2px 2px 0 2px;border-top:1px solid #000;border-right:1px solid #fff;border-bottom:1px solid #fff;border-left:1px solid #000;}.calendar tbody td.selected{font-weight:bold;border-top:1px solid #000;border-right:1px solid #fff;border-bottom:1px solid #fff;border-left:1px solid #000;padding:2px 2px 0 2px;background:#e4e0d8;}.calendar tbody td.weekend{color:#f00;}.calendar tbody td.today{font-weight:bold;color:#00f;}.calendar tbody .disabled{color:#999;}.calendar tbody .emptycell{visibility:hidden;}.calendar tbody .emptyrow{display:none;}.calendar tfoot .ttip{background:#f4f0e8;padding:1px;border:1px solid #000;background:#848078;color:#fff;text-align:center;}.calendar tfoot .hilite{border-top:1px solid #fff;border-right:1px solid #000;border-bottom:1px solid #000;border-left:1px solid #fff;padding:1px;background:#e4e0d8;}.calendar tfoot .active{padding:2px 0 0 2px;border-top:1px solid #000;border-right:1px solid #fff;border-bottom:1px solid #fff;border-left:1px solid #000;}.calendar .combo{position:absolute;display:none;width:4em;top:0;left:0;cursor:default;border-top:1px solid #fff;border-right:1px solid #000;border-bottom:1px solid #000;border-left:1px solid #fff;background:#e4e0d8;font-size:90%;padding:1px;z-index:100;}.calendar .combo .label,.calendar .combo .label-IEfix{text-align:center;padding:1px;}.calendar .combo .label-IEfix{width:4em;}.calendar .combo .active{background:#c4c0b8;padding:0;border-top:1px solid #000;border-right:1px solid #fff;border-bottom:1px solid #fff;border-left:1px solid #000;}.calendar .combo .hilite{background:#048;color:#fea;}.calendar td.time{border-top:1px solid #000;padding:1px 0;text-align:center;background-color:#f4f0e8;}.calendar td.time .hour,.calendar td.time .minute,.calendar td.time .ampm{padding:0 3px 0 4px;border:1px solid #889;font-weight:bold;background-color:#fff;}.calendar td.time .ampm{text-align:center;}.calendar td.time .colon{padding:0 2px 0 3px;font-weight:bold;}.calendar td.time span.hilite{border-color:#000;background-color:#766;color:#fff;}.calendar td.time span.active{border-color:#f00;background-color:#000;color:#0f0;} + +#CP_hourcont {z-index:99;padding: 0px;position:absolute;border:1px dashed #bbbbbb;background-color:#dddddd;display:none;} #CP_minutecont {z-index:99;background-color:#dddddd;padding: 0px;position:absolute;width:45px;border: 1px dashed #cccccc;display:none;} .floatleft {float:left;} .CP_hour {z-index:99;padding:1px;font-family: Arial, Helvetica, sans-serif;font-size:9px;white-space:nowrap;cursor:pointer;width:35px;margin:1px;background-color:#eeeeee;} .CP_minute {z-index:99;padding:1px;background-color:#eeeeee;font-family: Arial, Helvetica, sans-serif;font-size:9px;white-space:nowrap;cursor:pointer;width:auto;margin:1px;} .CP_over {z-index:99;background-color:#ffffff;} + ADDED applications/examples/static/css/home.css Index: applications/examples/static/css/home.css ================================================================== --- applications/examples/static/css/home.css +++ applications/examples/static/css/home.css @@ -0,0 +1,637 @@ +* { + margin:0; + padding:0; + } + +html, body { + height:100%; + background-color:#FFF;/*#f1f6f2;*/ + min-width:1020px; + font-size:14px; + /*font-family:Candara, Verdana, Arial, sans-serif;*/ + font-family:Helvetica,"Helvetica Neue",Arial,sans-serif; + } + +h1,h2,h3,h4,h5,h6 { + font-family: Helvetica,"Helvetica Neue",Arial,sans-serif; //'Garamond','Georgia',serif; + margin:15px 0 10px; + padding:0; + color: #678A86; + + } + +th, th {vertical-align:top} + +li { + padding-bottom:5px; + } + +a { + color: #832D20; //#732F2C; + text-decoration:none; + } + +a:hover { + text-shadow:0 1px 0 #FFF; + text-decoration:underline; + } + + +p { + border-bottom: 10px solid transparent; + color:#383838; + font-size:14px; + line-height:24px; + } + +pre { + font-family:'Consolas','Menlo','Deja Vu Sans Mono','Bitstream Vera Sans Mono',monospace !important; + } + + +.general { + min-height:100%; + position:relative; + width:100%; + } + +#font1 { + font-weight:bold; + font-size:12px; + font-family:Candara, Verdana, Arial, sans-serif; + line-height:19px; + color:#a7b6b5; + text-shadow:0 1px 0 #000; + } + + +/**************************** HEADER ************************************/ +.headerExt{ + top:0; + width:100%; + height:130px; + margin:0px auto; + background-image:url(../img/back-02.png); + background-repeat:repeat-x; + background-position:0px -57px; + z-index:99; + } + +.headerInt{ + position:relative; + width:982px; + height:130px; + margin:0px auto 0px auto; + } + +.boxLogin{ + float:right; + margin:8px 50px 0px 0px; + } + +.logo{ + position:absolute; + background-image:url(../img/web2py_logo.png); + background-position:16px 2px 0px 0px; + background-repeat:no-repeat; + width:230px; + height:50px; + top:33px; + left:45px; + } + + +/***************************** MAIN CONTENT ********************************/ +.content { + width:982px; + margin:0 auto; + overflow:hidden; + padding-bottom:100px; + } + +.onecolcontent { + color:#383838; + font-size:14px; + line-height:24px; + width:850px; + margin:0 auto; + height:auto; + } + +.contentleft { + color:#383838; + font-size:14px; + line-height:24px; + float:left; + width:650px; + height:auto; + } + +.contentright { + color:#383838; + font-size:14px; + line-height:24px; + float:right; + width:300px; + height:auto; + } + +/************************** FOOTER ******************************************/ + +.footerExt{ + background-image:url(../img/back-R-02.png); + background-repeat:repeat-x ; + background-position:0px -15px; + height:100px; + position:absolute; + bottom:0; + width:100%; + overflow:hidden; + + } + +.footerInt{ + position:relative; + width:982px; + height:70px; + margin:30px auto 0 auto; + } + +.Boxfooter1{ + position:absolute; + bottom:0px; + left:30px; + top:5px; + } + +.Boxfooter2{ + position:absolute; + left:130px; + top:5px; + } + +.Boxfooter3{ + position:absolute; + right:0; + top:2px; + } + +a.Boxfooter4{ + background-image:url(../img/1283522094_email.png); + background-position:center; + background-repeat:no-repeat; + position:absolute; + left:32px; + top:36px; + width:40px; + height:32px; + } + +a.Boxfooter5{ + background-image:url(../img/1283522082_phone.png); + background-position:center; + background-repeat:no-repeat; + position:absolute; + left:131px; + top:32px; + width:40px; + height:32px; + } + +.Boxfooter6{ + background-image:url(../img/Stickers4.png); + background-position:center; + background-repeat:no-repeat; + position:absolute; + left:840px; + top:24px; + width:135px; + height:44px; + } + +.Boxfooter7{ + position:absolute; + left:745px; + top:45px; + width:115px; + height:24px; + font-weight:bold; + font-size:12px; + font-family:Candara, Verdana, Arial, sans-serif; + font-style:italic; + line-height:19px; + color:#1B211F; + text-shadow:0 1px 0 #778E88; + } + + +/****************************** LINKS ************************************/ +a.login{ + font-weight:bold; + font-size:12px; + font-family:Candara, Verdana, Arial, sans-serif; + line-height:19px; + text-decoration:none; + color:#a7b6b5; + text-shadow:0 1px 0 #000; + } + +a.login:hover{ + color:#1B211F; + text-shadow:0 1px 0 #778E88; + } + +a.login:visited{ + color:#1B211F; + text-shadow:0 1px 0 #778E88; + } + + + +/********************DOWNLOAD****************************************/ +.boxBtDownload{ + width:240px; + height:57px; + position:relative; + margin:20px auto; + z-index:0; + } +.boxBtDownloadICO{ + width:40px; + height:40px; + float:right; + margin:6px 10px; + background-image:url(../img/netdow2.png);/*Download Icon 1 2 3 */ + background-position:center; + background-repeat:no-repeat; +} +#textBtDownload1{ + font-weight:bold; + font-size:26px; + font-family:Georgia,Palatino,"Palatino Linotype",Times,"Times New Roman",serif; + font-style:italic; + line-height:19px; + color:#1B211F; + text-shadow:0 1px 0 #b5b5b5; + text-decoration:none; + position:absolute; + left:35px; + top:15px; + } +#textBtDownload2{ + font-weight:bold; + font-size:12px; + font-family:Georgia,Palatino,"Palatino Linotype",Times,"Times New Roman",serif; + font-style:italic; + line-height:19px; + color:#747474; + text-shadow:0 1px 0 #FFFFFF; + text-decoration:none; + position:absolute; + left:18px; + top:34px; + } +a.btDownload{ + position:absolute; + + border-width: 1px; + border-style: solid; + border-color:#939593; + -moz-background-clip:border; + -moz-background-inline-policy:continuous; + -moz-background-origin:padding; + border-radius:5px; + -webkit-border-radius: 5px; + -moz-border-radius:5px; + background-image:url(../img/back-03.png); + background-position:bottom; + background-repeat:repeat-x; + text-decoration:none; + + } +.btDownloadPosicao1{ + width:240px; + height:57px; + } +.btDownloadPosicao2{ + width:250px; + height:57px; + } +a.btDownload:hover { + background-image:url(../img/back-05.png); + } + +.downloadb li a { + -moz-background-clip:border; + -moz-background-inline-policy:continuous; + -moz-background-origin:padding; + border-radius:5px; + -webkit-border-radius: 5px; + -moz-border-radius:5px; + color:#1B211F; + display:block; + font-size:93%; + margin:0; + padding:6% 40px 6% 6%; + line-height: 16px; + border-width: 1px; + border-style: solid; + border-color:#939593; + background-image:url(../img/back-03.png); + background-position:bottom; + background-repeat:repeat-x; + text-decoration:none; + + } + +.downloadb li a:hover {background-image:url(../img/back-05.png);} + +.downloadb li span{ + width:40px; + height:40px; + float:right; + /*margin:6px 10px;*/ + background-image:url(../img/netdow2.png);/*Download Icon 1 2 3 */ + background-position:center; + background-repeat:no-repeat; +} + +.downloadb li { + list-style-image:none; + list-style-position:outside; + list-style-type:none; + width: 200px; + height: 50px; + + + } + + +/********************************* BOX INFO 1 *****************************/ + +blockquote{ + width: 90%; + margin:10px; + padding: 3px 15px 10px 20px; + // padding: 5px; + border-width: 1px; + border-style: solid; + border-color:#CCC; + border-radius:5px; + background-color:#FFF; + -webkit-border-radius: 5px; + -moz-border-radius:5px; + // margin:20px auto; + text-align:left; + background: -moz-linear-gradient(top, #fbfbfb, #f1f1f1) repeat-X; + background: -webkit-gradient(linear, left top, left bottom, from(#fbfbfb), to(#f1f1f1)) repeat-X; + -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#666, endColorstr=#FFFFFF)"; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#fbfbfb, endColorstr=#f1f1f1); + border-width: 1px; + border-style: solid; + border-color:#CCC; + border-radius:5px; + background-color:#FFF; + -webkit-border-radius: 5px; + -moz-border-radius:5px; + font-size:12px; + } + +blockquote ul { + padding-left:15px; +// list-style-image: url(../img/bullet.gif); + } + +blockquote h5{ + margin:0; + padding:0; + font-size:12px; + } + +.boxInfoWH1{ + width: 80%; + height: auto; + } + +.boxInfoWH2{ + width:600px; + height: 100px; + } + + + +.boxCode{ + width: 90%; + margin:10px; + background-color:#fff; + text-align:left; + border-width: 1px; + border-style: solid; + border-color:#939593; + -moz-background-clip:border; + -moz-background-inline-policy:continuous; + -moz-background-origin:padding; + border-radius:5px; + -webkit-border-radius: 5px; + -moz-border-radius:5px; + padding:5px; + } + + + +.boxInfo2 a{ + color:#1B211F; + /*text-shadow:0 1px 0 #778E88;*/ + font-weight:bold; + font-size:12px; + line-height:19px; + text-decoration:none; + } + +.boxInfo2 a:hover{ + text-shadow:0 1px 0 #000; + } + +.boxInfo2 a:visited{ + color:#1B211F; + text-shadow:0 1px 0 #778E88; + } + + +/********************************* BOX BANNER *****************************/ +.boxInfoBanner{ + margin:20px auto; + border-width: 1px; + border-style: solid; + border-color:#939593; + -moz-background-clip:border; + -moz-background-inline-policy:continuous; + -moz-background-origin:padding; + border-radius:5px; + -webkit-border-radius: 5px; + -moz-border-radius:5px; + background-image:url(../img/online_book_cover.jpg); + background-position:center; + background-repeat:no-repeat; + width: 240px; + height: 175px; + /*-moz-box-shadow:1px 1px 4px #999999; + -webkit-box-shadow:0px 5px 10px #999999;*/ + cursor:pointer; + } + +.boxTopDesc{ + position:relative; + top:-25px; + font-weight:bold; + font-size:18px; + } + +.boxTopDesc:hover{ + color:#a7b6b5; + } + +.boxSimple{ + margin:auto; + border-width: 1px; + border-style: solid; + border-color:#939593; + -moz-background-clip:border; + -moz-background-inline-policy:continuous; + -moz-background-origin:padding; + border-radius:5px; + -webkit-border-radius: 5px; + -moz-border-radius:5px; + + background-position:center; + background-repeat:no-repeat; + width: 160px; + height: 106px; + left:0; + /*-moz-box-shadow:1px 1px 4px #999999; + -webkit-box-shadow:0px 5px 10px #999999;*/ + margin-right:110px; + margin-top:10px; + } + + +/******************************* BOX SEARCH *****************************/ + +.boxSearch1{ + width: 240px; + height: 27px; + margin:20px auto; + background-color:#f1f6f2; + text-align:left; + border-width: 1px; + border-style: solid; + border-color:#939593; + -moz-background-clip:border; + -moz-background-inline-policy:continuous; + -moz-background-origin:padding; + border-radius:5px; + -webkit-border-radius: 5px; + -moz-border-radius:5px; + background-image:url(../img/back-04.png); + background-position:bottom; + background-repeat:repeat-x; + } + +.boxSearch2{ + width:27px; + height:26px; + background-image:url(../img/icons/1283523626_search.png); + background-repeat:no-repeat; + background-position:center; + float:right; + } + + +/********************************* LEFT LINKS ************************/ +a.btleftlink{ + color:#383838; + font-family:Helvetica,Tahoma,Arial,Sans-Serif; + font-size:18px; + line-height:24px; + text-decoration:none; + float:left; + margin:4px 0px 0px 40px; + } + +a.btleftlink:hover{ + color:#a7b6b5; + } + +.leftlink{ + margin-left:30px; + width:245px; + height:30px; + background-repeat:no-repeat; + background-position:left; + } + +.leftlinkIcoA{ + background-image:url(../img/icons/usergroups.png); + } + +.leftlinkIcoB{ + background-image:url(../img/icons/twitter.png); + } + +.leftlinkIcoC{ + background-image:url(../img/icons/tutorials.png); + } + +.leftlinkIcoD{ + background-image:url(../img/icons/videos.png); + } + +.leftlinkIcoE{ + background-image:url(../img/icons/examples.png); + } + +.leftlinkIcoF{ + background-image:url(../img/icons/uservoice.png); + } + +.leftlinkIcoG{ + background-image:url(../img/icons/slices.png); + } +.leftlinkIcoH{ + background-image:url(../img/icons/demo.png); + } +.leftlinkIcoI{ + background-image:url(../img/icons/appliances.png); + } +.leftlinkIcoJ{ + background-image:url(../img/icons/plugins.png); + } +.leftlinkIcoK{ + background-image:url(../img/icons/livechat.png); + } + + +/************flash************************/ + +.flash{ + width:50%; + padding-bottom:5px; + margin:auto; + display:none; + + +} + +.flashdiv{ + position:fixed; + top:90px; + z-index:999; + width:100%; + margin:auto; + position:fixed; + text-align:center; + + +} + + ADDED applications/examples/static/css/menu.css Index: applications/examples/static/css/menu.css ================================================================== --- applications/examples/static/css/menu.css +++ applications/examples/static/css/menu.css @@ -0,0 +1,136 @@ +/*********** MENU COMPLETO **********/ +.headermenu{ + position:absolute; + margin:10px 10px; + background-image:url(../img/Menu-2.png); + background-repeat:no-repeat; + background-position:0px 0px; + width:655px; + height:37px; + left:290px; + top: 30px; + z-index:200px; +} +a.home{ + position:absolute; + top:0px; + left:0px; + z-index:0; + width:109px; + height:36px; +} +a:hover.home { + background-image:url(../img/Menu-2.png); + background-repeat:no-repeat; + background-position:0px -38px; +} +a:active.home { + background-image:url(../img/Menu-2.png); + background-repeat:no-repeat; + background-position:-0px -76px; + border-collapse:inherit; + +} +a.about{ + position:absolute; + top:0px; + left:108px; + z-index:1; + width:104px; + height:36px; +} +a:hover.about { + background-image:url(../img/Menu-2.png); + background-repeat:no-repeat; + background-position:-108px -37px; +} +a:active.about { + background-image:url(../img/Menu-2.png); + background-repeat:no-repeat; + background-position:-108px -76px +} + + +a.download{ + position:absolute; + top:0px; + left:211px; + z-index:2; + width:104px; + height:36px; +} +a:hover.download { + background-image:url(../img/Menu-2.png); + background-repeat:no-repeat; + background-position:-211px -37px; +} +a:active.download { + background-image:url(../img/Menu-2.png); + background-repeat:no-repeat; + background-position:-211px -76px +} +a.staff{ + position:absolute; + top:0px; + left:315px; + z-index:3; + width:103px; + height:36px; +} +a:hover.staff { + background-image:url(../img/Menu-2.png); + background-repeat:no-repeat; + background-position:-315px -37px; +} +a:active.staff { + background-image:url(../img/Menu-2.png); + background-repeat:no-repeat; + background-position:-315px -76px +} +a.support{ + position:absolute; + top:0px; + left:418px; + z-index:4; + width:103px; + height:36px; +} +a:hover.support { + background-image:url(../img/Menu-2.png); + background-repeat:no-repeat; + background-position:-418px -37px; +} +a:active.support { + background-image:url(../img/Menu-2.png); + background-repeat:no-repeat; + background-position:-418px -76px +} +a.documentation{ + position:absolute; + top:0px; + left:520px; + z-index:5; + width:133px; + height:36px; +} +a:hover.documentation { + background-image:url(../img/Menu-2.png); + background-repeat:no-repeat; + background-position:-520px -37px +} +a:active.documentation { + background-image:url(../img/Menu-2.png); + background-repeat:no-repeat; + background-position:-520px -76px; +} + +.pressed{ + z-index:0; + background-image:url(../img/Menu-2.png); + background-repeat:no-repeat; + + } + +/*************** FIM DO MENU COMPLETO **********************/ + + ADDED applications/examples/static/epydoc/api-objects.txt Index: applications/examples/static/epydoc/api-objects.txt ================================================================== --- applications/examples/static/epydoc/api-objects.txt +++ applications/examples/static/epydoc/api-objects.txt @@ -0,0 +1,5844 @@ +psycopg2 psycopg2-module.html +psycopg2.Binary psycopg2-module.html#Binary +psycopg2.NUMBER psycopg2-module.html#NUMBER +psycopg2.connect psycopg2-module.html#connect +psycopg2.threadsafety psycopg2-module.html#threadsafety +psycopg2.TimeFromTicks psycopg2-module.html#TimeFromTicks +psycopg2.Timestamp psycopg2-module.html#Timestamp +psycopg2.Date psycopg2-module.html#Date +psycopg2.TimestampFromTicks psycopg2-module.html#TimestampFromTicks +psycopg2.STRING psycopg2-module.html#STRING +psycopg2.DATETIME psycopg2-module.html#DATETIME +psycopg2.Time psycopg2-module.html#Time +psycopg2.DateFromTicks psycopg2-module.html#DateFromTicks +psycopg2.BINARY psycopg2-module.html#BINARY +psycopg2.paramstyle psycopg2-module.html#paramstyle +psycopg2.k psycopg2-module.html#k +psycopg2.ROWID psycopg2-module.html#ROWID +psycopg2.apilevel psycopg2-module.html#apilevel +psycopg2.tz psycopg2.tz-module.html +psycopg2.tz.STDOFFSET psycopg2.tz-module.html#STDOFFSET +psycopg2.tz.DSTDIFF psycopg2.tz-module.html#DSTDIFF +psycopg2.tz.DSTOFFSET psycopg2.tz-module.html#DSTOFFSET +psycopg2.tz.ZERO psycopg2.tz-module.html#ZERO +psycopg2.tz.LOCAL psycopg2.tz-module.html#LOCAL +sqlite3.dbapi2 sqlite3.dbapi2-module.html +sqlite3.dbapi2.SQLITE_DROP_VIEW sqlite3.dbapi2-module.html#SQLITE_DROP_VIEW +sqlite3.dbapi2.SQLITE_CREATE_TRIGGER sqlite3.dbapi2-module.html#SQLITE_CREATE_TRIGGER +sqlite3.dbapi2.version sqlite3.dbapi2-module.html#version +sqlite3.dbapi2.SQLITE_OK sqlite3.dbapi2-module.html#SQLITE_OK +sqlite3.dbapi2.SQLITE_DELETE sqlite3.dbapi2-module.html#SQLITE_DELETE +sqlite3.dbapi2.PARSE_DECLTYPES sqlite3.dbapi2-module.html#PARSE_DECLTYPES +sqlite3.dbapi2.SQLITE_DENY sqlite3.dbapi2-module.html#SQLITE_DENY +sqlite3.dbapi2.paramstyle sqlite3.dbapi2-module.html#paramstyle +sqlite3.dbapi2.SQLITE_DROP_TEMP_VIEW sqlite3.dbapi2-module.html#SQLITE_DROP_TEMP_VIEW +sqlite3.dbapi2.SQLITE_DROP_INDEX sqlite3.dbapi2-module.html#SQLITE_DROP_INDEX +sqlite3.dbapi2.SQLITE_CREATE_TEMP_INDEX sqlite3.dbapi2-module.html#SQLITE_CREATE_TEMP_INDEX +sqlite3.dbapi2.SQLITE_IGNORE sqlite3.dbapi2-module.html#SQLITE_IGNORE +sqlite3.dbapi2.sqlite_version_info sqlite3.dbapi2-module.html#sqlite_version_info +sqlite3.dbapi2.SQLITE_ATTACH sqlite3.dbapi2-module.html#SQLITE_ATTACH +sqlite3.dbapi2.SQLITE_SELECT sqlite3.dbapi2-module.html#SQLITE_SELECT +sqlite3.dbapi2.SQLITE_CREATE_TABLE sqlite3.dbapi2-module.html#SQLITE_CREATE_TABLE +sqlite3.dbapi2.SQLITE_READ sqlite3.dbapi2-module.html#SQLITE_READ +sqlite3.dbapi2.SQLITE_CREATE_VIEW sqlite3.dbapi2-module.html#SQLITE_CREATE_VIEW +sqlite3.dbapi2.SQLITE_CREATE_TEMP_TRIGGER sqlite3.dbapi2-module.html#SQLITE_CREATE_TEMP_TRIGGER +sqlite3.dbapi2.SQLITE_PRAGMA sqlite3.dbapi2-module.html#SQLITE_PRAGMA +sqlite3.dbapi2.adapters sqlite3.dbapi2-module.html#adapters +sqlite3.dbapi2.SQLITE_CREATE_INDEX sqlite3.dbapi2-module.html#SQLITE_CREATE_INDEX +sqlite3.dbapi2.threadsafety sqlite3.dbapi2-module.html#threadsafety +sqlite3.dbapi2.SQLITE_ANALYZE sqlite3.dbapi2-module.html#SQLITE_ANALYZE +sqlite3.dbapi2.SQLITE_DROP_TEMP_TRIGGER sqlite3.dbapi2-module.html#SQLITE_DROP_TEMP_TRIGGER +sqlite3.dbapi2.converters sqlite3.dbapi2-module.html#converters +sqlite3.dbapi2.SQLITE_CREATE_TEMP_VIEW sqlite3.dbapi2-module.html#SQLITE_CREATE_TEMP_VIEW +sqlite3.dbapi2.SQLITE_INSERT sqlite3.dbapi2-module.html#SQLITE_INSERT +sqlite3.dbapi2.SQLITE_DROP_TEMP_INDEX sqlite3.dbapi2-module.html#SQLITE_DROP_TEMP_INDEX +sqlite3.dbapi2.SQLITE_UPDATE sqlite3.dbapi2-module.html#SQLITE_UPDATE +sqlite3.dbapi2.version_info sqlite3.dbapi2-module.html#version_info +sqlite3.dbapi2.SQLITE_DETACH sqlite3.dbapi2-module.html#SQLITE_DETACH +sqlite3.dbapi2.SQLITE_ALTER_TABLE sqlite3.dbapi2-module.html#SQLITE_ALTER_TABLE +sqlite3.dbapi2.sqlite_version sqlite3.dbapi2-module.html#sqlite_version +sqlite3.dbapi2.SQLITE_DROP_TEMP_TABLE sqlite3.dbapi2-module.html#SQLITE_DROP_TEMP_TABLE +sqlite3.dbapi2.PARSE_COLNAMES sqlite3.dbapi2-module.html#PARSE_COLNAMES +sqlite3.dbapi2.apilevel sqlite3.dbapi2-module.html#apilevel +sqlite3.dbapi2.DateFromTicks sqlite3.dbapi2-module.html#DateFromTicks +sqlite3.dbapi2.TimeFromTicks sqlite3.dbapi2-module.html#TimeFromTicks +sqlite3.dbapi2.SQLITE_REINDEX sqlite3.dbapi2-module.html#SQLITE_REINDEX +sqlite3.dbapi2.TimestampFromTicks sqlite3.dbapi2-module.html#TimestampFromTicks +sqlite3.dbapi2.SQLITE_CREATE_TEMP_TABLE sqlite3.dbapi2-module.html#SQLITE_CREATE_TEMP_TABLE +sqlite3.dbapi2.SQLITE_TRANSACTION sqlite3.dbapi2-module.html#SQLITE_TRANSACTION +sqlite3.dbapi2.x sqlite3.dbapi2-module.html#x +sqlite3.dbapi2.SQLITE_DROP_TRIGGER sqlite3.dbapi2-module.html#SQLITE_DROP_TRIGGER +sqlite3.dbapi2.SQLITE_DROP_TABLE sqlite3.dbapi2-module.html#SQLITE_DROP_TABLE +web2py.gluon web2py.gluon-module.html +web2py.gluon.TAG web2py.gluon-module.html#TAG +web2py.gluon.URL web2py.gluon-module.html#URL +web2py.gluon.ON web2py.gluon-module.html#ON +web2py.gluon.current web2py.gluon-module.html#current +web2py.gluon.redirect web2py.gluon-module.html#redirect +web2py.gluon.embed64 web2py.gluon-module.html#embed64 +web2py.gluon.admin web2py.gluon.admin-module.html +web2py.gluon.admin.app_create web2py.gluon.admin-module.html#app_create +web2py.gluon.admin.app_cleanup web2py.gluon.admin-module.html#app_cleanup +web2py.gluon.admin.plugin_pack web2py.gluon.admin-module.html#plugin_pack +web2py.gluon.admin.w2p_unpack web2py.gluon.fileutils-module.html#w2p_unpack +web2py.gluon.admin.write_file web2py.gluon.fileutils-module.html#write_file +web2py.gluon.admin.web2py_uuid web2py.gluon.utils-module.html#web2py_uuid +web2py.gluon.admin.check_new_version web2py.gluon.admin-module.html#check_new_version +web2py.gluon.admin.read_file web2py.gluon.fileutils-module.html#read_file +web2py.gluon.admin.w2p_pack_plugin web2py.gluon.fileutils-module.html#w2p_pack_plugin +web2py.gluon.admin.upgrade web2py.gluon.admin-module.html#upgrade +web2py.gluon.admin.w2p_pack web2py.gluon.fileutils-module.html#w2p_pack +web2py.gluon.admin.app_pack web2py.gluon.admin-module.html#app_pack +web2py.gluon.admin.recursive_unlink web2py.gluon.fileutils-module.html#recursive_unlink +web2py.gluon.admin.unzip web2py.gluon.admin-module.html#unzip +web2py.gluon.admin.create_missing_folders web2py.gluon.admin-module.html#create_missing_folders +web2py.gluon.admin.app_install web2py.gluon.admin-module.html#app_install +web2py.gluon.admin.app_compile web2py.gluon.admin-module.html#app_compile +web2py.gluon.admin.w2p_unpack_plugin web2py.gluon.fileutils-module.html#w2p_unpack_plugin +web2py.gluon.admin.abspath web2py.gluon.fileutils-module.html#abspath +web2py.gluon.admin.apath web2py.gluon.admin-module.html#apath +web2py.gluon.admin.plugin_install web2py.gluon.admin-module.html#plugin_install +web2py.gluon.admin.add_path_first web2py.gluon.admin-module.html#add_path_first +web2py.gluon.admin.app_pack_compiled web2py.gluon.admin-module.html#app_pack_compiled +web2py.gluon.admin.fix_newlines web2py.gluon.fileutils-module.html#fix_newlines +web2py.gluon.admin.up web2py.gluon.fileutils-module.html#up +web2py.gluon.admin.create_missing_app_folders web2py.gluon.admin-module.html#create_missing_app_folders +web2py.gluon.admin.app_uninstall web2py.gluon.admin-module.html#app_uninstall +web2py.gluon.cache web2py.gluon.cache-module.html +web2py.gluon.cache.DEFAULT_TIME_EXPIRE web2py.gluon.cache-module.html#DEFAULT_TIME_EXPIRE +web2py.gluon.cache.logger web2py.gluon.cache-module.html#logger +web2py.gluon.cfs web2py.gluon.cfs-module.html +web2py.gluon.cfs.cfs_lock web2py.gluon.cfs-module.html#cfs_lock +web2py.gluon.cfs.read_file web2py.gluon.fileutils-module.html#read_file +web2py.gluon.cfs.getcfs web2py.gluon.cfs-module.html#getcfs +web2py.gluon.cfs.cfs web2py.gluon.cfs-module.html#cfs +web2py.gluon.compileapp web2py.gluon.compileapp-module.html +web2py.gluon.compileapp.run_controller_in web2py.gluon.compileapp-module.html#run_controller_in +web2py.gluon.compileapp.is_gae web2py.gluon.compileapp-module.html#is_gae +web2py.gluon.compileapp.build_environment web2py.gluon.compileapp-module.html#build_environment +web2py.gluon.compileapp.TEST_CODE web2py.gluon.compileapp-module.html#TEST_CODE +web2py.gluon.compileapp.read_file web2py.gluon.fileutils-module.html#read_file +web2py.gluon.compileapp.write_file web2py.gluon.fileutils-module.html#write_file +web2py.gluon.compileapp.save_pyc web2py.gluon.compileapp-module.html#save_pyc +web2py.gluon.compileapp.run_view_in web2py.gluon.compileapp-module.html#run_view_in +web2py.gluon.compileapp.compile2 web2py.gluon.restricted-module.html#compile2 +web2py.gluon.compileapp.redirect web2py.gluon.http-module.html#redirect +web2py.gluon.compileapp.is_jython web2py.gluon.compileapp-module.html#is_jython +web2py.gluon.compileapp.getcfs web2py.gluon.cfs-module.html#getcfs +web2py.gluon.compileapp.compile_models web2py.gluon.compileapp-module.html#compile_models +web2py.gluon.compileapp.logger web2py.gluon.compileapp-module.html#logger +web2py.gluon.compileapp.listdir web2py.gluon.fileutils-module.html#listdir +web2py.gluon.compileapp.read_pyc web2py.gluon.compileapp-module.html#read_pyc +web2py.gluon.compileapp.run_models_in web2py.gluon.compileapp-module.html#run_models_in +web2py.gluon.compileapp.parse_template web2py.gluon.template-module.html#parse_template +web2py.gluon.compileapp.compile_views web2py.gluon.compileapp-module.html#compile_views +web2py.gluon.compileapp.test web2py.gluon.compileapp-module.html#test +web2py.gluon.compileapp.local_import_aux web2py.gluon.compileapp-module.html#local_import_aux +web2py.gluon.compileapp.remove_compiled_application web2py.gluon.compileapp-module.html#remove_compiled_application +web2py.gluon.compileapp.mktree web2py.gluon.fileutils-module.html#mktree +web2py.gluon.compileapp.compile_controllers web2py.gluon.compileapp-module.html#compile_controllers +web2py.gluon.compileapp.restricted web2py.gluon.restricted-module.html#restricted +web2py.gluon.compileapp.compile_application web2py.gluon.compileapp-module.html#compile_application +web2py.gluon.contenttype web2py.gluon.contenttype-module.html +web2py.gluon.contenttype.contenttype web2py.gluon.contenttype-module.html#contenttype +web2py.gluon.contenttype.CONTENT_TYPE web2py.gluon.contenttype-module.html#CONTENT_TYPE +web2py.gluon.contrib.pymysql web2py.gluon.contrib.pymysql-module.html +web2py.gluon.contrib.pymysql.Binary web2py.gluon.contrib.pymysql-module.html#Binary +web2py.gluon.contrib.pymysql.NUMBER web2py.gluon.contrib.pymysql-module.html#NUMBER +web2py.gluon.contrib.pymysql.connect web2py.gluon.contrib.pymysql-module.html#connect +web2py.gluon.contrib.pymysql.escape_dict web2py.gluon.contrib.pymysql-module.html#escape_dict +web2py.gluon.contrib.pymysql.DATE web2py.gluon.contrib.pymysql-module.html#DATE +web2py.gluon.contrib.pymysql.escape_string web2py.gluon.contrib.pymysql-module.html#escape_string +web2py.gluon.contrib.pymysql.NULL web2py.gluon.contrib.pymysql-module.html#NULL +web2py.gluon.contrib.pymysql.threadsafety web2py.gluon.contrib.pymysql-module.html#threadsafety +web2py.gluon.contrib.pymysql.thread_safe web2py.gluon.contrib.pymysql-module.html#thread_safe +web2py.gluon.contrib.pymysql.TimeFromTicks web2py.gluon.contrib.pymysql-module.html#TimeFromTicks +web2py.gluon.contrib.pymysql.install_as_MySQLdb web2py.gluon.contrib.pymysql-module.html#install_as_MySQLdb +web2py.gluon.contrib.pymysql.TimestampFromTicks web2py.gluon.contrib.pymysql-module.html#TimestampFromTicks +web2py.gluon.contrib.pymysql.STRING web2py.gluon.contrib.pymysql-module.html#STRING +web2py.gluon.contrib.pymysql.version_info web2py.gluon.contrib.pymysql-module.html#version_info +web2py.gluon.contrib.pymysql.escape_sequence web2py.gluon.contrib.pymysql-module.html#escape_sequence +web2py.gluon.contrib.pymysql.DATETIME web2py.gluon.contrib.pymysql-module.html#DATETIME +web2py.gluon.contrib.pymysql.Connection web2py.gluon.contrib.pymysql-module.html#Connection +web2py.gluon.contrib.pymysql.VERSION web2py.gluon.contrib.pymysql-module.html#VERSION +web2py.gluon.contrib.pymysql.paramstyle web2py.gluon.contrib.pymysql-module.html#paramstyle +web2py.gluon.contrib.pymysql.DateFromTicks web2py.gluon.contrib.pymysql-module.html#DateFromTicks +web2py.gluon.contrib.pymysql.BINARY web2py.gluon.contrib.pymysql-module.html#BINARY +web2py.gluon.contrib.pymysql.TIMESTAMP web2py.gluon.contrib.pymysql-module.html#TIMESTAMP +web2py.gluon.contrib.pymysql.get_client_info web2py.gluon.contrib.pymysql-module.html#get_client_info +web2py.gluon.contrib.pymysql.Connect web2py.gluon.contrib.pymysql-module.html#Connect +web2py.gluon.contrib.pymysql.ROWID web2py.gluon.contrib.pymysql-module.html#ROWID +web2py.gluon.contrib.pymysql.TIME web2py.gluon.contrib.pymysql-module.html#TIME +web2py.gluon.contrib.pymysql.apilevel web2py.gluon.contrib.pymysql-module.html#apilevel +web2py.gluon.contrib.pymysql.constants web2py.gluon.contrib.pymysql.constants-module.html +web2py.gluon.contrib.pymysql.constants.FIELD_TYPE web2py.gluon.contrib.pymysql.constants.FIELD_TYPE-module.html +web2py.gluon.contrib.pymysql.constants.FIELD_TYPE.LONGLONG web2py.gluon.contrib.pymysql.constants.FIELD_TYPE-module.html#LONGLONG +web2py.gluon.contrib.pymysql.constants.FIELD_TYPE.SHORT web2py.gluon.contrib.pymysql.constants.FIELD_TYPE-module.html#SHORT +web2py.gluon.contrib.pymysql.constants.FIELD_TYPE.TINY_BLOB web2py.gluon.contrib.pymysql.constants.FIELD_TYPE-module.html#TINY_BLOB +web2py.gluon.contrib.pymysql.constants.FIELD_TYPE.CHAR web2py.gluon.contrib.pymysql.constants.FIELD_TYPE-module.html#CHAR +web2py.gluon.contrib.pymysql.constants.FIELD_TYPE.TINY web2py.gluon.contrib.pymysql.constants.FIELD_TYPE-module.html#TINY +web2py.gluon.contrib.pymysql.constants.FIELD_TYPE.DATE web2py.gluon.contrib.pymysql.constants.FIELD_TYPE-module.html#DATE +web2py.gluon.contrib.pymysql.constants.FIELD_TYPE.NEWDATE web2py.gluon.contrib.pymysql.constants.FIELD_TYPE-module.html#NEWDATE +web2py.gluon.contrib.pymysql.constants.FIELD_TYPE.NULL web2py.gluon.contrib.pymysql.constants.FIELD_TYPE-module.html#NULL +web2py.gluon.contrib.pymysql.constants.FIELD_TYPE.TIMESTAMP web2py.gluon.contrib.pymysql.constants.FIELD_TYPE-module.html#TIMESTAMP +web2py.gluon.contrib.pymysql.constants.FIELD_TYPE.SET web2py.gluon.contrib.pymysql.constants.FIELD_TYPE-module.html#SET +web2py.gluon.contrib.pymysql.constants.FIELD_TYPE.VARCHAR web2py.gluon.contrib.pymysql.constants.FIELD_TYPE-module.html#VARCHAR +web2py.gluon.contrib.pymysql.constants.FIELD_TYPE.INTERVAL web2py.gluon.contrib.pymysql.constants.FIELD_TYPE-module.html#INTERVAL +web2py.gluon.contrib.pymysql.constants.FIELD_TYPE.LONG web2py.gluon.contrib.pymysql.constants.FIELD_TYPE-module.html#LONG +web2py.gluon.contrib.pymysql.constants.FIELD_TYPE.NEWDECIMAL web2py.gluon.contrib.pymysql.constants.FIELD_TYPE-module.html#NEWDECIMAL +web2py.gluon.contrib.pymysql.constants.FIELD_TYPE.BIT web2py.gluon.contrib.pymysql.constants.FIELD_TYPE-module.html#BIT +web2py.gluon.contrib.pymysql.constants.FIELD_TYPE.MEDIUM_BLOB web2py.gluon.contrib.pymysql.constants.FIELD_TYPE-module.html#MEDIUM_BLOB +web2py.gluon.contrib.pymysql.constants.FIELD_TYPE.VAR_STRING web2py.gluon.contrib.pymysql.constants.FIELD_TYPE-module.html#VAR_STRING +web2py.gluon.contrib.pymysql.constants.FIELD_TYPE.STRING web2py.gluon.contrib.pymysql.constants.FIELD_TYPE-module.html#STRING +web2py.gluon.contrib.pymysql.constants.FIELD_TYPE.DECIMAL web2py.gluon.contrib.pymysql.constants.FIELD_TYPE-module.html#DECIMAL +web2py.gluon.contrib.pymysql.constants.FIELD_TYPE.ENUM web2py.gluon.contrib.pymysql.constants.FIELD_TYPE-module.html#ENUM +web2py.gluon.contrib.pymysql.constants.FIELD_TYPE.LONG_BLOB web2py.gluon.contrib.pymysql.constants.FIELD_TYPE-module.html#LONG_BLOB +web2py.gluon.contrib.pymysql.constants.FIELD_TYPE.DATETIME web2py.gluon.contrib.pymysql.constants.FIELD_TYPE-module.html#DATETIME +web2py.gluon.contrib.pymysql.constants.FIELD_TYPE.YEAR web2py.gluon.contrib.pymysql.constants.FIELD_TYPE-module.html#YEAR +web2py.gluon.contrib.pymysql.constants.FIELD_TYPE.GEOMETRY web2py.gluon.contrib.pymysql.constants.FIELD_TYPE-module.html#GEOMETRY +web2py.gluon.contrib.pymysql.constants.FIELD_TYPE.DOUBLE web2py.gluon.contrib.pymysql.constants.FIELD_TYPE-module.html#DOUBLE +web2py.gluon.contrib.pymysql.constants.FIELD_TYPE.FLOAT web2py.gluon.contrib.pymysql.constants.FIELD_TYPE-module.html#FLOAT +web2py.gluon.contrib.pymysql.constants.FIELD_TYPE.INT24 web2py.gluon.contrib.pymysql.constants.FIELD_TYPE-module.html#INT24 +web2py.gluon.contrib.pymysql.constants.FIELD_TYPE.BLOB web2py.gluon.contrib.pymysql.constants.FIELD_TYPE-module.html#BLOB +web2py.gluon.contrib.pymysql.constants.FIELD_TYPE.TIME web2py.gluon.contrib.pymysql.constants.FIELD_TYPE-module.html#TIME +web2py.gluon.contrib.pymysql.converters web2py.gluon.contrib.pymysql.converters-module.html +web2py.gluon.contrib.pymysql.converters.conversions web2py.gluon.contrib.pymysql.converters-module.html#conversions +web2py.gluon.contrib.pymysql.converters.convert_bit web2py.gluon.contrib.pymysql.converters-module.html#convert_bit +web2py.gluon.contrib.pymysql.converters.escape_int web2py.gluon.contrib.pymysql.converters-module.html#escape_int +web2py.gluon.contrib.pymysql.converters.decoders web2py.gluon.contrib.pymysql.converters-module.html#decoders +web2py.gluon.contrib.pymysql.converters.escape_object web2py.gluon.contrib.pymysql.converters-module.html#escape_object +web2py.gluon.contrib.pymysql.converters.convert_timedelta web2py.gluon.contrib.pymysql.converters-module.html#convert_timedelta +web2py.gluon.contrib.pymysql.converters.escape_item web2py.gluon.contrib.pymysql.converters-module.html#escape_item +web2py.gluon.contrib.pymysql.converters.escape_unicode web2py.gluon.contrib.pymysql.converters-module.html#escape_unicode +web2py.gluon.contrib.pymysql.converters.ESCAPE_MAP web2py.gluon.contrib.pymysql.converters-module.html#ESCAPE_MAP +web2py.gluon.contrib.pymysql.converters.escape_dict web2py.gluon.contrib.pymysql.converters-module.html#escape_dict +web2py.gluon.contrib.pymysql.converters.escape_None web2py.gluon.contrib.pymysql.converters-module.html#escape_None +web2py.gluon.contrib.pymysql.converters.escape_string web2py.gluon.contrib.pymysql.converters-module.html#escape_string +web2py.gluon.contrib.pymysql.converters.convert_characters web2py.gluon.contrib.pymysql.converters-module.html#convert_characters +web2py.gluon.contrib.pymysql.converters.convert_date web2py.gluon.contrib.pymysql.converters-module.html#convert_date +web2py.gluon.contrib.pymysql.converters.encoders web2py.gluon.contrib.pymysql.converters-module.html#encoders +web2py.gluon.contrib.pymysql.converters.escape_long web2py.gluon.contrib.pymysql.converters-module.html#escape_long +web2py.gluon.contrib.pymysql.converters.convert_float web2py.gluon.contrib.pymysql.converters-module.html#convert_float +web2py.gluon.contrib.pymysql.converters.escape_timedelta web2py.gluon.contrib.pymysql.converters-module.html#escape_timedelta +web2py.gluon.contrib.pymysql.converters.escape_datetime web2py.gluon.contrib.pymysql.converters-module.html#escape_datetime +web2py.gluon.contrib.pymysql.converters.escape_set web2py.gluon.contrib.pymysql.converters-module.html#escape_set +web2py.gluon.contrib.pymysql.converters.escape_struct_time web2py.gluon.contrib.pymysql.converters-module.html#escape_struct_time +web2py.gluon.contrib.pymysql.converters.escape_bool web2py.gluon.contrib.pymysql.converters-module.html#escape_bool +web2py.gluon.contrib.pymysql.converters.escape_date web2py.gluon.contrib.pymysql.converters-module.html#escape_date +web2py.gluon.contrib.pymysql.converters.escape_sequence web2py.gluon.contrib.pymysql.converters-module.html#escape_sequence +web2py.gluon.contrib.pymysql.converters.ESCAPE_REGEX web2py.gluon.contrib.pymysql.converters-module.html#ESCAPE_REGEX +web2py.gluon.contrib.pymysql.converters.convert_time web2py.gluon.contrib.pymysql.converters-module.html#convert_time +web2py.gluon.contrib.pymysql.converters.convert_mysql_timestamp web2py.gluon.contrib.pymysql.converters-module.html#convert_mysql_timestamp +web2py.gluon.contrib.pymysql.converters.convert_long web2py.gluon.contrib.pymysql.converters-module.html#convert_long +web2py.gluon.contrib.pymysql.converters.convert_set web2py.gluon.contrib.pymysql.converters-module.html#convert_set +web2py.gluon.contrib.pymysql.converters.convert_datetime web2py.gluon.contrib.pymysql.converters-module.html#convert_datetime +web2py.gluon.contrib.pymysql.converters.escape_float web2py.gluon.contrib.pymysql.converters-module.html#escape_float +web2py.gluon.contrib.pymysql.converters.convert_int web2py.gluon.contrib.pymysql.converters-module.html#convert_int +web2py.gluon.contrib.pymysql.converters.convert_decimal web2py.gluon.contrib.pymysql.converters-module.html#convert_decimal +web2py.gluon.contrib.pymysql.converters.escape_time web2py.gluon.contrib.pymysql.converters-module.html#escape_time +web2py.gluon.contrib.pymysql.converters.escape_decimal web2py.gluon.contrib.pymysql.converters-module.html#escape_decimal +web2py.gluon.custom_import web2py.gluon.custom_import-module.html +web2py.gluon.custom_import.custom_import_install web2py.gluon.custom_import-module.html#custom_import_install +web2py.gluon.custom_import._web2py_path web2py.gluon.custom_import-module.html#_web2py_path +web2py.gluon.custom_import.track_changes web2py.gluon.custom_import-module.html#track_changes +web2py.gluon.custom_import._web2py_date_tracker_importer web2py.gluon.custom_import-module.html#_web2py_date_tracker_importer +web2py.gluon.custom_import.is_tracking_changes web2py.gluon.custom_import-module.html#is_tracking_changes +web2py.gluon.custom_import._web2py_importer web2py.gluon.custom_import-module.html#_web2py_importer +web2py.gluon.custom_import._is_tracking_changes web2py.gluon.custom_import-module.html#_is_tracking_changes +web2py.gluon.dal web2py.gluon.dal-module.html +web2py.gluon.dal.regex_quotes web2py.gluon.dal-module.html#regex_quotes +web2py.gluon.dal.bar_decode_integer web2py.gluon.dal-module.html#bar_decode_integer +web2py.gluon.dal.have_serializers web2py.gluon.dal-module.html#have_serializers +web2py.gluon.dal.Rows_unpickler web2py.gluon.dal-module.html#Rows_unpickler +web2py.gluon.dal.Row_unpickler web2py.gluon.dal-module.html#Row_unpickler +web2py.gluon.dal.table_field web2py.gluon.dal-module.html#table_field +web2py.gluon.dal.logger web2py.gluon.dal-module.html#logger +web2py.gluon.dal.bar_escape web2py.gluon.dal-module.html#bar_escape +web2py.gluon.dal.Rows_pickler web2py.gluon.dal-module.html#Rows_pickler +web2py.gluon.dal.Row_pickler web2py.gluon.dal-module.html#Row_pickler +web2py.gluon.dal.bar_encode web2py.gluon.dal-module.html#bar_encode +web2py.gluon.dal.bar_decode_string web2py.gluon.dal-module.html#bar_decode_string +web2py.gluon.dal.test_all web2py.gluon.dal-module.html#test_all +web2py.gluon.dal.Reference_pickler web2py.gluon.dal-module.html#Reference_pickler +web2py.gluon.dal.regex_python_keywords web2py.gluon.dal-module.html#regex_python_keywords +web2py.gluon.dal.regex_dbname web2py.gluon.dal-module.html#regex_dbname +web2py.gluon.dal.have_validators web2py.gluon.dal-module.html#have_validators +web2py.gluon.dal.is_jdbc web2py.gluon.dal-module.html#is_jdbc +web2py.gluon.dal.thread web2py.gluon.dal-module.html#thread +web2py.gluon.dal.xorify web2py.gluon.dal-module.html#xorify +web2py.gluon.dal.DEFAULT web2py.gluon.dal-module.html#DEFAULT +web2py.gluon.dal.uuid2int web2py.gluon.dal-module.html#uuid2int +web2py.gluon.dal.regex_cleanup_fn web2py.gluon.dal-module.html#regex_cleanup_fn +web2py.gluon.dal.MAXCHARLENGTH web2py.gluon.dal-module.html#MAXCHARLENGTH +web2py.gluon.dal.update_record web2py.gluon.dal-module.html#update_record +web2py.gluon.dal.ADAPTERS web2py.gluon.dal-module.html#ADAPTERS +web2py.gluon.dal.regex_content web2py.gluon.dal-module.html#regex_content +web2py.gluon.dal.Reference_unpickler web2py.gluon.dal-module.html#Reference_unpickler +web2py.gluon.dal.INGRES_SEQNAME web2py.gluon.dal-module.html#INGRES_SEQNAME +web2py.gluon.dal.CALLABLETYPES web2py.gluon.dal-module.html#CALLABLETYPES +web2py.gluon.dal.have_portalocker web2py.gluon.dal-module.html#have_portalocker +web2py.gluon.dal.string_unpack web2py.gluon.dal-module.html#string_unpack +web2py.gluon.dal.sqlhtml_validators web2py.gluon.dal-module.html#sqlhtml_validators +web2py.gluon.dal.drivers web2py.gluon.dal-module.html#drivers +web2py.gluon.dal.cleanup web2py.gluon.dal-module.html#cleanup +web2py.gluon.dal.INFINITY web2py.gluon.dal-module.html#INFINITY +web2py.gluon.dal.sql_locker web2py.gluon.dal-module.html#sql_locker +web2py.gluon.dal.int2uuid web2py.gluon.dal-module.html#int2uuid +web2py.gluon.debug web2py.gluon.debug-module.html +web2py.gluon.debug.stop_trace web2py.gluon.debug-module.html#stop_trace +web2py.gluon.debug.debugger web2py.gluon.debug-module.html#debugger +web2py.gluon.debug.pipe_in web2py.gluon.debug-module.html#pipe_in +web2py.gluon.debug.set_trace web2py.gluon.debug-module.html#set_trace +web2py.gluon.debug.communicate web2py.gluon.debug-module.html#communicate +web2py.gluon.debug.pipe_out web2py.gluon.debug-module.html#pipe_out +web2py.gluon.debug.logger web2py.gluon.debug-module.html#logger +web2py.gluon.decoder web2py.gluon.decoder-module.html +web2py.gluon.decoder.autoDetectXMLEncoding web2py.gluon.decoder-module.html#autoDetectXMLEncoding +web2py.gluon.decoder.decoder web2py.gluon.decoder-module.html#decoder +web2py.gluon.decoder.autodetect_dict web2py.gluon.decoder-module.html#autodetect_dict +web2py.gluon.fileutils web2py.gluon.fileutils-module.html +web2py.gluon.fileutils.tar web2py.gluon.fileutils-module.html#tar +web2py.gluon.fileutils._extractall web2py.gluon.fileutils-module.html#_extractall +web2py.gluon.fileutils.listdir web2py.gluon.fileutils-module.html#listdir +web2py.gluon.fileutils.w2p_unpack web2py.gluon.fileutils-module.html#w2p_unpack +web2py.gluon.fileutils.write_file web2py.gluon.fileutils-module.html#write_file +web2py.gluon.fileutils.read_file web2py.gluon.fileutils-module.html#read_file +web2py.gluon.fileutils.w2p_pack_plugin web2py.gluon.fileutils-module.html#w2p_pack_plugin +web2py.gluon.fileutils.w2p_pack web2py.gluon.fileutils-module.html#w2p_pack +web2py.gluon.fileutils.recursive_unlink web2py.gluon.fileutils-module.html#recursive_unlink +web2py.gluon.fileutils.get_session web2py.gluon.fileutils-module.html#get_session +web2py.gluon.fileutils.w2p_unpack_plugin web2py.gluon.fileutils-module.html#w2p_unpack_plugin +web2py.gluon.fileutils.abspath web2py.gluon.fileutils-module.html#abspath +web2py.gluon.fileutils.untar web2py.gluon.fileutils-module.html#untar +web2py.gluon.fileutils.cleanpath web2py.gluon.fileutils-module.html#cleanpath +web2py.gluon.fileutils.make_fake_file_like_object web2py.gluon.fileutils-module.html#make_fake_file_like_object +web2py.gluon.fileutils.copystream web2py.gluon.fileutils-module.html#copystream +web2py.gluon.fileutils.fix_newlines web2py.gluon.fileutils-module.html#fix_newlines +web2py.gluon.fileutils.mktree web2py.gluon.fileutils-module.html#mktree +web2py.gluon.fileutils.readlines_file web2py.gluon.fileutils-module.html#readlines_file +web2py.gluon.fileutils.up web2py.gluon.fileutils-module.html#up +web2py.gluon.fileutils.tar_compiled web2py.gluon.fileutils-module.html#tar_compiled +web2py.gluon.fileutils.check_credentials web2py.gluon.fileutils-module.html#check_credentials +web2py.gluon.globals web2py.gluon.globals-module.html +web2py.gluon.globals.custom_json web2py.gluon.serializers-module.html#custom_json +web2py.gluon.globals.web2py_uuid web2py.gluon.utils-module.html#web2py_uuid +web2py.gluon.globals.streamer web2py.gluon.streamer-module.html#streamer +web2py.gluon.globals.stream_file_or_304_or_206 web2py.gluon.streamer-module.html#stream_file_or_304_or_206 +web2py.gluon.globals.contenttype web2py.gluon.contenttype-module.html#contenttype +web2py.gluon.globals.current web2py.gluon.globals-module.html#current +web2py.gluon.globals.json web2py.gluon.serializers-module.html#json +web2py.gluon.globals.handler web2py.gluon.xmlrpc-module.html#handler +web2py.gluon.globals.regex_session_id web2py.gluon.globals-module.html#regex_session_id +web2py.gluon.globals.up web2py.gluon.fileutils-module.html#up +web2py.gluon.globals.xmlescape web2py.gluon.html-module.html#xmlescape +web2py.gluon.highlight web2py.gluon.highlight-module.html +web2py.gluon.highlight.highlight web2py.gluon.highlight-module.html#highlight +web2py.gluon.html web2py.gluon.html-module.html +web2py.gluon.html.TAG web2py.gluon.html-module.html#TAG +web2py.gluon.html.URL web2py.gluon.html-module.html#URL +web2py.gluon.html.ON web2py.gluon.html-module.html#ON +web2py.gluon.html.TAG_unpickler web2py.gluon.html-module.html#TAG_unpickler +web2py.gluon.html.XML_pickle web2py.gluon.html-module.html#XML_pickle +web2py.gluon.html.hmac_hash web2py.gluon.utils-module.html#hmac_hash +web2py.gluon.html.markmin_serializer web2py.gluon.html-module.html#markmin_serializer +web2py.gluon.html.embed64 web2py.gluon.html-module.html#embed64 +web2py.gluon.html.xmlescape web2py.gluon.html-module.html#xmlescape +web2py.gluon.html.verifyURL web2py.gluon.html-module.html#verifyURL +web2py.gluon.html.web2py_uuid web2py.gluon.utils-module.html#web2py_uuid +web2py.gluon.html.regex_crlf web2py.gluon.html-module.html#regex_crlf +web2py.gluon.html.join web2py.gluon.html-module.html#join +web2py.gluon.html.highlight web2py.gluon.highlight-module.html#highlight +web2py.gluon.html.TAG_pickler web2py.gluon.html-module.html#TAG_pickler +web2py.gluon.html.XML_unpickle web2py.gluon.html-module.html#XML_unpickle +web2py.gluon.html.test web2py.gluon.html-module.html#test +web2py.gluon.html.markdown_serializer web2py.gluon.html-module.html#markdown_serializer +web2py.gluon.http web2py.gluon.http-module.html +web2py.gluon.http.redirect web2py.gluon.http-module.html#redirect +web2py.gluon.http.defined_status web2py.gluon.http-module.html#defined_status +web2py.gluon.import_all web2py.gluon.import_all-module.html +web2py.gluon.import_all.dirs web2py.gluon.import_all-module.html#dirs +web2py.gluon.import_all.files web2py.gluon.import_all-module.html#files +web2py.gluon.import_all.alert_dependency web2py.gluon.import_all-module.html#alert_dependency +web2py.gluon.import_all.base_modules web2py.gluon.import_all-module.html#base_modules +web2py.gluon.import_all.candidate web2py.gluon.import_all-module.html#candidate +web2py.gluon.import_all.python_version web2py.gluon.import_all-module.html#python_version +web2py.gluon.import_all.root web2py.gluon.import_all-module.html#root +web2py.gluon.import_all.contributed_modules web2py.gluon.import_all-module.html#contributed_modules +web2py.gluon.import_all.module web2py.gluon.import_all-module.html#module +web2py.gluon.import_all.py27_deprecated web2py.gluon.import_all-module.html#py27_deprecated +web2py.gluon.import_all.py26_deprecated web2py.gluon.import_all-module.html#py26_deprecated +web2py.gluon.import_all.name web2py.gluon.import_all-module.html#name +web2py.gluon.languages web2py.gluon.languages-module.html +web2py.gluon.languages.is_gae web2py.gluon.languages-module.html#is_gae +web2py.gluon.languages.regex_translate web2py.gluon.languages-module.html#regex_translate +web2py.gluon.languages.utf8_repr web2py.gluon.languages-module.html#utf8_repr +web2py.gluon.languages.PY_STRING_LITERAL_RE web2py.gluon.languages-module.html#PY_STRING_LITERAL_RE +web2py.gluon.languages.getcfs web2py.gluon.cfs-module.html#getcfs +web2py.gluon.languages.write_dict web2py.gluon.languages-module.html#write_dict +web2py.gluon.languages.listdir web2py.gluon.fileutils-module.html#listdir +web2py.gluon.languages.lazyT_pickle web2py.gluon.languages-module.html#lazyT_pickle +web2py.gluon.languages.lazyT_unpickle web2py.gluon.languages-module.html#lazyT_unpickle +web2py.gluon.languages.findT web2py.gluon.languages-module.html#findT +web2py.gluon.languages.read_dict_aux web2py.gluon.languages-module.html#read_dict_aux +web2py.gluon.languages.read_dict web2py.gluon.languages-module.html#read_dict +web2py.gluon.languages.regex_language web2py.gluon.languages-module.html#regex_language +web2py.gluon.languages.update_all_languages web2py.gluon.languages-module.html#update_all_languages +web2py.gluon.main web2py.gluon.main-module.html +web2py.gluon.main.build_environment web2py.gluon.compileapp-module.html#build_environment +web2py.gluon.main.parse_get_post_vars web2py.gluon.main-module.html#parse_get_post_vars +web2py.gluon.main.middleware_aux web2py.gluon.main-module.html#middleware_aux +web2py.gluon.main.write_file web2py.gluon.fileutils-module.html#write_file +web2py.gluon.main.get_client web2py.gluon.main-module.html#get_client +web2py.gluon.main.run_view_in web2py.gluon.compileapp-module.html#run_view_in +web2py.gluon.main.redirect web2py.gluon.http-module.html#redirect +web2py.gluon.main.contenttype web2py.gluon.contenttype-module.html#contenttype +web2py.gluon.main.logpath web2py.gluon.main-module.html#logpath +web2py.gluon.main.web2py_path web2py.gluon.main-module.html#web2py_path +web2py.gluon.main.logger web2py.gluon.main-module.html#logger +web2py.gluon.main.start_response_aux web2py.gluon.main-module.html#start_response_aux +web2py.gluon.main.create_missing_folders web2py.gluon.admin-module.html#create_missing_folders +web2py.gluon.main.version_info web2py.gluon.main-module.html#version_info +web2py.gluon.main.Url web2py.gluon.html-module.html#URL +web2py.gluon.main.regex_client web2py.gluon.main-module.html#regex_client +web2py.gluon.main.abspath web2py.gluon.fileutils-module.html#abspath +web2py.gluon.main.run_models_in web2py.gluon.compileapp-module.html#run_models_in +web2py.gluon.main.wsgibase web2py.gluon.main-module.html#wsgibase +web2py.gluon.main.copystream_progress web2py.gluon.main-module.html#copystream_progress +web2py.gluon.main.web2py_version web2py.gluon.main-module.html#web2py_version +web2py.gluon.main.run_controller_in web2py.gluon.compileapp-module.html#run_controller_in +web2py.gluon.main.custom_import_install web2py.gluon.custom_import-module.html#custom_import_install +web2py.gluon.main.appfactory web2py.gluon.main-module.html#appfactory +web2py.gluon.main.add_path_first web2py.gluon.admin-module.html#add_path_first +web2py.gluon.main.serve_controller web2py.gluon.main-module.html#serve_controller +web2py.gluon.main.copystream web2py.gluon.fileutils-module.html#copystream +web2py.gluon.main.create_missing_app_folders web2py.gluon.admin-module.html#create_missing_app_folders +web2py.gluon.main.environ_aux web2py.gluon.main-module.html#environ_aux +web2py.gluon.main.save_password web2py.gluon.main-module.html#save_password +web2py.gluon.main.requests web2py.gluon.main-module.html#requests +web2py.gluon.myregex web2py.gluon.myregex-module.html +web2py.gluon.myregex.regex_expose web2py.gluon.myregex-module.html#regex_expose +web2py.gluon.myregex.regex_tables web2py.gluon.myregex-module.html#regex_tables +web2py.gluon.myregex.regex_extend web2py.gluon.myregex-module.html#regex_extend +web2py.gluon.myregex.regex_include web2py.gluon.myregex-module.html#regex_include +web2py.gluon.newcron web2py.gluon.newcron-module.html +web2py.gluon.newcron._cron_stopping web2py.gluon.newcron-module.html#_cron_stopping +web2py.gluon.newcron.stopcron web2py.gluon.newcron-module.html#stopcron +web2py.gluon.newcron.parsecronline web2py.gluon.newcron-module.html#parsecronline +web2py.gluon.newcron.crondance web2py.gluon.newcron-module.html#crondance +web2py.gluon.newcron.logger web2py.gluon.newcron-module.html#logger +web2py.gluon.newcron.rangetolist web2py.gluon.newcron-module.html#rangetolist +web2py.gluon.portalocker web2py.gluon.portalocker-module.html +web2py.gluon.portalocker.__overlapped web2py.gluon.portalocker-module.html#__overlapped +web2py.gluon.portalocker.lock web2py.gluon.portalocker-module.html#lock +web2py.gluon.portalocker.os_locking web2py.gluon.portalocker-module.html#os_locking +web2py.gluon.portalocker.unlock web2py.gluon.portalocker-module.html#unlock +web2py.gluon.portalocker.LOCK_NB web2py.gluon.portalocker-module.html#LOCK_NB +web2py.gluon.portalocker.LOCK_EX web2py.gluon.portalocker-module.html#LOCK_EX +web2py.gluon.portalocker.logger web2py.gluon.portalocker-module.html#logger +web2py.gluon.portalocker.LOCK_SH web2py.gluon.portalocker-module.html#LOCK_SH +web2py.gluon.reserved_sql_keywords web2py.gluon.reserved_sql_keywords-module.html +web2py.gluon.reserved_sql_keywords.POSTGRESQL_NONRESERVED web2py.gluon.reserved_sql_keywords-module.html#POSTGRESQL_NONRESERVED +web2py.gluon.reserved_sql_keywords.SQLITE web2py.gluon.reserved_sql_keywords-module.html#SQLITE +web2py.gluon.reserved_sql_keywords.POSTGRESQL web2py.gluon.reserved_sql_keywords-module.html#POSTGRESQL +web2py.gluon.reserved_sql_keywords.FIREBIRD web2py.gluon.reserved_sql_keywords-module.html#FIREBIRD +web2py.gluon.reserved_sql_keywords.MSSQL web2py.gluon.reserved_sql_keywords-module.html#MSSQL +web2py.gluon.reserved_sql_keywords.FIREBIRD_NONRESERVED web2py.gluon.reserved_sql_keywords-module.html#FIREBIRD_NONRESERVED +web2py.gluon.reserved_sql_keywords.JDBCPOSTGRESQL web2py.gluon.reserved_sql_keywords-module.html#JDBCPOSTGRESQL +web2py.gluon.reserved_sql_keywords.__author__ web2py.gluon.reserved_sql_keywords-module.html#__author__ +web2py.gluon.reserved_sql_keywords.INFORMIX web2py.gluon.reserved_sql_keywords-module.html#INFORMIX +web2py.gluon.reserved_sql_keywords.COMMON web2py.gluon.reserved_sql_keywords-module.html#COMMON +web2py.gluon.reserved_sql_keywords.MYSQL web2py.gluon.reserved_sql_keywords-module.html#MYSQL +web2py.gluon.reserved_sql_keywords.ORACLE web2py.gluon.reserved_sql_keywords-module.html#ORACLE +web2py.gluon.reserved_sql_keywords.INGRES web2py.gluon.reserved_sql_keywords-module.html#INGRES +web2py.gluon.reserved_sql_keywords.DB2 web2py.gluon.reserved_sql_keywords-module.html#DB2 +web2py.gluon.reserved_sql_keywords.ADAPTERS web2py.gluon.reserved_sql_keywords-module.html#ADAPTERS +web2py.gluon.reserved_sql_keywords.JDBCSQLITE web2py.gluon.reserved_sql_keywords-module.html#JDBCSQLITE +web2py.gluon.restricted web2py.gluon.restricted-module.html +web2py.gluon.restricted.restricted web2py.gluon.restricted-module.html#restricted +web2py.gluon.restricted.web2py_uuid web2py.gluon.utils-module.html#web2py_uuid +web2py.gluon.restricted.snapshot web2py.gluon.restricted-module.html#snapshot +web2py.gluon.restricted.logger web2py.gluon.restricted-module.html#logger +web2py.gluon.restricted.compile2 web2py.gluon.restricted-module.html#compile2 +web2py.gluon.rewrite web2py.gluon.rewrite-module.html +web2py.gluon.rewrite.load web2py.gluon.rewrite-module.html#load +web2py.gluon.rewrite.ROUTER_KEYS web2py.gluon.rewrite-module.html#ROUTER_KEYS +web2py.gluon.rewrite.params_apps web2py.gluon.rewrite-module.html#params_apps +web2py.gluon.rewrite.load_routers web2py.gluon.rewrite-module.html#load_routers +web2py.gluon.rewrite.routers web2py.gluon.rewrite-module.html#routers +web2py.gluon.rewrite.regex_url_in web2py.gluon.rewrite-module.html#regex_url_in +web2py.gluon.rewrite.get_effective_router web2py.gluon.rewrite-module.html#get_effective_router +web2py.gluon.rewrite.regex_anything web2py.gluon.rewrite-module.html#regex_anything +web2py.gluon.rewrite.read_file web2py.gluon.fileutils-module.html#read_file +web2py.gluon.rewrite.url_in web2py.gluon.rewrite-module.html#url_in +web2py.gluon.rewrite.map_url_in web2py.gluon.rewrite-module.html#map_url_in +web2py.gluon.rewrite.map_url_out web2py.gluon.rewrite-module.html#map_url_out +web2py.gluon.rewrite.regex_filter_in web2py.gluon.rewrite-module.html#regex_filter_in +web2py.gluon.rewrite.regex_select web2py.gluon.rewrite-module.html#regex_select +web2py.gluon.rewrite.regex_at web2py.gluon.rewrite-module.html#regex_at +web2py.gluon.rewrite.regex_url web2py.gluon.rewrite-module.html#regex_url +web2py.gluon.rewrite.filter_err web2py.gluon.rewrite-module.html#filter_err +web2py.gluon.rewrite.regex_args web2py.gluon.rewrite-module.html#regex_args +web2py.gluon.rewrite.regex_uri web2py.gluon.rewrite-module.html#regex_uri +web2py.gluon.rewrite.params web2py.gluon.rewrite-module.html#params +web2py.gluon.rewrite.logger web2py.gluon.rewrite-module.html#logger +web2py.gluon.rewrite.regex_filter_out web2py.gluon.rewrite-module.html#regex_filter_out +web2py.gluon.rewrite.try_redirect_on_error web2py.gluon.rewrite-module.html#try_redirect_on_error +web2py.gluon.rewrite.ROUTER_BASE_KEYS web2py.gluon.rewrite-module.html#ROUTER_BASE_KEYS +web2py.gluon.rewrite.abspath web2py.gluon.fileutils-module.html#abspath +web2py.gluon.rewrite.url_out web2py.gluon.rewrite-module.html#url_out +web2py.gluon.rewrite.try_rewrite_on_error web2py.gluon.rewrite-module.html#try_rewrite_on_error +web2py.gluon.rewrite._params_default web2py.gluon.rewrite-module.html#_params_default +web2py.gluon.rewrite.filter_url web2py.gluon.rewrite-module.html#filter_url +web2py.gluon.rewrite.compile_regex web2py.gluon.rewrite-module.html#compile_regex +web2py.gluon.rewrite.thread web2py.gluon.rewrite-module.html#thread +web2py.gluon.rewrite.regex_static web2py.gluon.rewrite-module.html#regex_static +web2py.gluon.rewrite._router_default web2py.gluon.rewrite-module.html#_router_default +web2py.gluon.rewrite.regex_space web2py.gluon.rewrite-module.html#regex_space +web2py.gluon.rocket web2py.gluon.rocket-module.html +web2py.gluon.rocket.BUF_SIZE web2py.gluon.rocket-module.html#BUF_SIZE +web2py.gluon.rocket.demo web2py.gluon.rocket-module.html#demo +web2py.gluon.rocket.PY3K web2py.gluon.rocket-module.html#PY3K +web2py.gluon.rocket.SERVER_SOFTWARE web2py.gluon.rocket-module.html#SERVER_SOFTWARE +web2py.gluon.rocket.HTTP_SERVER_SOFTWARE web2py.gluon.rocket-module.html#HTTP_SERVER_SOFTWARE +web2py.gluon.rocket.LOG_LINE web2py.gluon.rocket-module.html#LOG_LINE +web2py.gluon.rocket.BASE_ENV web2py.gluon.rocket-module.html#BASE_ENV +web2py.gluon.rocket._formatparam web2py.gluon.rocket-module.html#_formatparam +web2py.gluon.rocket.HTTP_METHODS web2py.gluon.rocket-module.html#HTTP_METHODS +web2py.gluon.rocket.log web2py.gluon.rocket-module.html#log +web2py.gluon.rocket.SERVER_NAME web2py.gluon.rocket-module.html#SERVER_NAME +web2py.gluon.rocket.NEWLINE web2py.gluon.rocket-module.html#NEWLINE +web2py.gluon.rocket.HEADER_RESPONSE web2py.gluon.rocket-module.html#HEADER_RESPONSE +web2py.gluon.rocket.DEFAULTS web2py.gluon.rocket-module.html#DEFAULTS +web2py.gluon.rocket.b web2py.gluon.rocket-module.html#b +web2py.gluon.rocket.THREAD_STOP_CHECK_INTERVAL web2py.gluon.rocket-module.html#THREAD_STOP_CHECK_INTERVAL +web2py.gluon.rocket.re_REQUEST_LINE web2py.gluon.rocket-module.html#re_REQUEST_LINE +web2py.gluon.rocket.re_SLASH web2py.gluon.rocket-module.html#re_SLASH +web2py.gluon.rocket.VERSION web2py.gluon.rocket-module.html#VERSION +web2py.gluon.rocket.DEFAULT_LISTEN_QUEUE_SIZE web2py.gluon.rocket-module.html#DEFAULT_LISTEN_QUEUE_SIZE +web2py.gluon.rocket.DEFAULT_MIN_THREADS web2py.gluon.rocket-module.html#DEFAULT_MIN_THREADS +web2py.gluon.rocket.RESPONSE web2py.gluon.rocket-module.html#RESPONSE +web2py.gluon.rocket.get_method web2py.gluon.rocket-module.html#get_method +web2py.gluon.rocket.CherryPyWSGIServer web2py.gluon.rocket-module.html#CherryPyWSGIServer +web2py.gluon.rocket.DEFAULT_MAX_THREADS web2py.gluon.rocket-module.html#DEFAULT_MAX_THREADS +web2py.gluon.rocket.SOCKET_TIMEOUT web2py.gluon.rocket-module.html#SOCKET_TIMEOUT +web2py.gluon.rocket.IS_JYTHON web2py.gluon.rocket-module.html#IS_JYTHON +web2py.gluon.rocket.IGNORE_ERRORS_ON_CLOSE web2py.gluon.rocket-module.html#IGNORE_ERRORS_ON_CLOSE +web2py.gluon.rocket.u web2py.gluon.rocket-module.html#u +web2py.gluon.rocket.has_ssl web2py.gluon.rocket-module.html#has_ssl +web2py.gluon.rocket.demo_app web2py.gluon.rocket-module.html#demo_app +web2py.gluon.rocket._tspecials web2py.gluon.rocket-module.html#_tspecials +web2py.gluon.sanitizer web2py.gluon.sanitizer-module.html +web2py.gluon.sanitizer.sanitize web2py.gluon.sanitizer-module.html#sanitize +web2py.gluon.sanitizer.xssescape web2py.gluon.sanitizer-module.html#xssescape +web2py.gluon.serializers web2py.gluon.serializers-module.html +web2py.gluon.serializers.xml web2py.gluon.serializers-module.html#xml +web2py.gluon.serializers.custom_json web2py.gluon.serializers-module.html#custom_json +web2py.gluon.serializers.xml_rec web2py.gluon.serializers-module.html#xml_rec +web2py.gluon.serializers.json web2py.gluon.serializers-module.html#json +web2py.gluon.serializers.xmlescape web2py.gluon.html-module.html#xmlescape +web2py.gluon.serializers.csv web2py.gluon.serializers-module.html#csv +web2py.gluon.serializers.rss web2py.gluon.serializers-module.html#rss +web2py.gluon.settings web2py.gluon.settings-module.html +web2py.gluon.settings.global_settings web2py.gluon.settings-module.html#global_settings +web2py.gluon.settings.settings web2py.gluon.settings-module.html#settings +web2py.gluon.shell web2py.gluon.shell-module.html +web2py.gluon.shell.exec_environment web2py.gluon.shell-module.html#exec_environment +web2py.gluon.shell.build_environment web2py.gluon.compileapp-module.html#build_environment +web2py.gluon.shell.get_usage web2py.gluon.shell-module.html#get_usage +web2py.gluon.shell.w2p_unpack web2py.gluon.fileutils-module.html#w2p_unpack +web2py.gluon.shell.web2py_uuid web2py.gluon.utils-module.html#web2py_uuid +web2py.gluon.shell.exec_pythonrc web2py.gluon.shell-module.html#exec_pythonrc +web2py.gluon.shell.execute_from_command_line web2py.gluon.shell-module.html#execute_from_command_line +web2py.gluon.shell.parse_path_info web2py.gluon.shell-module.html#parse_path_info +web2py.gluon.shell.env web2py.gluon.shell-module.html#env +web2py.gluon.shell.test web2py.gluon.shell-module.html#test +web2py.gluon.shell.logger web2py.gluon.shell-module.html#logger +web2py.gluon.shell.run web2py.gluon.shell-module.html#run +web2py.gluon.shell.read_pyc web2py.gluon.compileapp-module.html#read_pyc +web2py.gluon.shell.die web2py.gluon.shell-module.html#die +web2py.gluon.shell.run_models_in web2py.gluon.compileapp-module.html#run_models_in +web2py.gluon.sql web2py.gluon.sql-module.html +web2py.gluon.sql.drivers web2py.gluon.sql-module.html#drivers +web2py.gluon.sqlhtml web2py.gluon.sqlhtml-module.html +web2py.gluon.sqlhtml.table_field web2py.gluon.sqlhtml-module.html#table_field +web2py.gluon.sqlhtml.safe_int web2py.gluon.sqlhtml-module.html#safe_int +web2py.gluon.sqlhtml.form_factory web2py.gluon.sqlhtml-module.html#form_factory +web2py.gluon.sqlhtml.represent web2py.gluon.sqlhtml-module.html#represent +web2py.gluon.sqlhtml.safe_float web2py.gluon.sqlhtml-module.html#safe_float +web2py.gluon.sqlhtml.Url web2py.gluon.html-module.html#URL +web2py.gluon.sqlhtml.md5_hash web2py.gluon.utils-module.html#md5_hash +web2py.gluon.sqlhtml.widget_class web2py.gluon.sqlhtml-module.html#widget_class +web2py.gluon.storage web2py.gluon.storage-module.html +web2py.gluon.storage.load_storage web2py.gluon.storage-module.html#load_storage +web2py.gluon.storage.save_storage web2py.gluon.storage-module.html#save_storage +web2py.gluon.streamer web2py.gluon.streamer-module.html +web2py.gluon.streamer.contenttype web2py.gluon.contenttype-module.html#contenttype +web2py.gluon.streamer.DEFAULT_CHUNK_SIZE web2py.gluon.streamer-module.html#DEFAULT_CHUNK_SIZE +web2py.gluon.streamer.regex_start_range web2py.gluon.streamer-module.html#regex_start_range +web2py.gluon.streamer.streamer web2py.gluon.streamer-module.html#streamer +web2py.gluon.streamer.regex_stop_range web2py.gluon.streamer-module.html#regex_stop_range +web2py.gluon.streamer.stream_file_or_304_or_206 web2py.gluon.streamer-module.html#stream_file_or_304_or_206 +web2py.gluon.template web2py.gluon.template-module.html +web2py.gluon.template.render web2py.gluon.template-module.html#render +web2py.gluon.template.parse_template web2py.gluon.template-module.html#parse_template +web2py.gluon.template.get_parsed web2py.gluon.template-module.html#get_parsed +web2py.gluon.tools web2py.gluon.tools-module.html +web2py.gluon.tools.validators web2py.gluon.tools-module.html#validators +web2py.gluon.tools.geocode web2py.gluon.tools-module.html#geocode +web2py.gluon.tools.logger web2py.gluon.tools-module.html#logger +web2py.gluon.tools.regex_geocode web2py.gluon.tools-module.html#regex_geocode +web2py.gluon.tools.ON web2py.gluon.tools-module.html#ON +web2py.gluon.tools.read_file web2py.gluon.fileutils-module.html#read_file +web2py.gluon.tools.contenttype web2py.gluon.contenttype-module.html#contenttype +web2py.gluon.tools.prettydate web2py.gluon.tools-module.html#prettydate +web2py.gluon.tools.current web2py.gluon.tools-module.html#current +web2py.gluon.tools.universal_caller web2py.gluon.tools-module.html#universal_caller +web2py.gluon.tools.test_thread_separation web2py.gluon.tools-module.html#test_thread_separation +web2py.gluon.tools.DEFAULT web2py.gluon.tools-module.html#DEFAULT +web2py.gluon.tools.web2py_uuid web2py.gluon.utils-module.html#web2py_uuid +web2py.gluon.tools.TAG web2py.gluon.tools-module.html#TAG +web2py.gluon.tools.completion web2py.gluon.tools-module.html#completion +web2py.gluon.tools.fetch web2py.gluon.tools-module.html#fetch +web2py.gluon.tools.call_or_redirect web2py.gluon.tools-module.html#call_or_redirect +web2py.gluon.tools.addrow web2py.gluon.tools-module.html#addrow +web2py.gluon.tools.callback web2py.gluon.tools-module.html#callback +web2py.gluon.utils web2py.gluon.utils-module.html +web2py.gluon.utils.initialize_urandom web2py.gluon.utils-module.html#initialize_urandom +web2py.gluon.utils.ctokens web2py.gluon.utils-module.html#ctokens +web2py.gluon.utils.hmac_hash web2py.gluon.utils-module.html#hmac_hash +web2py.gluon.utils.get_digest web2py.gluon.utils-module.html#get_digest +web2py.gluon.utils.web2py_uuid web2py.gluon.utils-module.html#web2py_uuid +web2py.gluon.utils.md5_hash web2py.gluon.utils-module.html#md5_hash +web2py.gluon.utils.logger web2py.gluon.utils-module.html#logger +web2py.gluon.utils.simple_hash web2py.gluon.utils-module.html#simple_hash +web2py.gluon.validators web2py.gluon.validators-module.html +web2py.gluon.validators.unicode_to_ascii_url web2py.gluon.validators-module.html#unicode_to_ascii_url +web2py.gluon.validators.unofficial_url_schemes web2py.gluon.validators-module.html#unofficial_url_schemes +web2py.gluon.validators.http_schemes web2py.gluon.validators-module.html#http_schemes +web2py.gluon.validators.regex1 web2py.gluon.validators-module.html#regex1 +web2py.gluon.validators.regex2 web2py.gluon.validators-module.html#regex2 +web2py.gluon.validators.simple_hash web2py.gluon.utils-module.html#simple_hash +web2py.gluon.validators.hmac_hash web2py.gluon.utils-module.html#hmac_hash +web2py.gluon.validators.unicode_to_ascii_authority web2py.gluon.validators-module.html#unicode_to_ascii_authority +web2py.gluon.validators.regex_time web2py.gluon.validators-module.html#regex_time +web2py.gluon.validators.label_split_regex web2py.gluon.validators-module.html#label_split_regex +web2py.gluon.validators.url_split_regex web2py.gluon.validators-module.html#url_split_regex +web2py.gluon.validators.all_url_schemes web2py.gluon.validators-module.html#all_url_schemes +web2py.gluon.validators.official_url_schemes web2py.gluon.validators-module.html#official_url_schemes +web2py.gluon.validators.is_empty web2py.gluon.validators-module.html#is_empty +web2py.gluon.validators.urlify web2py.gluon.validators-module.html#urlify +web2py.gluon.validators.translate web2py.gluon.validators-module.html#translate +web2py.gluon.validators.options_sorter web2py.gluon.validators-module.html#options_sorter +web2py.gluon.validators.escape_unicode web2py.gluon.validators-module.html#escape_unicode +web2py.gluon.validators.official_top_level_domains web2py.gluon.validators-module.html#official_top_level_domains +web2py.gluon.widget web2py.gluon.widget-module.html +web2py.gluon.widget.ProgramAuthor web2py.gluon.widget-module.html#ProgramAuthor +web2py.gluon.widget.try_start_browser web2py.gluon.widget-module.html#try_start_browser +web2py.gluon.widget.read_file web2py.gluon.fileutils-module.html#read_file +web2py.gluon.widget.write_file web2py.gluon.fileutils-module.html#write_file +web2py.gluon.widget.run web2py.gluon.shell-module.html#run +web2py.gluon.widget.console web2py.gluon.widget-module.html#console +web2py.gluon.widget.ProgramVersion web2py.gluon.widget-module.html#ProgramVersion +web2py.gluon.widget.start web2py.gluon.widget-module.html#start +web2py.gluon.widget.w2p_pack web2py.gluon.fileutils-module.html#w2p_pack +web2py.gluon.widget.test web2py.gluon.shell-module.html#test +web2py.gluon.widget.logger web2py.gluon.widget-module.html#logger +web2py.gluon.widget.presentation web2py.gluon.widget-module.html#presentation +web2py.gluon.widget.msg web2py.gluon.widget-module.html#msg +web2py.gluon.widget.start_browser web2py.gluon.widget-module.html#start_browser +web2py.gluon.widget.ProgramName web2py.gluon.widget-module.html#ProgramName +web2py.gluon.widget.ProgramInfo web2py.gluon.widget-module.html#ProgramInfo +web2py.gluon.winservice web2py.gluon.winservice-module.html +web2py.gluon.winservice.web2py_windows_service_handler web2py.gluon.winservice-module.html#web2py_windows_service_handler +web2py.gluon.xmlrpc web2py.gluon.xmlrpc-module.html +web2py.gluon.xmlrpc.handler web2py.gluon.xmlrpc-module.html#handler +datetime.date datetime.date-class.html +datetime.date.__str__ datetime.date-class.html#__str__ +datetime.date.__getattribute__ datetime.date-class.html#__getattribute__ +datetime.date.__radd__ datetime.date-class.html#__radd__ +datetime.date.toordinal datetime.date-class.html#toordinal +datetime.date.__rsub__ datetime.date-class.html#__rsub__ +datetime.date.year datetime.date-class.html#year +datetime.date.__lt__ datetime.date-class.html#__lt__ +datetime.date.isocalendar datetime.date-class.html#isocalendar +datetime.date.__new__ datetime.date-class.html#__new__ +datetime.date.min datetime.date-class.html#min +datetime.date.strftime datetime.date-class.html#strftime +datetime.date.timetuple datetime.date-class.html#timetuple +datetime.date.today datetime.date-class.html#today +datetime.date.__repr__ datetime.date-class.html#__repr__ +datetime.date.isoformat datetime.date-class.html#isoformat +datetime.date.__ne__ datetime.date-class.html#__ne__ +datetime.date.month datetime.date-class.html#month +datetime.date.max datetime.date-class.html#max +datetime.date.isoweekday datetime.date-class.html#isoweekday +datetime.date.__add__ datetime.date-class.html#__add__ +datetime.date.__gt__ datetime.date-class.html#__gt__ +datetime.date.__reduce__ datetime.date-class.html#__reduce__ +datetime.date.replace datetime.date-class.html#replace +datetime.date.__eq__ datetime.date-class.html#__eq__ +datetime.date.day datetime.date-class.html#day +datetime.date.ctime datetime.date-class.html#ctime +datetime.date.fromordinal datetime.date-class.html#fromordinal +datetime.date.fromtimestamp datetime.date-class.html#fromtimestamp +datetime.date.__le__ datetime.date-class.html#__le__ +datetime.date.weekday datetime.date-class.html#weekday +datetime.date.__hash__ datetime.date-class.html#__hash__ +datetime.date.__sub__ datetime.date-class.html#__sub__ +datetime.date.resolution datetime.date-class.html#resolution +datetime.date.__ge__ datetime.date-class.html#__ge__ +datetime.datetime datetime.datetime-class.html +datetime.date.weekday datetime.date-class.html#weekday +datetime.datetime.__rsub__ datetime.datetime-class.html#__rsub__ +datetime.datetime.__str__ datetime.datetime-class.html#__str__ +datetime.datetime.__reduce__ datetime.datetime-class.html#__reduce__ +datetime.datetime.__radd__ datetime.datetime-class.html#__radd__ +datetime.datetime.utctimetuple datetime.datetime-class.html#utctimetuple +datetime.datetime.second datetime.datetime-class.html#second +datetime.date.toordinal datetime.date-class.html#toordinal +datetime.datetime.utcnow datetime.datetime-class.html#utcnow +datetime.date.year datetime.date-class.html#year +datetime.datetime.__lt__ datetime.datetime-class.html#__lt__ +datetime.date.isocalendar datetime.date-class.html#isocalendar +datetime.datetime.now datetime.datetime-class.html#now +datetime.datetime.__new__ datetime.datetime-class.html#__new__ +datetime.datetime.min datetime.datetime-class.html#min +datetime.datetime.dst datetime.datetime-class.html#dst +datetime.datetime.astimezone datetime.datetime-class.html#astimezone +datetime.datetime.strptime datetime.datetime-class.html#strptime +datetime.datetime.utcfromtimestamp datetime.datetime-class.html#utcfromtimestamp +datetime.date.strftime datetime.date-class.html#strftime +datetime.datetime.combine datetime.datetime-class.html#combine +datetime.datetime.timetuple datetime.datetime-class.html#timetuple +datetime.datetime.max datetime.datetime-class.html#max +datetime.datetime.tzinfo datetime.datetime-class.html#tzinfo +datetime.date.today datetime.date-class.html#today +datetime.datetime.__repr__ datetime.datetime-class.html#__repr__ +datetime.datetime.__ne__ datetime.datetime-class.html#__ne__ +datetime.date.month datetime.date-class.html#month +datetime.datetime.__getattribute__ datetime.datetime-class.html#__getattribute__ +datetime.date.isoweekday datetime.date-class.html#isoweekday +datetime.datetime.replace datetime.datetime-class.html#replace +datetime.datetime.utcoffset datetime.datetime-class.html#utcoffset +datetime.datetime.microsecond datetime.datetime-class.html#microsecond +datetime.datetime.__add__ datetime.datetime-class.html#__add__ +datetime.datetime.__gt__ datetime.datetime-class.html#__gt__ +datetime.datetime.date datetime.datetime-class.html#date +datetime.datetime.isoformat datetime.datetime-class.html#isoformat +datetime.datetime.__eq__ datetime.datetime-class.html#__eq__ +datetime.date.day datetime.date-class.html#day +datetime.datetime.minute datetime.datetime-class.html#minute +datetime.datetime.ctime datetime.datetime-class.html#ctime +datetime.datetime.hour datetime.datetime-class.html#hour +datetime.date.fromordinal datetime.date-class.html#fromordinal +datetime.datetime.fromtimestamp datetime.datetime-class.html#fromtimestamp +datetime.datetime.__le__ datetime.datetime-class.html#__le__ +datetime.datetime.tzname datetime.datetime-class.html#tzname +datetime.datetime.time datetime.datetime-class.html#time +datetime.datetime.__hash__ datetime.datetime-class.html#__hash__ +datetime.datetime.__sub__ datetime.datetime-class.html#__sub__ +datetime.datetime.timetz datetime.datetime-class.html#timetz +datetime.datetime.resolution datetime.datetime-class.html#resolution +datetime.datetime.__ge__ datetime.datetime-class.html#__ge__ +datetime.time datetime.time-class.html +datetime.time.__str__ datetime.time-class.html#__str__ +datetime.time.__getattribute__ datetime.time-class.html#__getattribute__ +datetime.time.isoformat datetime.time-class.html#isoformat +datetime.time.second datetime.time-class.html#second +datetime.time.__lt__ datetime.time-class.html#__lt__ +datetime.time.__new__ datetime.time-class.html#__new__ +datetime.time.min datetime.time-class.html#min +datetime.time.dst datetime.time-class.html#dst +datetime.time.strftime datetime.time-class.html#strftime +datetime.time.tzinfo datetime.time-class.html#tzinfo +datetime.time.__repr__ datetime.time-class.html#__repr__ +datetime.time.__ne__ datetime.time-class.html#__ne__ +datetime.time.max datetime.time-class.html#max +datetime.time.utcoffset datetime.time-class.html#utcoffset +datetime.time.microsecond datetime.time-class.html#microsecond +datetime.time.__gt__ datetime.time-class.html#__gt__ +datetime.time.__reduce__ datetime.time-class.html#__reduce__ +datetime.time.replace datetime.time-class.html#replace +datetime.time.__eq__ datetime.time-class.html#__eq__ +datetime.time.minute datetime.time-class.html#minute +datetime.time.__nonzero__ datetime.time-class.html#__nonzero__ +datetime.time.hour datetime.time-class.html#hour +datetime.time.__le__ datetime.time-class.html#__le__ +datetime.time.tzname datetime.time-class.html#tzname +datetime.time.__hash__ datetime.time-class.html#__hash__ +datetime.time.resolution datetime.time-class.html#resolution +datetime.time.__ge__ datetime.time-class.html#__ge__ +exceptions.Exception exceptions.Exception-class.html +exceptions.Exception.__init__ exceptions.Exception-class.html#__init__ +exceptions.Exception.__new__ exceptions.Exception-class.html#__new__ +psycopg2.DataError psycopg2.DataError-class.html +psycopg2.DatabaseError psycopg2.DatabaseError-class.html +psycopg2.Error psycopg2.Error-class.html +psycopg2.IntegrityError psycopg2.IntegrityError-class.html +psycopg2.InterfaceError psycopg2.InterfaceError-class.html +psycopg2.InternalError psycopg2.InternalError-class.html +psycopg2.NotSupportedError psycopg2.NotSupportedError-class.html +psycopg2.OperationalError psycopg2.OperationalError-class.html +psycopg2.ProgrammingError psycopg2.ProgrammingError-class.html +psycopg2.Warning psycopg2.Warning-class.html +psycopg2.tz.FixedOffsetTimezone psycopg2.tz.FixedOffsetTimezone-class.html +psycopg2.tz.FixedOffsetTimezone.dst psycopg2.tz.FixedOffsetTimezone-class.html#dst +psycopg2.tz.FixedOffsetTimezone._name psycopg2.tz.FixedOffsetTimezone-class.html#_name +psycopg2.tz.FixedOffsetTimezone._offset psycopg2.tz.FixedOffsetTimezone-class.html#_offset +psycopg2.tz.FixedOffsetTimezone.utcoffset psycopg2.tz.FixedOffsetTimezone-class.html#utcoffset +psycopg2.tz.FixedOffsetTimezone.tzname psycopg2.tz.FixedOffsetTimezone-class.html#tzname +psycopg2.tz.FixedOffsetTimezone.__init__ psycopg2.tz.FixedOffsetTimezone-class.html#__init__ +psycopg2.tz.LocalTimezone psycopg2.tz.LocalTimezone-class.html +psycopg2.tz.LocalTimezone._isdst psycopg2.tz.LocalTimezone-class.html#_isdst +psycopg2.tz.LocalTimezone.utcoffset psycopg2.tz.LocalTimezone-class.html#utcoffset +psycopg2.tz.LocalTimezone.tzname psycopg2.tz.LocalTimezone-class.html#tzname +psycopg2.tz.LocalTimezone.dst psycopg2.tz.LocalTimezone-class.html#dst +web2py.gluon.cache.Cache web2py.gluon.cache.Cache-class.html +web2py.gluon.cache.Cache.__call__ web2py.gluon.cache.Cache-class.html#__call__ +web2py.gluon.cache.Cache.__init__ web2py.gluon.cache.Cache-class.html#__init__ +web2py.gluon.cache.CacheAbstract web2py.gluon.cache.CacheAbstract-class.html +web2py.gluon.cache.CacheAbstract._clear web2py.gluon.cache.CacheAbstract-class.html#_clear +web2py.gluon.cache.CacheAbstract.clear web2py.gluon.cache.CacheAbstract-class.html#clear +web2py.gluon.cache.CacheAbstract.increment web2py.gluon.cache.CacheAbstract-class.html#increment +web2py.gluon.cache.CacheAbstract.__call__ web2py.gluon.cache.CacheAbstract-class.html#__call__ +web2py.gluon.cache.CacheAbstract.cache_stats_name web2py.gluon.cache.CacheAbstract-class.html#cache_stats_name +web2py.gluon.cache.CacheAbstract.__init__ web2py.gluon.cache.CacheAbstract-class.html#__init__ +web2py.gluon.cache.CacheInRam web2py.gluon.cache.CacheInRam-class.html +web2py.gluon.cache.CacheInRam.meta_storage web2py.gluon.cache.CacheInRam-class.html#meta_storage +web2py.gluon.cache.CacheAbstract._clear web2py.gluon.cache.CacheAbstract-class.html#_clear +web2py.gluon.cache.CacheInRam.clear web2py.gluon.cache.CacheInRam-class.html#clear +web2py.gluon.cache.CacheInRam.locker web2py.gluon.cache.CacheInRam-class.html#locker +web2py.gluon.cache.CacheInRam.increment web2py.gluon.cache.CacheInRam-class.html#increment +web2py.gluon.cache.CacheInRam.__call__ web2py.gluon.cache.CacheInRam-class.html#__call__ +web2py.gluon.cache.CacheAbstract.cache_stats_name web2py.gluon.cache.CacheAbstract-class.html#cache_stats_name +web2py.gluon.cache.CacheInRam.__init__ web2py.gluon.cache.CacheInRam-class.html#__init__ +web2py.gluon.cache.CacheOnDisk web2py.gluon.cache.CacheOnDisk-class.html +web2py.gluon.cache.CacheAbstract._clear web2py.gluon.cache.CacheAbstract-class.html#_clear +web2py.gluon.cache.CacheOnDisk.clear web2py.gluon.cache.CacheOnDisk-class.html#clear +web2py.gluon.cache.CacheOnDisk.__call__ web2py.gluon.cache.CacheOnDisk-class.html#__call__ +web2py.gluon.cache.CacheOnDisk.increment web2py.gluon.cache.CacheOnDisk-class.html#increment +web2py.gluon.cache.CacheOnDisk.speedup_checks web2py.gluon.cache.CacheOnDisk-class.html#speedup_checks +web2py.gluon.cache.CacheAbstract.cache_stats_name web2py.gluon.cache.CacheAbstract-class.html#cache_stats_name +web2py.gluon.cache.CacheOnDisk.__init__ web2py.gluon.cache.CacheOnDisk-class.html#__init__ +web2py.gluon.compileapp.LoadFactory web2py.gluon.compileapp.LoadFactory-class.html +web2py.gluon.compileapp.LoadFactory.__call__ web2py.gluon.compileapp.LoadFactory-class.html#__call__ +web2py.gluon.compileapp.LoadFactory.__init__ web2py.gluon.compileapp.LoadFactory-class.html#__init__ +web2py.gluon.compileapp.mybuiltin web2py.gluon.compileapp.mybuiltin-class.html +web2py.gluon.compileapp.mybuiltin.__getitem__ web2py.gluon.compileapp.mybuiltin-class.html#__getitem__ +web2py.gluon.compileapp.mybuiltin.__setitem__ web2py.gluon.compileapp.mybuiltin-class.html#__setitem__ +web2py.gluon.contrib.pymysql.DBAPISet web2py.gluon.contrib.pymysql.DBAPISet-class.html +web2py.gluon.contrib.pymysql.DBAPISet.__ne__ web2py.gluon.contrib.pymysql.DBAPISet-class.html#__ne__ +web2py.gluon.contrib.pymysql.DBAPISet.__eq__ web2py.gluon.contrib.pymysql.DBAPISet-class.html#__eq__ +web2py.gluon.contrib.pymysql.DBAPISet.__hash__ web2py.gluon.contrib.pymysql.DBAPISet-class.html#__hash__ +web2py.gluon.contrib.pymysql.err.DataError web2py.gluon.contrib.pymysql.err.DataError-class.html +exceptions.Exception.__init__ exceptions.Exception-class.html#__init__ +exceptions.Exception.__new__ exceptions.Exception-class.html#__new__ +web2py.gluon.contrib.pymysql.err.DatabaseError web2py.gluon.contrib.pymysql.err.DatabaseError-class.html +exceptions.Exception.__init__ exceptions.Exception-class.html#__init__ +exceptions.Exception.__new__ exceptions.Exception-class.html#__new__ +web2py.gluon.contrib.pymysql.err.Error web2py.gluon.contrib.pymysql.err.Error-class.html +exceptions.Exception.__init__ exceptions.Exception-class.html#__init__ +exceptions.Exception.__new__ exceptions.Exception-class.html#__new__ +web2py.gluon.contrib.pymysql.err.IntegrityError web2py.gluon.contrib.pymysql.err.IntegrityError-class.html +exceptions.Exception.__init__ exceptions.Exception-class.html#__init__ +exceptions.Exception.__new__ exceptions.Exception-class.html#__new__ +web2py.gluon.contrib.pymysql.err.InterfaceError web2py.gluon.contrib.pymysql.err.InterfaceError-class.html +exceptions.Exception.__init__ exceptions.Exception-class.html#__init__ +exceptions.Exception.__new__ exceptions.Exception-class.html#__new__ +web2py.gluon.contrib.pymysql.err.InternalError web2py.gluon.contrib.pymysql.err.InternalError-class.html +exceptions.Exception.__init__ exceptions.Exception-class.html#__init__ +exceptions.Exception.__new__ exceptions.Exception-class.html#__new__ +web2py.gluon.contrib.pymysql.err.NotSupportedError web2py.gluon.contrib.pymysql.err.NotSupportedError-class.html +exceptions.Exception.__init__ exceptions.Exception-class.html#__init__ +exceptions.Exception.__new__ exceptions.Exception-class.html#__new__ +web2py.gluon.contrib.pymysql.err.OperationalError web2py.gluon.contrib.pymysql.err.OperationalError-class.html +exceptions.Exception.__init__ exceptions.Exception-class.html#__init__ +exceptions.Exception.__new__ exceptions.Exception-class.html#__new__ +web2py.gluon.contrib.pymysql.err.ProgrammingError web2py.gluon.contrib.pymysql.err.ProgrammingError-class.html +exceptions.Exception.__init__ exceptions.Exception-class.html#__init__ +exceptions.Exception.__new__ exceptions.Exception-class.html#__new__ +web2py.gluon.contrib.pymysql.err.Warning web2py.gluon.contrib.pymysql.err.Warning-class.html +web2py.gluon.custom_import._BaseImporter web2py.gluon.custom_import._BaseImporter-class.html +web2py.gluon.custom_import._BaseImporter.begin web2py.gluon.custom_import._BaseImporter-class.html#begin +web2py.gluon.custom_import._BaseImporter.end web2py.gluon.custom_import._BaseImporter-class.html#end +web2py.gluon.custom_import._BaseImporter.__call__ web2py.gluon.custom_import._BaseImporter-class.html#__call__ +web2py.gluon.custom_import._DateTrackerImporter web2py.gluon.custom_import._DateTrackerImporter-class.html +web2py.gluon.custom_import._DateTrackerImporter._get_module_file web2py.gluon.custom_import._DateTrackerImporter-class.html#_get_module_file +web2py.gluon.custom_import._DateTrackerImporter.begin web2py.gluon.custom_import._DateTrackerImporter-class.html#begin +web2py.gluon.custom_import._DateTrackerImporter.end web2py.gluon.custom_import._DateTrackerImporter-class.html#end +web2py.gluon.custom_import._DateTrackerImporter._update_dates web2py.gluon.custom_import._DateTrackerImporter-class.html#_update_dates +web2py.gluon.custom_import._DateTrackerImporter._PACKAGE_PATH_SUFFIX web2py.gluon.custom_import._DateTrackerImporter-class.html#_PACKAGE_PATH_SUFFIX +web2py.gluon.custom_import._DateTrackerImporter.__call__ web2py.gluon.custom_import._DateTrackerImporter-class.html#__call__ +web2py.gluon.custom_import._DateTrackerImporter._reload_check web2py.gluon.custom_import._DateTrackerImporter-class.html#_reload_check +web2py.gluon.custom_import._DateTrackerImporter.__init__ web2py.gluon.custom_import._DateTrackerImporter-class.html#__init__ +web2py.gluon.custom_import._Web2pyDateTrackerImporter web2py.gluon.custom_import._Web2pyDateTrackerImporter-class.html +web2py.gluon.custom_import._Web2pyImporter._matchAppDir web2py.gluon.custom_import._Web2pyImporter-class.html#_matchAppDir +web2py.gluon.custom_import._Web2pyImporter._Web2pyImporter__import__dot web2py.gluon.custom_import._Web2pyImporter-class.html#_Web2pyImporter__import__dot +web2py.gluon.custom_import._DateTrackerImporter.begin web2py.gluon.custom_import._DateTrackerImporter-class.html#begin +web2py.gluon.custom_import._DateTrackerImporter.end web2py.gluon.custom_import._DateTrackerImporter-class.html#end +web2py.gluon.custom_import._DateTrackerImporter._get_module_file web2py.gluon.custom_import._DateTrackerImporter-class.html#_get_module_file +web2py.gluon.custom_import._DateTrackerImporter._update_dates web2py.gluon.custom_import._DateTrackerImporter-class.html#_update_dates +web2py.gluon.custom_import._DateTrackerImporter._PACKAGE_PATH_SUFFIX web2py.gluon.custom_import._DateTrackerImporter-class.html#_PACKAGE_PATH_SUFFIX +web2py.gluon.custom_import._Web2pyImporter._RE_ESCAPED_PATH_SEP web2py.gluon.custom_import._Web2pyImporter-class.html#_RE_ESCAPED_PATH_SEP +web2py.gluon.custom_import._Web2pyImporter.__call__ web2py.gluon.custom_import._Web2pyImporter-class.html#__call__ +web2py.gluon.custom_import._DateTrackerImporter._reload_check web2py.gluon.custom_import._DateTrackerImporter-class.html#_reload_check +web2py.gluon.custom_import._Web2pyImporter.__init__ web2py.gluon.custom_import._Web2pyImporter-class.html#__init__ +web2py.gluon.custom_import._Web2pyImporter web2py.gluon.custom_import._Web2pyImporter-class.html +web2py.gluon.custom_import._Web2pyImporter._matchAppDir web2py.gluon.custom_import._Web2pyImporter-class.html#_matchAppDir +web2py.gluon.custom_import._Web2pyImporter._Web2pyImporter__import__dot web2py.gluon.custom_import._Web2pyImporter-class.html#_Web2pyImporter__import__dot +web2py.gluon.custom_import._BaseImporter.begin web2py.gluon.custom_import._BaseImporter-class.html#begin +web2py.gluon.custom_import._BaseImporter.end web2py.gluon.custom_import._BaseImporter-class.html#end +web2py.gluon.custom_import._Web2pyImporter.__import__dot web2py.gluon.custom_import._Web2pyImporter-class.html#__import__dot +web2py.gluon.custom_import._Web2pyImporter._RE_ESCAPED_PATH_SEP web2py.gluon.custom_import._Web2pyImporter-class.html#_RE_ESCAPED_PATH_SEP +web2py.gluon.custom_import._Web2pyImporter.__call__ web2py.gluon.custom_import._Web2pyImporter-class.html#__call__ +web2py.gluon.custom_import._Web2pyImporter.__init__ web2py.gluon.custom_import._Web2pyImporter-class.html#__init__ +web2py.gluon.dal.BaseAdapter web2py.gluon.dal.BaseAdapter-class.html +web2py.gluon.dal.BaseAdapter.represent web2py.gluon.dal.BaseAdapter-class.html#represent +web2py.gluon.dal.BaseAdapter.log_execute web2py.gluon.dal.BaseAdapter-class.html#log_execute +web2py.gluon.dal.BaseAdapter.select_limitby web2py.gluon.dal.BaseAdapter-class.html#select_limitby +web2py.gluon.dal.BaseAdapter._truncate web2py.gluon.dal.BaseAdapter-class.html#_truncate +web2py.gluon.dal.BaseAdapter.tables web2py.gluon.dal.BaseAdapter-class.html#tables +web2py.gluon.dal.BaseAdapter.LEFT_JOIN web2py.gluon.dal.BaseAdapter-class.html#LEFT_JOIN +web2py.gluon.dal.BaseAdapter.distributed_transaction_begin web2py.gluon.dal.BaseAdapter-class.html#distributed_transaction_begin +web2py.gluon.dal.BaseAdapter.GT web2py.gluon.dal.BaseAdapter-class.html#GT +web2py.gluon.dal.BaseAdapter.rollback web2py.gluon.dal.BaseAdapter-class.html#rollback +web2py.gluon.dal.BaseAdapter.ON web2py.gluon.dal.BaseAdapter-class.html#ON +web2py.gluon.dal.BaseAdapter.ALLOW_NULL web2py.gluon.dal.BaseAdapter-class.html#ALLOW_NULL +web2py.gluon.dal.BaseAdapter.maxcharlength web2py.gluon.dal.BaseAdapter-class.html#maxcharlength +web2py.gluon.dal.BaseAdapter.GE web2py.gluon.dal.BaseAdapter-class.html#GE +web2py.gluon.dal.BaseAdapter.get_table web2py.gluon.dal.BaseAdapter-class.html#get_table +web2py.gluon.dal.BaseAdapter._drop web2py.gluon.dal.BaseAdapter-class.html#_drop +web2py.gluon.dal.BaseAdapter.integrity_error_class web2py.gluon.dal.BaseAdapter-class.html#integrity_error_class +web2py.gluon.dal.ConnectionPool.pool_connection web2py.gluon.dal.ConnectionPool-class.html#pool_connection +web2py.gluon.dal.BaseAdapter._select web2py.gluon.dal.BaseAdapter-class.html#_select +web2py.gluon.dal.BaseAdapter.insert web2py.gluon.dal.BaseAdapter-class.html#insert +web2py.gluon.dal.BaseAdapter.execute web2py.gluon.dal.BaseAdapter-class.html#execute +web2py.gluon.dal.BaseAdapter.drop web2py.gluon.dal.BaseAdapter-class.html#drop +web2py.gluon.dal.BaseAdapter.migrate_table web2py.gluon.dal.BaseAdapter-class.html#migrate_table +web2py.gluon.dal.BaseAdapter.concat_add web2py.gluon.dal.BaseAdapter-class.html#concat_add +web2py.gluon.dal.BaseAdapter._insert web2py.gluon.dal.BaseAdapter-class.html#_insert +web2py.gluon.dal.ConnectionPool.pools web2py.gluon.dal.ConnectionPool-class.html#pools +web2py.gluon.dal.BaseAdapter.support_distributed_transaction web2py.gluon.dal.BaseAdapter-class.html#support_distributed_transaction +web2py.gluon.dal.BaseAdapter.OR web2py.gluon.dal.BaseAdapter-class.html#OR +web2py.gluon.dal.BaseAdapter.file_delete web2py.gluon.dal.BaseAdapter-class.html#file_delete +web2py.gluon.dal.BaseAdapter.NOT_NULL web2py.gluon.dal.BaseAdapter-class.html#NOT_NULL +web2py.gluon.dal.ConnectionPool.find_or_make_work_folder web2py.gluon.dal.ConnectionPool-class.html#find_or_make_work_folder +web2py.gluon.dal.BaseAdapter.close web2py.gluon.dal.BaseAdapter-class.html#close +web2py.gluon.dal.BaseAdapter.integrity_error web2py.gluon.dal.BaseAdapter-class.html#integrity_error +web2py.gluon.dal.BaseAdapter.rowslice web2py.gluon.dal.BaseAdapter-class.html#rowslice +web2py.gluon.dal.BaseAdapter.create_sequence_and_triggers web2py.gluon.dal.BaseAdapter-class.html#create_sequence_and_triggers +web2py.gluon.dal.BaseAdapter.rollback_prepared web2py.gluon.dal.BaseAdapter-class.html#rollback_prepared +web2py.gluon.dal.BaseAdapter.commit_prepared web2py.gluon.dal.BaseAdapter-class.html#commit_prepared +web2py.gluon.dal.BaseAdapter.EQ web2py.gluon.dal.BaseAdapter-class.html#EQ +web2py.gluon.dal.BaseAdapter.AND web2py.gluon.dal.BaseAdapter-class.html#AND +web2py.gluon.dal.BaseAdapter.uploads_in_blob web2py.gluon.dal.BaseAdapter-class.html#uploads_in_blob +web2py.gluon.dal.BaseAdapter.NOT web2py.gluon.dal.BaseAdapter-class.html#NOT +web2py.gluon.dal.BaseAdapter.commit web2py.gluon.dal.BaseAdapter-class.html#commit +web2py.gluon.dal.BaseAdapter.MOD web2py.gluon.dal.BaseAdapter-class.html#MOD +web2py.gluon.dal.BaseAdapter.STARTSWITH web2py.gluon.dal.BaseAdapter-class.html#STARTSWITH +web2py.gluon.dal.BaseAdapter.SUB web2py.gluon.dal.BaseAdapter-class.html#SUB +web2py.gluon.dal.BaseAdapter.COALESCE_ZERO web2py.gluon.dal.BaseAdapter-class.html#COALESCE_ZERO +web2py.gluon.dal.BaseAdapter._delete web2py.gluon.dal.BaseAdapter-class.html#_delete +web2py.gluon.dal.BaseAdapter.BELONGS web2py.gluon.dal.BaseAdapter-class.html#BELONGS +web2py.gluon.dal.BaseAdapter.MUL web2py.gluon.dal.BaseAdapter-class.html#MUL +web2py.gluon.dal.BaseAdapter.select web2py.gluon.dal.BaseAdapter-class.html#select +web2py.gluon.dal.BaseAdapter.PRIMARY_KEY web2py.gluon.dal.BaseAdapter-class.html#PRIMARY_KEY +web2py.gluon.dal.BaseAdapter.prepare web2py.gluon.dal.BaseAdapter-class.html#prepare +web2py.gluon.dal.BaseAdapter.NE web2py.gluon.dal.BaseAdapter-class.html#NE +web2py.gluon.dal.BaseAdapter.SUBSTRING web2py.gluon.dal.BaseAdapter-class.html#SUBSTRING +web2py.gluon.dal.BaseAdapter.expand web2py.gluon.dal.BaseAdapter-class.html#expand +web2py.gluon.dal.BaseAdapter.EXTRACT web2py.gluon.dal.BaseAdapter-class.html#EXTRACT +web2py.gluon.dal.BaseAdapter.commit_on_alter_table web2py.gluon.dal.BaseAdapter-class.html#commit_on_alter_table +web2py.gluon.dal.BaseAdapter.represent_exceptions web2py.gluon.dal.BaseAdapter-class.html#represent_exceptions +web2py.gluon.dal.BaseAdapter.sequence_name web2py.gluon.dal.BaseAdapter-class.html#sequence_name +web2py.gluon.dal.BaseAdapter.ADD web2py.gluon.dal.BaseAdapter-class.html#ADD +web2py.gluon.dal.BaseAdapter.count web2py.gluon.dal.BaseAdapter-class.html#count +web2py.gluon.dal.BaseAdapter.UPPER web2py.gluon.dal.BaseAdapter-class.html#UPPER +web2py.gluon.dal.BaseAdapter.JOIN web2py.gluon.dal.BaseAdapter-class.html#JOIN +web2py.gluon.dal.BaseAdapter.LIKE web2py.gluon.dal.BaseAdapter-class.html#LIKE +web2py.gluon.dal.ConnectionPool.close_all_instances web2py.gluon.dal.ConnectionPool-class.html#close_all_instances +web2py.gluon.dal.BaseAdapter.file_exists web2py.gluon.dal.BaseAdapter-class.html#file_exists +web2py.gluon.dal.BaseAdapter.delete web2py.gluon.dal.BaseAdapter-class.html#delete +web2py.gluon.dal.BaseAdapter.LOWER web2py.gluon.dal.BaseAdapter-class.html#LOWER +web2py.gluon.dal.BaseAdapter._update web2py.gluon.dal.BaseAdapter-class.html#_update +web2py.gluon.dal.BaseAdapter.parse web2py.gluon.dal.BaseAdapter-class.html#parse +web2py.gluon.dal.BaseAdapter.ENDSWITH web2py.gluon.dal.BaseAdapter-class.html#ENDSWITH +web2py.gluon.dal.BaseAdapter.constraint_name web2py.gluon.dal.BaseAdapter-class.html#constraint_name +web2py.gluon.dal.BaseAdapter.DIV web2py.gluon.dal.BaseAdapter-class.html#DIV +web2py.gluon.dal.BaseAdapter.__init__ web2py.gluon.dal.BaseAdapter-class.html#__init__ +web2py.gluon.dal.BaseAdapter.filter_tenant web2py.gluon.dal.BaseAdapter-class.html#filter_tenant +web2py.gluon.dal.BaseAdapter.LE web2py.gluon.dal.BaseAdapter-class.html#LE +web2py.gluon.dal.BaseAdapter.file_open web2py.gluon.dal.BaseAdapter-class.html#file_open +web2py.gluon.dal.ConnectionPool.set_folder web2py.gluon.dal.ConnectionPool-class.html#set_folder +web2py.gluon.dal.BaseAdapter.LT web2py.gluon.dal.BaseAdapter-class.html#LT +web2py.gluon.dal.BaseAdapter.COMMA web2py.gluon.dal.BaseAdapter-class.html#COMMA +web2py.gluon.dal.BaseAdapter.create_table web2py.gluon.dal.BaseAdapter-class.html#create_table +web2py.gluon.dal.BaseAdapter.bulk_insert web2py.gluon.dal.BaseAdapter-class.html#bulk_insert +web2py.gluon.dal.BaseAdapter.AGGREGATE web2py.gluon.dal.BaseAdapter-class.html#AGGREGATE +web2py.gluon.dal.BaseAdapter.file_close web2py.gluon.dal.BaseAdapter-class.html#file_close +web2py.gluon.dal.BaseAdapter.RANDOM web2py.gluon.dal.BaseAdapter-class.html#RANDOM +web2py.gluon.dal.BaseAdapter.truncate web2py.gluon.dal.BaseAdapter-class.html#truncate +web2py.gluon.dal.BaseAdapter.driver web2py.gluon.dal.BaseAdapter-class.html#driver +web2py.gluon.dal.BaseAdapter.update web2py.gluon.dal.BaseAdapter-class.html#update +web2py.gluon.dal.BaseAdapter.AS web2py.gluon.dal.BaseAdapter-class.html#AS +web2py.gluon.dal.BaseAdapter.trigger_name web2py.gluon.dal.BaseAdapter-class.html#trigger_name +web2py.gluon.dal.BaseAdapter.types web2py.gluon.dal.BaseAdapter-class.html#types +web2py.gluon.dal.BaseAdapter._count web2py.gluon.dal.BaseAdapter-class.html#_count +web2py.gluon.dal.BaseAdapter.CONTAINS web2py.gluon.dal.BaseAdapter-class.html#CONTAINS +web2py.gluon.dal.BaseAdapter.INVERT web2py.gluon.dal.BaseAdapter-class.html#INVERT +web2py.gluon.dal.BaseAdapter.alias web2py.gluon.dal.BaseAdapter-class.html#alias +web2py.gluon.dal.BaseAdapter.lastrowid web2py.gluon.dal.BaseAdapter-class.html#lastrowid +web2py.gluon.dal.ConnectionPool web2py.gluon.dal.ConnectionPool-class.html +web2py.gluon.dal.ConnectionPool.close_all_instances web2py.gluon.dal.ConnectionPool-class.html#close_all_instances +web2py.gluon.dal.ConnectionPool.find_or_make_work_folder web2py.gluon.dal.ConnectionPool-class.html#find_or_make_work_folder +web2py.gluon.dal.ConnectionPool.pools web2py.gluon.dal.ConnectionPool-class.html#pools +web2py.gluon.dal.ConnectionPool.set_folder web2py.gluon.dal.ConnectionPool-class.html#set_folder +web2py.gluon.dal.ConnectionPool.pool_connection web2py.gluon.dal.ConnectionPool-class.html#pool_connection +web2py.gluon.dal.CouchDBAdapter web2py.gluon.dal.CouchDBAdapter-class.html +web2py.gluon.dal.CouchDBAdapter.represent web2py.gluon.dal.CouchDBAdapter-class.html#represent +web2py.gluon.dal.NoSQLAdapter.log_execute web2py.gluon.dal.NoSQLAdapter-class.html#log_execute +web2py.gluon.dal.BaseAdapter.select_limitby web2py.gluon.dal.BaseAdapter-class.html#select_limitby +web2py.gluon.dal.BaseAdapter._truncate web2py.gluon.dal.BaseAdapter-class.html#_truncate +web2py.gluon.dal.NoSQLAdapter.SUB web2py.gluon.dal.NoSQLAdapter-class.html#SUB +web2py.gluon.dal.BaseAdapter.tables web2py.gluon.dal.BaseAdapter-class.html#tables +web2py.gluon.dal.NoSQLAdapter.LEFT_JOIN web2py.gluon.dal.NoSQLAdapter-class.html#LEFT_JOIN +web2py.gluon.dal.NoSQLAdapter.distributed_transaction_begin web2py.gluon.dal.NoSQLAdapter-class.html#distributed_transaction_begin +web2py.gluon.dal.BaseAdapter.GT web2py.gluon.dal.BaseAdapter-class.html#GT +web2py.gluon.dal.NoSQLAdapter.rollback web2py.gluon.dal.NoSQLAdapter-class.html#rollback +web2py.gluon.dal.NoSQLAdapter.ON web2py.gluon.dal.NoSQLAdapter-class.html#ON +web2py.gluon.dal.BaseAdapter.ALLOW_NULL web2py.gluon.dal.BaseAdapter-class.html#ALLOW_NULL +web2py.gluon.dal.BaseAdapter.maxcharlength web2py.gluon.dal.BaseAdapter-class.html#maxcharlength +web2py.gluon.dal.BaseAdapter.GE web2py.gluon.dal.BaseAdapter-class.html#GE +web2py.gluon.dal.BaseAdapter.get_table web2py.gluon.dal.BaseAdapter-class.html#get_table +web2py.gluon.dal.BaseAdapter._drop web2py.gluon.dal.BaseAdapter-class.html#_drop +web2py.gluon.dal.NoSQLAdapter.integrity_error_class web2py.gluon.dal.NoSQLAdapter-class.html#integrity_error_class +web2py.gluon.dal.ConnectionPool.pool_connection web2py.gluon.dal.ConnectionPool-class.html#pool_connection +web2py.gluon.dal.CouchDBAdapter._select web2py.gluon.dal.CouchDBAdapter-class.html#_select +web2py.gluon.dal.CouchDBAdapter.insert web2py.gluon.dal.CouchDBAdapter-class.html#insert +web2py.gluon.dal.NoSQLAdapter.execute web2py.gluon.dal.NoSQLAdapter-class.html#execute +web2py.gluon.dal.NoSQLAdapter.drop web2py.gluon.dal.NoSQLAdapter-class.html#drop +web2py.gluon.dal.NoSQLAdapter.migrate_table web2py.gluon.dal.NoSQLAdapter-class.html#migrate_table +web2py.gluon.dal.NoSQLAdapter.concat_add web2py.gluon.dal.NoSQLAdapter-class.html#concat_add +web2py.gluon.dal.NoSQLAdapter._insert web2py.gluon.dal.NoSQLAdapter-class.html#_insert +web2py.gluon.dal.ConnectionPool.pools web2py.gluon.dal.ConnectionPool-class.html#pools +web2py.gluon.dal.BaseAdapter.support_distributed_transaction web2py.gluon.dal.BaseAdapter-class.html#support_distributed_transaction +web2py.gluon.dal.CouchDBAdapter.OR web2py.gluon.dal.CouchDBAdapter-class.html#OR +web2py.gluon.dal.BaseAdapter.file_delete web2py.gluon.dal.BaseAdapter-class.html#file_delete +web2py.gluon.dal.BaseAdapter.NOT_NULL web2py.gluon.dal.BaseAdapter-class.html#NOT_NULL +web2py.gluon.dal.ConnectionPool.find_or_make_work_folder web2py.gluon.dal.ConnectionPool-class.html#find_or_make_work_folder +web2py.gluon.dal.NoSQLAdapter.close web2py.gluon.dal.NoSQLAdapter-class.html#close +web2py.gluon.dal.BaseAdapter.integrity_error web2py.gluon.dal.BaseAdapter-class.html#integrity_error +web2py.gluon.dal.NoSQLAdapter.rowslice web2py.gluon.dal.NoSQLAdapter-class.html#rowslice +web2py.gluon.dal.NoSQLAdapter.create_sequence_and_triggers web2py.gluon.dal.NoSQLAdapter-class.html#create_sequence_and_triggers +web2py.gluon.dal.NoSQLAdapter.rollback_prepared web2py.gluon.dal.NoSQLAdapter-class.html#rollback_prepared +web2py.gluon.dal.NoSQLAdapter.commit_prepared web2py.gluon.dal.NoSQLAdapter-class.html#commit_prepared +web2py.gluon.dal.CouchDBAdapter.EQ web2py.gluon.dal.CouchDBAdapter-class.html#EQ +web2py.gluon.dal.CouchDBAdapter.AND web2py.gluon.dal.CouchDBAdapter-class.html#AND +web2py.gluon.dal.CouchDBAdapter.uploads_in_blob web2py.gluon.dal.CouchDBAdapter-class.html#uploads_in_blob +web2py.gluon.dal.BaseAdapter.NOT web2py.gluon.dal.BaseAdapter-class.html#NOT +web2py.gluon.dal.NoSQLAdapter.commit web2py.gluon.dal.NoSQLAdapter-class.html#commit +web2py.gluon.dal.BaseAdapter.MOD web2py.gluon.dal.BaseAdapter-class.html#MOD +web2py.gluon.dal.NoSQLAdapter.STARTSWITH web2py.gluon.dal.NoSQLAdapter-class.html#STARTSWITH +web2py.gluon.dal.NoSQLAdapter.to_unicode web2py.gluon.dal.NoSQLAdapter-class.html#to_unicode +web2py.gluon.dal.BaseAdapter.COALESCE_ZERO web2py.gluon.dal.BaseAdapter-class.html#COALESCE_ZERO +web2py.gluon.dal.NoSQLAdapter._delete web2py.gluon.dal.NoSQLAdapter-class.html#_delete +web2py.gluon.dal.BaseAdapter.BELONGS web2py.gluon.dal.BaseAdapter-class.html#BELONGS +web2py.gluon.dal.NoSQLAdapter.MUL web2py.gluon.dal.NoSQLAdapter-class.html#MUL +web2py.gluon.dal.CouchDBAdapter.select web2py.gluon.dal.CouchDBAdapter-class.html#select +web2py.gluon.dal.NoSQLAdapter.PRIMARY_KEY web2py.gluon.dal.NoSQLAdapter-class.html#PRIMARY_KEY +web2py.gluon.dal.NoSQLAdapter.prepare web2py.gluon.dal.NoSQLAdapter-class.html#prepare +web2py.gluon.dal.CouchDBAdapter.NE web2py.gluon.dal.CouchDBAdapter-class.html#NE +web2py.gluon.dal.NoSQLAdapter.SUBSTRING web2py.gluon.dal.NoSQLAdapter-class.html#SUBSTRING +web2py.gluon.dal.CouchDBAdapter.expand web2py.gluon.dal.CouchDBAdapter-class.html#expand +web2py.gluon.dal.NoSQLAdapter.EXTRACT web2py.gluon.dal.NoSQLAdapter-class.html#EXTRACT +web2py.gluon.dal.BaseAdapter.commit_on_alter_table web2py.gluon.dal.BaseAdapter-class.html#commit_on_alter_table +web2py.gluon.dal.NoSQLAdapter.represent_exceptions web2py.gluon.dal.NoSQLAdapter-class.html#represent_exceptions +web2py.gluon.dal.BaseAdapter.sequence_name web2py.gluon.dal.BaseAdapter-class.html#sequence_name +web2py.gluon.dal.NoSQLAdapter.ADD web2py.gluon.dal.NoSQLAdapter-class.html#ADD +web2py.gluon.dal.CouchDBAdapter.count web2py.gluon.dal.CouchDBAdapter-class.html#count +web2py.gluon.dal.NoSQLAdapter.UPPER web2py.gluon.dal.NoSQLAdapter-class.html#UPPER +web2py.gluon.dal.BaseAdapter.JOIN web2py.gluon.dal.BaseAdapter-class.html#JOIN +web2py.gluon.dal.NoSQLAdapter.LIKE web2py.gluon.dal.NoSQLAdapter-class.html#LIKE +web2py.gluon.dal.ConnectionPool.close_all_instances web2py.gluon.dal.ConnectionPool-class.html#close_all_instances +web2py.gluon.dal.CouchDBAdapter.file_exists web2py.gluon.dal.CouchDBAdapter-class.html#file_exists +web2py.gluon.dal.CouchDBAdapter.delete web2py.gluon.dal.CouchDBAdapter-class.html#delete +web2py.gluon.dal.NoSQLAdapter.LOWER web2py.gluon.dal.NoSQLAdapter-class.html#LOWER +web2py.gluon.dal.NoSQLAdapter._update web2py.gluon.dal.NoSQLAdapter-class.html#_update +web2py.gluon.dal.BaseAdapter.parse web2py.gluon.dal.BaseAdapter-class.html#parse +web2py.gluon.dal.NoSQLAdapter.ENDSWITH web2py.gluon.dal.NoSQLAdapter-class.html#ENDSWITH +web2py.gluon.dal.NoSQLAdapter.constraint_name web2py.gluon.dal.NoSQLAdapter-class.html#constraint_name +web2py.gluon.dal.NoSQLAdapter.DIV web2py.gluon.dal.NoSQLAdapter-class.html#DIV +web2py.gluon.dal.CouchDBAdapter.__init__ web2py.gluon.dal.CouchDBAdapter-class.html#__init__ +web2py.gluon.dal.BaseAdapter.filter_tenant web2py.gluon.dal.BaseAdapter-class.html#filter_tenant +web2py.gluon.dal.BaseAdapter.LE web2py.gluon.dal.BaseAdapter-class.html#LE +web2py.gluon.dal.CouchDBAdapter.file_open web2py.gluon.dal.CouchDBAdapter-class.html#file_open +web2py.gluon.dal.ConnectionPool.set_folder web2py.gluon.dal.ConnectionPool-class.html#set_folder +web2py.gluon.dal.BaseAdapter.LT web2py.gluon.dal.BaseAdapter-class.html#LT +web2py.gluon.dal.CouchDBAdapter.COMMA web2py.gluon.dal.CouchDBAdapter-class.html#COMMA +web2py.gluon.dal.CouchDBAdapter.create_table web2py.gluon.dal.CouchDBAdapter-class.html#create_table +web2py.gluon.dal.BaseAdapter.bulk_insert web2py.gluon.dal.BaseAdapter-class.html#bulk_insert +web2py.gluon.dal.NoSQLAdapter.AGGREGATE web2py.gluon.dal.NoSQLAdapter-class.html#AGGREGATE +web2py.gluon.dal.CouchDBAdapter.file_close web2py.gluon.dal.CouchDBAdapter-class.html#file_close +web2py.gluon.dal.NoSQLAdapter.RANDOM web2py.gluon.dal.NoSQLAdapter-class.html#RANDOM +web2py.gluon.dal.BaseAdapter.truncate web2py.gluon.dal.BaseAdapter-class.html#truncate +web2py.gluon.dal.BaseAdapter.driver web2py.gluon.dal.BaseAdapter-class.html#driver +web2py.gluon.dal.CouchDBAdapter.update web2py.gluon.dal.CouchDBAdapter-class.html#update +web2py.gluon.dal.NoSQLAdapter.AS web2py.gluon.dal.NoSQLAdapter-class.html#AS +web2py.gluon.dal.BaseAdapter.trigger_name web2py.gluon.dal.BaseAdapter-class.html#trigger_name +web2py.gluon.dal.CouchDBAdapter.types web2py.gluon.dal.CouchDBAdapter-class.html#types +web2py.gluon.dal.NoSQLAdapter._count web2py.gluon.dal.NoSQLAdapter-class.html#_count +web2py.gluon.dal.BaseAdapter.CONTAINS web2py.gluon.dal.BaseAdapter-class.html#CONTAINS +web2py.gluon.dal.BaseAdapter.INVERT web2py.gluon.dal.BaseAdapter-class.html#INVERT +web2py.gluon.dal.NoSQLAdapter.alias web2py.gluon.dal.NoSQLAdapter-class.html#alias +web2py.gluon.dal.NoSQLAdapter.lastrowid web2py.gluon.dal.NoSQLAdapter-class.html#lastrowid +web2py.gluon.dal.CubridAdapter web2py.gluon.dal.CubridAdapter-class.html +web2py.gluon.dal.BaseAdapter.represent web2py.gluon.dal.BaseAdapter-class.html#represent +web2py.gluon.dal.BaseAdapter.log_execute web2py.gluon.dal.BaseAdapter-class.html#log_execute +web2py.gluon.dal.BaseAdapter.select_limitby web2py.gluon.dal.BaseAdapter-class.html#select_limitby +web2py.gluon.dal.BaseAdapter._truncate web2py.gluon.dal.BaseAdapter-class.html#_truncate +web2py.gluon.dal.BaseAdapter.tables web2py.gluon.dal.BaseAdapter-class.html#tables +web2py.gluon.dal.BaseAdapter.LEFT_JOIN web2py.gluon.dal.BaseAdapter-class.html#LEFT_JOIN +web2py.gluon.dal.MySQLAdapter.distributed_transaction_begin web2py.gluon.dal.MySQLAdapter-class.html#distributed_transaction_begin +web2py.gluon.dal.BaseAdapter.GT web2py.gluon.dal.BaseAdapter-class.html#GT +web2py.gluon.dal.BaseAdapter.rollback web2py.gluon.dal.BaseAdapter-class.html#rollback +web2py.gluon.dal.BaseAdapter.ON web2py.gluon.dal.BaseAdapter-class.html#ON +web2py.gluon.dal.BaseAdapter.ALLOW_NULL web2py.gluon.dal.BaseAdapter-class.html#ALLOW_NULL +web2py.gluon.dal.MySQLAdapter.maxcharlength web2py.gluon.dal.MySQLAdapter-class.html#maxcharlength +web2py.gluon.dal.BaseAdapter.GE web2py.gluon.dal.BaseAdapter-class.html#GE +web2py.gluon.dal.BaseAdapter.get_table web2py.gluon.dal.BaseAdapter-class.html#get_table +web2py.gluon.dal.MySQLAdapter._drop web2py.gluon.dal.MySQLAdapter-class.html#_drop +web2py.gluon.dal.BaseAdapter.integrity_error_class web2py.gluon.dal.BaseAdapter-class.html#integrity_error_class +web2py.gluon.dal.ConnectionPool.pool_connection web2py.gluon.dal.ConnectionPool-class.html#pool_connection +web2py.gluon.dal.BaseAdapter._select web2py.gluon.dal.BaseAdapter-class.html#_select +web2py.gluon.dal.BaseAdapter.insert web2py.gluon.dal.BaseAdapter-class.html#insert +web2py.gluon.dal.BaseAdapter.execute web2py.gluon.dal.BaseAdapter-class.html#execute +web2py.gluon.dal.BaseAdapter.drop web2py.gluon.dal.BaseAdapter-class.html#drop +web2py.gluon.dal.BaseAdapter.migrate_table web2py.gluon.dal.BaseAdapter-class.html#migrate_table +web2py.gluon.dal.MySQLAdapter.concat_add web2py.gluon.dal.MySQLAdapter-class.html#concat_add +web2py.gluon.dal.BaseAdapter._insert web2py.gluon.dal.BaseAdapter-class.html#_insert +web2py.gluon.dal.ConnectionPool.pools web2py.gluon.dal.ConnectionPool-class.html#pools +web2py.gluon.dal.MySQLAdapter.support_distributed_transaction web2py.gluon.dal.MySQLAdapter-class.html#support_distributed_transaction +web2py.gluon.dal.BaseAdapter.OR web2py.gluon.dal.BaseAdapter-class.html#OR +web2py.gluon.dal.BaseAdapter.file_delete web2py.gluon.dal.BaseAdapter-class.html#file_delete +web2py.gluon.dal.BaseAdapter.NOT_NULL web2py.gluon.dal.BaseAdapter-class.html#NOT_NULL +web2py.gluon.dal.ConnectionPool.find_or_make_work_folder web2py.gluon.dal.ConnectionPool-class.html#find_or_make_work_folder +web2py.gluon.dal.BaseAdapter.close web2py.gluon.dal.BaseAdapter-class.html#close +web2py.gluon.dal.BaseAdapter.integrity_error web2py.gluon.dal.BaseAdapter-class.html#integrity_error +web2py.gluon.dal.BaseAdapter.rowslice web2py.gluon.dal.BaseAdapter-class.html#rowslice +web2py.gluon.dal.BaseAdapter.create_sequence_and_triggers web2py.gluon.dal.BaseAdapter-class.html#create_sequence_and_triggers +web2py.gluon.dal.MySQLAdapter.rollback_prepared web2py.gluon.dal.MySQLAdapter-class.html#rollback_prepared +web2py.gluon.dal.MySQLAdapter.commit_prepared web2py.gluon.dal.MySQLAdapter-class.html#commit_prepared +web2py.gluon.dal.BaseAdapter.EQ web2py.gluon.dal.BaseAdapter-class.html#EQ +web2py.gluon.dal.BaseAdapter.AND web2py.gluon.dal.BaseAdapter-class.html#AND +web2py.gluon.dal.BaseAdapter.uploads_in_blob web2py.gluon.dal.BaseAdapter-class.html#uploads_in_blob +web2py.gluon.dal.BaseAdapter.NOT web2py.gluon.dal.BaseAdapter-class.html#NOT +web2py.gluon.dal.BaseAdapter.commit web2py.gluon.dal.BaseAdapter-class.html#commit +web2py.gluon.dal.BaseAdapter.MOD web2py.gluon.dal.BaseAdapter-class.html#MOD +web2py.gluon.dal.BaseAdapter.STARTSWITH web2py.gluon.dal.BaseAdapter-class.html#STARTSWITH +web2py.gluon.dal.BaseAdapter.SUB web2py.gluon.dal.BaseAdapter-class.html#SUB +web2py.gluon.dal.BaseAdapter.COALESCE_ZERO web2py.gluon.dal.BaseAdapter-class.html#COALESCE_ZERO +web2py.gluon.dal.BaseAdapter._delete web2py.gluon.dal.BaseAdapter-class.html#_delete +web2py.gluon.dal.BaseAdapter.BELONGS web2py.gluon.dal.BaseAdapter-class.html#BELONGS +web2py.gluon.dal.BaseAdapter.MUL web2py.gluon.dal.BaseAdapter-class.html#MUL +web2py.gluon.dal.BaseAdapter.select web2py.gluon.dal.BaseAdapter-class.html#select +web2py.gluon.dal.BaseAdapter.PRIMARY_KEY web2py.gluon.dal.BaseAdapter-class.html#PRIMARY_KEY +web2py.gluon.dal.MySQLAdapter.prepare web2py.gluon.dal.MySQLAdapter-class.html#prepare +web2py.gluon.dal.BaseAdapter.NE web2py.gluon.dal.BaseAdapter-class.html#NE +web2py.gluon.dal.MySQLAdapter.SUBSTRING web2py.gluon.dal.MySQLAdapter-class.html#SUBSTRING +web2py.gluon.dal.BaseAdapter.expand web2py.gluon.dal.BaseAdapter-class.html#expand +web2py.gluon.dal.BaseAdapter.EXTRACT web2py.gluon.dal.BaseAdapter-class.html#EXTRACT +web2py.gluon.dal.MySQLAdapter.commit_on_alter_table web2py.gluon.dal.MySQLAdapter-class.html#commit_on_alter_table +web2py.gluon.dal.BaseAdapter.represent_exceptions web2py.gluon.dal.BaseAdapter-class.html#represent_exceptions +web2py.gluon.dal.BaseAdapter.sequence_name web2py.gluon.dal.BaseAdapter-class.html#sequence_name +web2py.gluon.dal.BaseAdapter.ADD web2py.gluon.dal.BaseAdapter-class.html#ADD +web2py.gluon.dal.BaseAdapter.count web2py.gluon.dal.BaseAdapter-class.html#count +web2py.gluon.dal.BaseAdapter.UPPER web2py.gluon.dal.BaseAdapter-class.html#UPPER +web2py.gluon.dal.BaseAdapter.JOIN web2py.gluon.dal.BaseAdapter-class.html#JOIN +web2py.gluon.dal.BaseAdapter.LIKE web2py.gluon.dal.BaseAdapter-class.html#LIKE +web2py.gluon.dal.ConnectionPool.close_all_instances web2py.gluon.dal.ConnectionPool-class.html#close_all_instances +web2py.gluon.dal.BaseAdapter.file_exists web2py.gluon.dal.BaseAdapter-class.html#file_exists +web2py.gluon.dal.BaseAdapter.delete web2py.gluon.dal.BaseAdapter-class.html#delete +web2py.gluon.dal.BaseAdapter.LOWER web2py.gluon.dal.BaseAdapter-class.html#LOWER +web2py.gluon.dal.BaseAdapter._update web2py.gluon.dal.BaseAdapter-class.html#_update +web2py.gluon.dal.BaseAdapter.parse web2py.gluon.dal.BaseAdapter-class.html#parse +web2py.gluon.dal.BaseAdapter.ENDSWITH web2py.gluon.dal.BaseAdapter-class.html#ENDSWITH +web2py.gluon.dal.BaseAdapter.constraint_name web2py.gluon.dal.BaseAdapter-class.html#constraint_name +web2py.gluon.dal.BaseAdapter.DIV web2py.gluon.dal.BaseAdapter-class.html#DIV +web2py.gluon.dal.CubridAdapter.__init__ web2py.gluon.dal.CubridAdapter-class.html#__init__ +web2py.gluon.dal.BaseAdapter.filter_tenant web2py.gluon.dal.BaseAdapter-class.html#filter_tenant +web2py.gluon.dal.BaseAdapter.LE web2py.gluon.dal.BaseAdapter-class.html#LE +web2py.gluon.dal.BaseAdapter.file_open web2py.gluon.dal.BaseAdapter-class.html#file_open +web2py.gluon.dal.ConnectionPool.set_folder web2py.gluon.dal.ConnectionPool-class.html#set_folder +web2py.gluon.dal.BaseAdapter.LT web2py.gluon.dal.BaseAdapter-class.html#LT +web2py.gluon.dal.BaseAdapter.COMMA web2py.gluon.dal.BaseAdapter-class.html#COMMA +web2py.gluon.dal.BaseAdapter.create_table web2py.gluon.dal.BaseAdapter-class.html#create_table +web2py.gluon.dal.BaseAdapter.bulk_insert web2py.gluon.dal.BaseAdapter-class.html#bulk_insert +web2py.gluon.dal.BaseAdapter.AGGREGATE web2py.gluon.dal.BaseAdapter-class.html#AGGREGATE +web2py.gluon.dal.BaseAdapter.file_close web2py.gluon.dal.BaseAdapter-class.html#file_close +web2py.gluon.dal.MySQLAdapter.RANDOM web2py.gluon.dal.MySQLAdapter-class.html#RANDOM +web2py.gluon.dal.BaseAdapter.truncate web2py.gluon.dal.BaseAdapter-class.html#truncate +web2py.gluon.dal.CubridAdapter.driver web2py.gluon.dal.CubridAdapter-class.html#driver +web2py.gluon.dal.BaseAdapter.update web2py.gluon.dal.BaseAdapter-class.html#update +web2py.gluon.dal.BaseAdapter.AS web2py.gluon.dal.BaseAdapter-class.html#AS +web2py.gluon.dal.BaseAdapter.trigger_name web2py.gluon.dal.BaseAdapter-class.html#trigger_name +web2py.gluon.dal.MySQLAdapter.types web2py.gluon.dal.MySQLAdapter-class.html#types +web2py.gluon.dal.BaseAdapter._count web2py.gluon.dal.BaseAdapter-class.html#_count +web2py.gluon.dal.BaseAdapter.CONTAINS web2py.gluon.dal.BaseAdapter-class.html#CONTAINS +web2py.gluon.dal.BaseAdapter.INVERT web2py.gluon.dal.BaseAdapter-class.html#INVERT +web2py.gluon.dal.BaseAdapter.alias web2py.gluon.dal.BaseAdapter-class.html#alias +web2py.gluon.dal.MySQLAdapter.lastrowid web2py.gluon.dal.MySQLAdapter-class.html#lastrowid +web2py.gluon.dal.DAL web2py.gluon.dal.DAL-class.html +web2py.gluon.dal.DAL.Field web2py.gluon.dal.Field-class.html +web2py.gluon.dal.DAL.set_folder web2py.gluon.dal.DAL-class.html#set_folder +web2py.gluon.dal.DAL.__init__ web2py.gluon.dal.DAL-class.html#__init__ +web2py.gluon.dal.DAL.import_from_csv_file web2py.gluon.dal.DAL-class.html#import_from_csv_file +web2py.gluon.dal.DAL.__setattr__ web2py.gluon.dal.DAL-class.html#__setattr__ +web2py.gluon.dal.DAL._update_referenced_by web2py.gluon.dal.DAL-class.html#_update_referenced_by +web2py.gluon.dal.DAL.parse_as_rest web2py.gluon.dal.DAL-class.html#parse_as_rest +web2py.gluon.dal.DAL.export_to_csv_file web2py.gluon.dal.DAL-class.html#export_to_csv_file +web2py.gluon.dal.DAL.define_table web2py.gluon.dal.DAL-class.html#define_table +web2py.gluon.dal.DAL.__getattr__ web2py.gluon.dal.DAL-class.html#__getattr__ +web2py.gluon.dal.DAL.__call__ web2py.gluon.dal.DAL-class.html#__call__ +web2py.gluon.dal.DAL.import_table_definitions web2py.gluon.dal.DAL-class.html#import_table_definitions +web2py.gluon.dal.DAL.distributed_transaction_begin web2py.gluon.dal.DAL-class.html#distributed_transaction_begin +web2py.gluon.dal.DAL.rollback web2py.gluon.dal.DAL-class.html#rollback +web2py.gluon.dal.DAL.__getitem__ web2py.gluon.dal.DAL-class.html#__getitem__ +web2py.gluon.dal.DAL.executesql web2py.gluon.dal.DAL-class.html#executesql +web2py.gluon.dal.DAL.distributed_transaction_commit web2py.gluon.dal.DAL-class.html#distributed_transaction_commit +web2py.gluon.dal.DAL.check_reserved_keyword web2py.gluon.dal.DAL-class.html#check_reserved_keyword +web2py.gluon.dal.DAL.__setitem__ web2py.gluon.dal.DAL-class.html#__setitem__ +web2py.gluon.dal.DAL.commit web2py.gluon.dal.DAL-class.html#commit +web2py.gluon.dal.DAL.__contains__ web2py.gluon.dal.DAL-class.html#__contains__ +web2py.gluon.dal.DAL.__iter__ web2py.gluon.dal.DAL-class.html#__iter__ +web2py.gluon.dal.DAL.__repr__ web2py.gluon.dal.DAL-class.html#__repr__ +web2py.gluon.dal.DAL.Table web2py.gluon.dal.Table-class.html +web2py.gluon.dal.DB2Adapter web2py.gluon.dal.DB2Adapter-class.html +web2py.gluon.dal.BaseAdapter.represent web2py.gluon.dal.BaseAdapter-class.html#represent +web2py.gluon.dal.BaseAdapter.log_execute web2py.gluon.dal.BaseAdapter-class.html#log_execute +web2py.gluon.dal.DB2Adapter.select_limitby web2py.gluon.dal.DB2Adapter-class.html#select_limitby +web2py.gluon.dal.BaseAdapter._truncate web2py.gluon.dal.BaseAdapter-class.html#_truncate +web2py.gluon.dal.BaseAdapter.tables web2py.gluon.dal.BaseAdapter-class.html#tables +web2py.gluon.dal.DB2Adapter.LEFT_JOIN web2py.gluon.dal.DB2Adapter-class.html#LEFT_JOIN +web2py.gluon.dal.BaseAdapter.distributed_transaction_begin web2py.gluon.dal.BaseAdapter-class.html#distributed_transaction_begin +web2py.gluon.dal.BaseAdapter.GT web2py.gluon.dal.BaseAdapter-class.html#GT +web2py.gluon.dal.BaseAdapter.rollback web2py.gluon.dal.BaseAdapter-class.html#rollback +web2py.gluon.dal.BaseAdapter.ON web2py.gluon.dal.BaseAdapter-class.html#ON +web2py.gluon.dal.BaseAdapter.ALLOW_NULL web2py.gluon.dal.BaseAdapter-class.html#ALLOW_NULL +web2py.gluon.dal.BaseAdapter.maxcharlength web2py.gluon.dal.BaseAdapter-class.html#maxcharlength +web2py.gluon.dal.BaseAdapter.GE web2py.gluon.dal.BaseAdapter-class.html#GE +web2py.gluon.dal.BaseAdapter.get_table web2py.gluon.dal.BaseAdapter-class.html#get_table +web2py.gluon.dal.BaseAdapter._drop web2py.gluon.dal.BaseAdapter-class.html#_drop +web2py.gluon.dal.BaseAdapter.integrity_error_class web2py.gluon.dal.BaseAdapter-class.html#integrity_error_class +web2py.gluon.dal.ConnectionPool.pool_connection web2py.gluon.dal.ConnectionPool-class.html#pool_connection +web2py.gluon.dal.BaseAdapter._select web2py.gluon.dal.BaseAdapter-class.html#_select +web2py.gluon.dal.BaseAdapter.insert web2py.gluon.dal.BaseAdapter-class.html#insert +web2py.gluon.dal.DB2Adapter.execute web2py.gluon.dal.DB2Adapter-class.html#execute +web2py.gluon.dal.BaseAdapter.drop web2py.gluon.dal.BaseAdapter-class.html#drop +web2py.gluon.dal.BaseAdapter.migrate_table web2py.gluon.dal.BaseAdapter-class.html#migrate_table +web2py.gluon.dal.BaseAdapter.concat_add web2py.gluon.dal.BaseAdapter-class.html#concat_add +web2py.gluon.dal.BaseAdapter._insert web2py.gluon.dal.BaseAdapter-class.html#_insert +web2py.gluon.dal.ConnectionPool.pools web2py.gluon.dal.ConnectionPool-class.html#pools +web2py.gluon.dal.BaseAdapter.support_distributed_transaction web2py.gluon.dal.BaseAdapter-class.html#support_distributed_transaction +web2py.gluon.dal.BaseAdapter.OR web2py.gluon.dal.BaseAdapter-class.html#OR +web2py.gluon.dal.BaseAdapter.file_delete web2py.gluon.dal.BaseAdapter-class.html#file_delete +web2py.gluon.dal.BaseAdapter.NOT_NULL web2py.gluon.dal.BaseAdapter-class.html#NOT_NULL +web2py.gluon.dal.ConnectionPool.find_or_make_work_folder web2py.gluon.dal.ConnectionPool-class.html#find_or_make_work_folder +web2py.gluon.dal.BaseAdapter.close web2py.gluon.dal.BaseAdapter-class.html#close +web2py.gluon.dal.BaseAdapter.integrity_error web2py.gluon.dal.BaseAdapter-class.html#integrity_error +web2py.gluon.dal.DB2Adapter.rowslice web2py.gluon.dal.DB2Adapter-class.html#rowslice +web2py.gluon.dal.BaseAdapter.create_sequence_and_triggers web2py.gluon.dal.BaseAdapter-class.html#create_sequence_and_triggers +web2py.gluon.dal.BaseAdapter.rollback_prepared web2py.gluon.dal.BaseAdapter-class.html#rollback_prepared +web2py.gluon.dal.BaseAdapter.commit_prepared web2py.gluon.dal.BaseAdapter-class.html#commit_prepared +web2py.gluon.dal.BaseAdapter.EQ web2py.gluon.dal.BaseAdapter-class.html#EQ +web2py.gluon.dal.BaseAdapter.AND web2py.gluon.dal.BaseAdapter-class.html#AND +web2py.gluon.dal.BaseAdapter.uploads_in_blob web2py.gluon.dal.BaseAdapter-class.html#uploads_in_blob +web2py.gluon.dal.BaseAdapter.NOT web2py.gluon.dal.BaseAdapter-class.html#NOT +web2py.gluon.dal.BaseAdapter.commit web2py.gluon.dal.BaseAdapter-class.html#commit +web2py.gluon.dal.BaseAdapter.MOD web2py.gluon.dal.BaseAdapter-class.html#MOD +web2py.gluon.dal.BaseAdapter.STARTSWITH web2py.gluon.dal.BaseAdapter-class.html#STARTSWITH +web2py.gluon.dal.BaseAdapter.SUB web2py.gluon.dal.BaseAdapter-class.html#SUB +web2py.gluon.dal.BaseAdapter.COALESCE_ZERO web2py.gluon.dal.BaseAdapter-class.html#COALESCE_ZERO +web2py.gluon.dal.BaseAdapter._delete web2py.gluon.dal.BaseAdapter-class.html#_delete +web2py.gluon.dal.BaseAdapter.BELONGS web2py.gluon.dal.BaseAdapter-class.html#BELONGS +web2py.gluon.dal.BaseAdapter.MUL web2py.gluon.dal.BaseAdapter-class.html#MUL +web2py.gluon.dal.BaseAdapter.select web2py.gluon.dal.BaseAdapter-class.html#select +web2py.gluon.dal.BaseAdapter.PRIMARY_KEY web2py.gluon.dal.BaseAdapter-class.html#PRIMARY_KEY +web2py.gluon.dal.BaseAdapter.prepare web2py.gluon.dal.BaseAdapter-class.html#prepare +web2py.gluon.dal.BaseAdapter.NE web2py.gluon.dal.BaseAdapter-class.html#NE +web2py.gluon.dal.BaseAdapter.SUBSTRING web2py.gluon.dal.BaseAdapter-class.html#SUBSTRING +web2py.gluon.dal.DB2Adapter.types web2py.gluon.dal.DB2Adapter-class.html#types +web2py.gluon.dal.BaseAdapter.EXTRACT web2py.gluon.dal.BaseAdapter-class.html#EXTRACT +web2py.gluon.dal.BaseAdapter.commit_on_alter_table web2py.gluon.dal.BaseAdapter-class.html#commit_on_alter_table +web2py.gluon.dal.DB2Adapter.represent_exceptions web2py.gluon.dal.DB2Adapter-class.html#represent_exceptions +web2py.gluon.dal.BaseAdapter.sequence_name web2py.gluon.dal.BaseAdapter-class.html#sequence_name +web2py.gluon.dal.BaseAdapter.ADD web2py.gluon.dal.BaseAdapter-class.html#ADD +web2py.gluon.dal.BaseAdapter.count web2py.gluon.dal.BaseAdapter-class.html#count +web2py.gluon.dal.BaseAdapter.UPPER web2py.gluon.dal.BaseAdapter-class.html#UPPER +web2py.gluon.dal.BaseAdapter.JOIN web2py.gluon.dal.BaseAdapter-class.html#JOIN +web2py.gluon.dal.BaseAdapter.LIKE web2py.gluon.dal.BaseAdapter-class.html#LIKE +web2py.gluon.dal.ConnectionPool.close_all_instances web2py.gluon.dal.ConnectionPool-class.html#close_all_instances +web2py.gluon.dal.BaseAdapter.file_exists web2py.gluon.dal.BaseAdapter-class.html#file_exists +web2py.gluon.dal.BaseAdapter.delete web2py.gluon.dal.BaseAdapter-class.html#delete +web2py.gluon.dal.BaseAdapter.LOWER web2py.gluon.dal.BaseAdapter-class.html#LOWER +web2py.gluon.dal.BaseAdapter._update web2py.gluon.dal.BaseAdapter-class.html#_update +web2py.gluon.dal.BaseAdapter.parse web2py.gluon.dal.BaseAdapter-class.html#parse +web2py.gluon.dal.BaseAdapter.ENDSWITH web2py.gluon.dal.BaseAdapter-class.html#ENDSWITH +web2py.gluon.dal.BaseAdapter.constraint_name web2py.gluon.dal.BaseAdapter-class.html#constraint_name +web2py.gluon.dal.BaseAdapter.DIV web2py.gluon.dal.BaseAdapter-class.html#DIV +web2py.gluon.dal.DB2Adapter.__init__ web2py.gluon.dal.DB2Adapter-class.html#__init__ +web2py.gluon.dal.BaseAdapter.filter_tenant web2py.gluon.dal.BaseAdapter-class.html#filter_tenant +web2py.gluon.dal.BaseAdapter.LE web2py.gluon.dal.BaseAdapter-class.html#LE +web2py.gluon.dal.BaseAdapter.file_open web2py.gluon.dal.BaseAdapter-class.html#file_open +web2py.gluon.dal.ConnectionPool.set_folder web2py.gluon.dal.ConnectionPool-class.html#set_folder +web2py.gluon.dal.BaseAdapter.LT web2py.gluon.dal.BaseAdapter-class.html#LT +web2py.gluon.dal.BaseAdapter.COMMA web2py.gluon.dal.BaseAdapter-class.html#COMMA +web2py.gluon.dal.BaseAdapter.create_table web2py.gluon.dal.BaseAdapter-class.html#create_table +web2py.gluon.dal.BaseAdapter.bulk_insert web2py.gluon.dal.BaseAdapter-class.html#bulk_insert +web2py.gluon.dal.BaseAdapter.AGGREGATE web2py.gluon.dal.BaseAdapter-class.html#AGGREGATE +web2py.gluon.dal.BaseAdapter.file_close web2py.gluon.dal.BaseAdapter-class.html#file_close +web2py.gluon.dal.DB2Adapter.RANDOM web2py.gluon.dal.DB2Adapter-class.html#RANDOM +web2py.gluon.dal.BaseAdapter.truncate web2py.gluon.dal.BaseAdapter-class.html#truncate +web2py.gluon.dal.DB2Adapter.driver web2py.gluon.dal.DB2Adapter-class.html#driver +web2py.gluon.dal.BaseAdapter.update web2py.gluon.dal.BaseAdapter-class.html#update +web2py.gluon.dal.BaseAdapter.AS web2py.gluon.dal.BaseAdapter-class.html#AS +web2py.gluon.dal.BaseAdapter.trigger_name web2py.gluon.dal.BaseAdapter-class.html#trigger_name +web2py.gluon.dal.BaseAdapter.expand web2py.gluon.dal.BaseAdapter-class.html#expand +web2py.gluon.dal.BaseAdapter._count web2py.gluon.dal.BaseAdapter-class.html#_count +web2py.gluon.dal.BaseAdapter.CONTAINS web2py.gluon.dal.BaseAdapter-class.html#CONTAINS +web2py.gluon.dal.BaseAdapter.INVERT web2py.gluon.dal.BaseAdapter-class.html#INVERT +web2py.gluon.dal.BaseAdapter.alias web2py.gluon.dal.BaseAdapter-class.html#alias +web2py.gluon.dal.DB2Adapter.lastrowid web2py.gluon.dal.DB2Adapter-class.html#lastrowid +web2py.gluon.dal.DatabaseStoredFile web2py.gluon.dal.DatabaseStoredFile-class.html +web2py.gluon.dal.DatabaseStoredFile.exists web2py.gluon.dal.DatabaseStoredFile-class.html#exists +web2py.gluon.dal.DatabaseStoredFile.read web2py.gluon.dal.DatabaseStoredFile-class.html#read +web2py.gluon.dal.DatabaseStoredFile.write web2py.gluon.dal.DatabaseStoredFile-class.html#write +web2py.gluon.dal.DatabaseStoredFile.web2py_filesystem web2py.gluon.dal.DatabaseStoredFile-class.html#web2py_filesystem +web2py.gluon.dal.DatabaseStoredFile.close web2py.gluon.dal.DatabaseStoredFile-class.html#close +web2py.gluon.dal.DatabaseStoredFile.readline web2py.gluon.dal.DatabaseStoredFile-class.html#readline +web2py.gluon.dal.DatabaseStoredFile.__init__ web2py.gluon.dal.DatabaseStoredFile-class.html#__init__ +web2py.gluon.dal.Expression web2py.gluon.dal.Expression-class.html +web2py.gluon.dal.Expression.upper web2py.gluon.dal.Expression-class.html#upper +web2py.gluon.dal.Expression.__getslice__ web2py.gluon.dal.Expression-class.html#__getslice__ +web2py.gluon.dal.Expression.coalesce_zero web2py.gluon.dal.Expression-class.html#coalesce_zero +web2py.gluon.dal.Expression.__str__ web2py.gluon.dal.Expression-class.html#__str__ +web2py.gluon.dal.Expression.month web2py.gluon.dal.Expression-class.html#month +web2py.gluon.dal.Expression.endswith web2py.gluon.dal.Expression-class.html#endswith +web2py.gluon.dal.Expression.year web2py.gluon.dal.Expression-class.html#year +web2py.gluon.dal.Expression.__lt__ web2py.gluon.dal.Expression-class.html#__lt__ +web2py.gluon.dal.Expression.__init__ web2py.gluon.dal.Expression-class.html#__init__ +web2py.gluon.dal.Expression.__invert__ web2py.gluon.dal.Expression-class.html#__invert__ +web2py.gluon.dal.Expression.min web2py.gluon.dal.Expression-class.html#min +web2py.gluon.dal.Expression.sum web2py.gluon.dal.Expression-class.html#sum +web2py.gluon.dal.Expression.contains web2py.gluon.dal.Expression-class.html#contains +web2py.gluon.dal.Expression.belongs web2py.gluon.dal.Expression-class.html#belongs +web2py.gluon.dal.Expression.with_alias web2py.gluon.dal.Expression-class.html#with_alias +web2py.gluon.dal.Expression.startswith web2py.gluon.dal.Expression-class.html#startswith +web2py.gluon.dal.Expression.__getitem__ web2py.gluon.dal.Expression-class.html#__getitem__ +web2py.gluon.dal.Expression.seconds web2py.gluon.dal.Expression-class.html#seconds +web2py.gluon.dal.Expression.max web2py.gluon.dal.Expression-class.html#max +web2py.gluon.dal.Expression.len web2py.gluon.dal.Expression-class.html#len +web2py.gluon.dal.Expression.__or__ web2py.gluon.dal.Expression-class.html#__or__ +web2py.gluon.dal.Expression.__add__ web2py.gluon.dal.Expression-class.html#__add__ +web2py.gluon.dal.Expression.__gt__ web2py.gluon.dal.Expression-class.html#__gt__ +web2py.gluon.dal.Expression.__ne__ web2py.gluon.dal.Expression-class.html#__ne__ +web2py.gluon.dal.Expression.__eq__ web2py.gluon.dal.Expression-class.html#__eq__ +web2py.gluon.dal.Expression.day web2py.gluon.dal.Expression-class.html#day +web2py.gluon.dal.Expression.lower web2py.gluon.dal.Expression-class.html#lower +web2py.gluon.dal.Expression.like web2py.gluon.dal.Expression-class.html#like +web2py.gluon.dal.Expression.hour web2py.gluon.dal.Expression-class.html#hour +web2py.gluon.dal.Expression.__mod__ web2py.gluon.dal.Expression-class.html#__mod__ +web2py.gluon.dal.Expression.__div__ web2py.gluon.dal.Expression-class.html#__div__ +web2py.gluon.dal.Expression.__le__ web2py.gluon.dal.Expression-class.html#__le__ +web2py.gluon.dal.Expression.__mul__ web2py.gluon.dal.Expression-class.html#__mul__ +web2py.gluon.dal.Expression.__sub__ web2py.gluon.dal.Expression-class.html#__sub__ +web2py.gluon.dal.Expression.minutes web2py.gluon.dal.Expression-class.html#minutes +web2py.gluon.dal.Expression.__ge__ web2py.gluon.dal.Expression-class.html#__ge__ +web2py.gluon.dal.Field web2py.gluon.dal.Field-class.html +web2py.gluon.dal.Expression.upper web2py.gluon.dal.Expression-class.html#upper +web2py.gluon.dal.Field.retrieve web2py.gluon.dal.Field-class.html#retrieve +web2py.gluon.dal.Expression.__getslice__ web2py.gluon.dal.Expression-class.html#__getslice__ +web2py.gluon.dal.Expression.coalesce_zero web2py.gluon.dal.Expression-class.html#coalesce_zero +web2py.gluon.dal.Field.__str__ web2py.gluon.dal.Field-class.html#__str__ +web2py.gluon.dal.Expression.month web2py.gluon.dal.Expression-class.html#month +web2py.gluon.dal.Expression.endswith web2py.gluon.dal.Expression-class.html#endswith +web2py.gluon.dal.Expression.year web2py.gluon.dal.Expression-class.html#year +web2py.gluon.dal.Expression.__lt__ web2py.gluon.dal.Expression-class.html#__lt__ +web2py.gluon.dal.Field.__init__ web2py.gluon.dal.Field-class.html#__init__ +web2py.gluon.dal.Expression.__getitem__ web2py.gluon.dal.Expression-class.html#__getitem__ +web2py.gluon.dal.Expression.min web2py.gluon.dal.Expression-class.html#min +web2py.gluon.dal.Expression.sum web2py.gluon.dal.Expression-class.html#sum +web2py.gluon.dal.Expression.contains web2py.gluon.dal.Expression-class.html#contains +web2py.gluon.dal.Expression.belongs web2py.gluon.dal.Expression-class.html#belongs +web2py.gluon.dal.Expression.with_alias web2py.gluon.dal.Expression-class.html#with_alias +web2py.gluon.dal.Field.formatter web2py.gluon.dal.Field-class.html#formatter +web2py.gluon.dal.Field.store web2py.gluon.dal.Field-class.html#store +web2py.gluon.dal.Expression.startswith web2py.gluon.dal.Expression-class.html#startswith +web2py.gluon.dal.Expression.__invert__ web2py.gluon.dal.Expression-class.html#__invert__ +web2py.gluon.dal.Expression.seconds web2py.gluon.dal.Expression-class.html#seconds +web2py.gluon.dal.Expression.max web2py.gluon.dal.Expression-class.html#max +web2py.gluon.dal.Expression.len web2py.gluon.dal.Expression-class.html#len +web2py.gluon.dal.Expression.__or__ web2py.gluon.dal.Expression-class.html#__or__ +web2py.gluon.dal.Expression.__add__ web2py.gluon.dal.Expression-class.html#__add__ +web2py.gluon.dal.Expression.__gt__ web2py.gluon.dal.Expression-class.html#__gt__ +web2py.gluon.dal.Field.validate web2py.gluon.dal.Field-class.html#validate +web2py.gluon.dal.Expression.__eq__ web2py.gluon.dal.Expression-class.html#__eq__ +web2py.gluon.dal.Expression.day web2py.gluon.dal.Expression-class.html#day +web2py.gluon.dal.Field.count web2py.gluon.dal.Field-class.html#count +web2py.gluon.dal.Expression.lower web2py.gluon.dal.Expression-class.html#lower +web2py.gluon.dal.Expression.__ne__ web2py.gluon.dal.Expression-class.html#__ne__ +web2py.gluon.dal.Field.__nonzero__ web2py.gluon.dal.Field-class.html#__nonzero__ +web2py.gluon.dal.Expression.like web2py.gluon.dal.Expression-class.html#like +web2py.gluon.dal.Expression.hour web2py.gluon.dal.Expression-class.html#hour +web2py.gluon.dal.Expression.__mod__ web2py.gluon.dal.Expression-class.html#__mod__ +web2py.gluon.dal.Expression.__div__ web2py.gluon.dal.Expression-class.html#__div__ +web2py.gluon.dal.Expression.__le__ web2py.gluon.dal.Expression-class.html#__le__ +web2py.gluon.dal.Expression.__mul__ web2py.gluon.dal.Expression-class.html#__mul__ +web2py.gluon.dal.Expression.__sub__ web2py.gluon.dal.Expression-class.html#__sub__ +web2py.gluon.dal.Expression.minutes web2py.gluon.dal.Expression-class.html#minutes +web2py.gluon.dal.Expression.__ge__ web2py.gluon.dal.Expression-class.html#__ge__ +web2py.gluon.dal.FireBirdAdapter web2py.gluon.dal.FireBirdAdapter-class.html +web2py.gluon.dal.BaseAdapter.represent web2py.gluon.dal.BaseAdapter-class.html#represent +web2py.gluon.dal.BaseAdapter.log_execute web2py.gluon.dal.BaseAdapter-class.html#log_execute +web2py.gluon.dal.FireBirdAdapter.select_limitby web2py.gluon.dal.FireBirdAdapter-class.html#select_limitby +web2py.gluon.dal.FireBirdAdapter._truncate web2py.gluon.dal.FireBirdAdapter-class.html#_truncate +web2py.gluon.dal.BaseAdapter.tables web2py.gluon.dal.BaseAdapter-class.html#tables +web2py.gluon.dal.BaseAdapter.LEFT_JOIN web2py.gluon.dal.BaseAdapter-class.html#LEFT_JOIN +web2py.gluon.dal.BaseAdapter.distributed_transaction_begin web2py.gluon.dal.BaseAdapter-class.html#distributed_transaction_begin +web2py.gluon.dal.BaseAdapter.GT web2py.gluon.dal.BaseAdapter-class.html#GT +web2py.gluon.dal.BaseAdapter.rollback web2py.gluon.dal.BaseAdapter-class.html#rollback +web2py.gluon.dal.BaseAdapter.ON web2py.gluon.dal.BaseAdapter-class.html#ON +web2py.gluon.dal.BaseAdapter.ALLOW_NULL web2py.gluon.dal.BaseAdapter-class.html#ALLOW_NULL +web2py.gluon.dal.BaseAdapter.maxcharlength web2py.gluon.dal.BaseAdapter-class.html#maxcharlength +web2py.gluon.dal.BaseAdapter.GE web2py.gluon.dal.BaseAdapter-class.html#GE +web2py.gluon.dal.BaseAdapter.get_table web2py.gluon.dal.BaseAdapter-class.html#get_table +web2py.gluon.dal.FireBirdAdapter._drop web2py.gluon.dal.FireBirdAdapter-class.html#_drop +web2py.gluon.dal.BaseAdapter.integrity_error_class web2py.gluon.dal.BaseAdapter-class.html#integrity_error_class +web2py.gluon.dal.ConnectionPool.pool_connection web2py.gluon.dal.ConnectionPool-class.html#pool_connection +web2py.gluon.dal.BaseAdapter._select web2py.gluon.dal.BaseAdapter-class.html#_select +web2py.gluon.dal.BaseAdapter.insert web2py.gluon.dal.BaseAdapter-class.html#insert +web2py.gluon.dal.BaseAdapter.execute web2py.gluon.dal.BaseAdapter-class.html#execute +web2py.gluon.dal.BaseAdapter.drop web2py.gluon.dal.BaseAdapter-class.html#drop +web2py.gluon.dal.BaseAdapter.migrate_table web2py.gluon.dal.BaseAdapter-class.html#migrate_table +web2py.gluon.dal.BaseAdapter.concat_add web2py.gluon.dal.BaseAdapter-class.html#concat_add +web2py.gluon.dal.BaseAdapter._insert web2py.gluon.dal.BaseAdapter-class.html#_insert +web2py.gluon.dal.ConnectionPool.pools web2py.gluon.dal.ConnectionPool-class.html#pools +web2py.gluon.dal.FireBirdAdapter.support_distributed_transaction web2py.gluon.dal.FireBirdAdapter-class.html#support_distributed_transaction +web2py.gluon.dal.BaseAdapter.OR web2py.gluon.dal.BaseAdapter-class.html#OR +web2py.gluon.dal.BaseAdapter.file_delete web2py.gluon.dal.BaseAdapter-class.html#file_delete +web2py.gluon.dal.FireBirdAdapter.NOT_NULL web2py.gluon.dal.FireBirdAdapter-class.html#NOT_NULL +web2py.gluon.dal.ConnectionPool.find_or_make_work_folder web2py.gluon.dal.ConnectionPool-class.html#find_or_make_work_folder +web2py.gluon.dal.BaseAdapter.close web2py.gluon.dal.BaseAdapter-class.html#close +web2py.gluon.dal.BaseAdapter.integrity_error web2py.gluon.dal.BaseAdapter-class.html#integrity_error +web2py.gluon.dal.BaseAdapter.rowslice web2py.gluon.dal.BaseAdapter-class.html#rowslice +web2py.gluon.dal.FireBirdAdapter.create_sequence_and_triggers web2py.gluon.dal.FireBirdAdapter-class.html#create_sequence_and_triggers +web2py.gluon.dal.BaseAdapter.rollback_prepared web2py.gluon.dal.BaseAdapter-class.html#rollback_prepared +web2py.gluon.dal.BaseAdapter.commit_prepared web2py.gluon.dal.BaseAdapter-class.html#commit_prepared +web2py.gluon.dal.BaseAdapter.EQ web2py.gluon.dal.BaseAdapter-class.html#EQ +web2py.gluon.dal.BaseAdapter.AND web2py.gluon.dal.BaseAdapter-class.html#AND +web2py.gluon.dal.BaseAdapter.uploads_in_blob web2py.gluon.dal.BaseAdapter-class.html#uploads_in_blob +web2py.gluon.dal.BaseAdapter.NOT web2py.gluon.dal.BaseAdapter-class.html#NOT +web2py.gluon.dal.BaseAdapter.commit web2py.gluon.dal.BaseAdapter-class.html#commit +web2py.gluon.dal.BaseAdapter.MOD web2py.gluon.dal.BaseAdapter-class.html#MOD +web2py.gluon.dal.BaseAdapter.STARTSWITH web2py.gluon.dal.BaseAdapter-class.html#STARTSWITH +web2py.gluon.dal.BaseAdapter.SUB web2py.gluon.dal.BaseAdapter-class.html#SUB +web2py.gluon.dal.BaseAdapter.COALESCE_ZERO web2py.gluon.dal.BaseAdapter-class.html#COALESCE_ZERO +web2py.gluon.dal.BaseAdapter._delete web2py.gluon.dal.BaseAdapter-class.html#_delete +web2py.gluon.dal.BaseAdapter.BELONGS web2py.gluon.dal.BaseAdapter-class.html#BELONGS +web2py.gluon.dal.BaseAdapter.MUL web2py.gluon.dal.BaseAdapter-class.html#MUL +web2py.gluon.dal.BaseAdapter.select web2py.gluon.dal.BaseAdapter-class.html#select +web2py.gluon.dal.BaseAdapter.PRIMARY_KEY web2py.gluon.dal.BaseAdapter-class.html#PRIMARY_KEY +web2py.gluon.dal.BaseAdapter.prepare web2py.gluon.dal.BaseAdapter-class.html#prepare +web2py.gluon.dal.BaseAdapter.NE web2py.gluon.dal.BaseAdapter-class.html#NE +web2py.gluon.dal.FireBirdAdapter.SUBSTRING web2py.gluon.dal.FireBirdAdapter-class.html#SUBSTRING +web2py.gluon.dal.BaseAdapter.expand web2py.gluon.dal.BaseAdapter-class.html#expand +web2py.gluon.dal.BaseAdapter.EXTRACT web2py.gluon.dal.BaseAdapter-class.html#EXTRACT +web2py.gluon.dal.FireBirdAdapter.commit_on_alter_table web2py.gluon.dal.FireBirdAdapter-class.html#commit_on_alter_table +web2py.gluon.dal.BaseAdapter.represent_exceptions web2py.gluon.dal.BaseAdapter-class.html#represent_exceptions +web2py.gluon.dal.FireBirdAdapter.sequence_name web2py.gluon.dal.FireBirdAdapter-class.html#sequence_name +web2py.gluon.dal.BaseAdapter.ADD web2py.gluon.dal.BaseAdapter-class.html#ADD +web2py.gluon.dal.BaseAdapter.count web2py.gluon.dal.BaseAdapter-class.html#count +web2py.gluon.dal.BaseAdapter.UPPER web2py.gluon.dal.BaseAdapter-class.html#UPPER +web2py.gluon.dal.BaseAdapter.JOIN web2py.gluon.dal.BaseAdapter-class.html#JOIN +web2py.gluon.dal.BaseAdapter.LIKE web2py.gluon.dal.BaseAdapter-class.html#LIKE +web2py.gluon.dal.ConnectionPool.close_all_instances web2py.gluon.dal.ConnectionPool-class.html#close_all_instances +web2py.gluon.dal.BaseAdapter.file_exists web2py.gluon.dal.BaseAdapter-class.html#file_exists +web2py.gluon.dal.BaseAdapter.delete web2py.gluon.dal.BaseAdapter-class.html#delete +web2py.gluon.dal.BaseAdapter.LOWER web2py.gluon.dal.BaseAdapter-class.html#LOWER +web2py.gluon.dal.BaseAdapter._update web2py.gluon.dal.BaseAdapter-class.html#_update +web2py.gluon.dal.BaseAdapter.parse web2py.gluon.dal.BaseAdapter-class.html#parse +web2py.gluon.dal.BaseAdapter.ENDSWITH web2py.gluon.dal.BaseAdapter-class.html#ENDSWITH +web2py.gluon.dal.BaseAdapter.constraint_name web2py.gluon.dal.BaseAdapter-class.html#constraint_name +web2py.gluon.dal.BaseAdapter.DIV web2py.gluon.dal.BaseAdapter-class.html#DIV +web2py.gluon.dal.FireBirdAdapter.__init__ web2py.gluon.dal.FireBirdAdapter-class.html#__init__ +web2py.gluon.dal.BaseAdapter.filter_tenant web2py.gluon.dal.BaseAdapter-class.html#filter_tenant +web2py.gluon.dal.BaseAdapter.LE web2py.gluon.dal.BaseAdapter-class.html#LE +web2py.gluon.dal.BaseAdapter.file_open web2py.gluon.dal.BaseAdapter-class.html#file_open +web2py.gluon.dal.ConnectionPool.set_folder web2py.gluon.dal.ConnectionPool-class.html#set_folder +web2py.gluon.dal.BaseAdapter.LT web2py.gluon.dal.BaseAdapter-class.html#LT +web2py.gluon.dal.BaseAdapter.COMMA web2py.gluon.dal.BaseAdapter-class.html#COMMA +web2py.gluon.dal.BaseAdapter.create_table web2py.gluon.dal.BaseAdapter-class.html#create_table +web2py.gluon.dal.BaseAdapter.bulk_insert web2py.gluon.dal.BaseAdapter-class.html#bulk_insert +web2py.gluon.dal.BaseAdapter.AGGREGATE web2py.gluon.dal.BaseAdapter-class.html#AGGREGATE +web2py.gluon.dal.BaseAdapter.file_close web2py.gluon.dal.BaseAdapter-class.html#file_close +web2py.gluon.dal.FireBirdAdapter.RANDOM web2py.gluon.dal.FireBirdAdapter-class.html#RANDOM +web2py.gluon.dal.BaseAdapter.truncate web2py.gluon.dal.BaseAdapter-class.html#truncate +web2py.gluon.dal.FireBirdAdapter.driver web2py.gluon.dal.FireBirdAdapter-class.html#driver +web2py.gluon.dal.BaseAdapter.update web2py.gluon.dal.BaseAdapter-class.html#update +web2py.gluon.dal.BaseAdapter.AS web2py.gluon.dal.BaseAdapter-class.html#AS +web2py.gluon.dal.FireBirdAdapter.trigger_name web2py.gluon.dal.FireBirdAdapter-class.html#trigger_name +web2py.gluon.dal.FireBirdAdapter.types web2py.gluon.dal.FireBirdAdapter-class.html#types +web2py.gluon.dal.BaseAdapter._count web2py.gluon.dal.BaseAdapter-class.html#_count +web2py.gluon.dal.BaseAdapter.CONTAINS web2py.gluon.dal.BaseAdapter-class.html#CONTAINS +web2py.gluon.dal.BaseAdapter.INVERT web2py.gluon.dal.BaseAdapter-class.html#INVERT +web2py.gluon.dal.BaseAdapter.alias web2py.gluon.dal.BaseAdapter-class.html#alias +web2py.gluon.dal.FireBirdAdapter.lastrowid web2py.gluon.dal.FireBirdAdapter-class.html#lastrowid +web2py.gluon.dal.FireBirdEmbeddedAdapter web2py.gluon.dal.FireBirdEmbeddedAdapter-class.html +web2py.gluon.dal.BaseAdapter.represent web2py.gluon.dal.BaseAdapter-class.html#represent +web2py.gluon.dal.BaseAdapter.log_execute web2py.gluon.dal.BaseAdapter-class.html#log_execute +web2py.gluon.dal.FireBirdAdapter.select_limitby web2py.gluon.dal.FireBirdAdapter-class.html#select_limitby +web2py.gluon.dal.FireBirdAdapter._truncate web2py.gluon.dal.FireBirdAdapter-class.html#_truncate +web2py.gluon.dal.BaseAdapter.tables web2py.gluon.dal.BaseAdapter-class.html#tables +web2py.gluon.dal.BaseAdapter.LEFT_JOIN web2py.gluon.dal.BaseAdapter-class.html#LEFT_JOIN +web2py.gluon.dal.BaseAdapter.distributed_transaction_begin web2py.gluon.dal.BaseAdapter-class.html#distributed_transaction_begin +web2py.gluon.dal.BaseAdapter.GT web2py.gluon.dal.BaseAdapter-class.html#GT +web2py.gluon.dal.BaseAdapter.rollback web2py.gluon.dal.BaseAdapter-class.html#rollback +web2py.gluon.dal.BaseAdapter.ON web2py.gluon.dal.BaseAdapter-class.html#ON +web2py.gluon.dal.BaseAdapter.ALLOW_NULL web2py.gluon.dal.BaseAdapter-class.html#ALLOW_NULL +web2py.gluon.dal.BaseAdapter.maxcharlength web2py.gluon.dal.BaseAdapter-class.html#maxcharlength +web2py.gluon.dal.BaseAdapter.GE web2py.gluon.dal.BaseAdapter-class.html#GE +web2py.gluon.dal.BaseAdapter.get_table web2py.gluon.dal.BaseAdapter-class.html#get_table +web2py.gluon.dal.FireBirdAdapter._drop web2py.gluon.dal.FireBirdAdapter-class.html#_drop +web2py.gluon.dal.BaseAdapter.integrity_error_class web2py.gluon.dal.BaseAdapter-class.html#integrity_error_class +web2py.gluon.dal.ConnectionPool.pool_connection web2py.gluon.dal.ConnectionPool-class.html#pool_connection +web2py.gluon.dal.BaseAdapter._select web2py.gluon.dal.BaseAdapter-class.html#_select +web2py.gluon.dal.BaseAdapter.insert web2py.gluon.dal.BaseAdapter-class.html#insert +web2py.gluon.dal.BaseAdapter.execute web2py.gluon.dal.BaseAdapter-class.html#execute +web2py.gluon.dal.BaseAdapter.drop web2py.gluon.dal.BaseAdapter-class.html#drop +web2py.gluon.dal.BaseAdapter.migrate_table web2py.gluon.dal.BaseAdapter-class.html#migrate_table +web2py.gluon.dal.BaseAdapter.concat_add web2py.gluon.dal.BaseAdapter-class.html#concat_add +web2py.gluon.dal.BaseAdapter._insert web2py.gluon.dal.BaseAdapter-class.html#_insert +web2py.gluon.dal.ConnectionPool.pools web2py.gluon.dal.ConnectionPool-class.html#pools +web2py.gluon.dal.FireBirdAdapter.support_distributed_transaction web2py.gluon.dal.FireBirdAdapter-class.html#support_distributed_transaction +web2py.gluon.dal.BaseAdapter.OR web2py.gluon.dal.BaseAdapter-class.html#OR +web2py.gluon.dal.BaseAdapter.file_delete web2py.gluon.dal.BaseAdapter-class.html#file_delete +web2py.gluon.dal.FireBirdAdapter.NOT_NULL web2py.gluon.dal.FireBirdAdapter-class.html#NOT_NULL +web2py.gluon.dal.ConnectionPool.find_or_make_work_folder web2py.gluon.dal.ConnectionPool-class.html#find_or_make_work_folder +web2py.gluon.dal.BaseAdapter.close web2py.gluon.dal.BaseAdapter-class.html#close +web2py.gluon.dal.BaseAdapter.integrity_error web2py.gluon.dal.BaseAdapter-class.html#integrity_error +web2py.gluon.dal.BaseAdapter.rowslice web2py.gluon.dal.BaseAdapter-class.html#rowslice +web2py.gluon.dal.FireBirdAdapter.create_sequence_and_triggers web2py.gluon.dal.FireBirdAdapter-class.html#create_sequence_and_triggers +web2py.gluon.dal.BaseAdapter.rollback_prepared web2py.gluon.dal.BaseAdapter-class.html#rollback_prepared +web2py.gluon.dal.BaseAdapter.commit_prepared web2py.gluon.dal.BaseAdapter-class.html#commit_prepared +web2py.gluon.dal.BaseAdapter.EQ web2py.gluon.dal.BaseAdapter-class.html#EQ +web2py.gluon.dal.BaseAdapter.AND web2py.gluon.dal.BaseAdapter-class.html#AND +web2py.gluon.dal.BaseAdapter.uploads_in_blob web2py.gluon.dal.BaseAdapter-class.html#uploads_in_blob +web2py.gluon.dal.BaseAdapter.NOT web2py.gluon.dal.BaseAdapter-class.html#NOT +web2py.gluon.dal.BaseAdapter.commit web2py.gluon.dal.BaseAdapter-class.html#commit +web2py.gluon.dal.BaseAdapter.MOD web2py.gluon.dal.BaseAdapter-class.html#MOD +web2py.gluon.dal.BaseAdapter.STARTSWITH web2py.gluon.dal.BaseAdapter-class.html#STARTSWITH +web2py.gluon.dal.BaseAdapter.SUB web2py.gluon.dal.BaseAdapter-class.html#SUB +web2py.gluon.dal.BaseAdapter.COALESCE_ZERO web2py.gluon.dal.BaseAdapter-class.html#COALESCE_ZERO +web2py.gluon.dal.BaseAdapter._delete web2py.gluon.dal.BaseAdapter-class.html#_delete +web2py.gluon.dal.BaseAdapter.BELONGS web2py.gluon.dal.BaseAdapter-class.html#BELONGS +web2py.gluon.dal.BaseAdapter.MUL web2py.gluon.dal.BaseAdapter-class.html#MUL +web2py.gluon.dal.BaseAdapter.select web2py.gluon.dal.BaseAdapter-class.html#select +web2py.gluon.dal.BaseAdapter.PRIMARY_KEY web2py.gluon.dal.BaseAdapter-class.html#PRIMARY_KEY +web2py.gluon.dal.BaseAdapter.prepare web2py.gluon.dal.BaseAdapter-class.html#prepare +web2py.gluon.dal.BaseAdapter.NE web2py.gluon.dal.BaseAdapter-class.html#NE +web2py.gluon.dal.FireBirdAdapter.SUBSTRING web2py.gluon.dal.FireBirdAdapter-class.html#SUBSTRING +web2py.gluon.dal.BaseAdapter.expand web2py.gluon.dal.BaseAdapter-class.html#expand +web2py.gluon.dal.BaseAdapter.EXTRACT web2py.gluon.dal.BaseAdapter-class.html#EXTRACT +web2py.gluon.dal.FireBirdAdapter.commit_on_alter_table web2py.gluon.dal.FireBirdAdapter-class.html#commit_on_alter_table +web2py.gluon.dal.BaseAdapter.represent_exceptions web2py.gluon.dal.BaseAdapter-class.html#represent_exceptions +web2py.gluon.dal.FireBirdAdapter.sequence_name web2py.gluon.dal.FireBirdAdapter-class.html#sequence_name +web2py.gluon.dal.BaseAdapter.ADD web2py.gluon.dal.BaseAdapter-class.html#ADD +web2py.gluon.dal.BaseAdapter.count web2py.gluon.dal.BaseAdapter-class.html#count +web2py.gluon.dal.BaseAdapter.UPPER web2py.gluon.dal.BaseAdapter-class.html#UPPER +web2py.gluon.dal.BaseAdapter.JOIN web2py.gluon.dal.BaseAdapter-class.html#JOIN +web2py.gluon.dal.BaseAdapter.LIKE web2py.gluon.dal.BaseAdapter-class.html#LIKE +web2py.gluon.dal.ConnectionPool.close_all_instances web2py.gluon.dal.ConnectionPool-class.html#close_all_instances +web2py.gluon.dal.BaseAdapter.file_exists web2py.gluon.dal.BaseAdapter-class.html#file_exists +web2py.gluon.dal.BaseAdapter.delete web2py.gluon.dal.BaseAdapter-class.html#delete +web2py.gluon.dal.BaseAdapter.LOWER web2py.gluon.dal.BaseAdapter-class.html#LOWER +web2py.gluon.dal.BaseAdapter._update web2py.gluon.dal.BaseAdapter-class.html#_update +web2py.gluon.dal.BaseAdapter.parse web2py.gluon.dal.BaseAdapter-class.html#parse +web2py.gluon.dal.BaseAdapter.ENDSWITH web2py.gluon.dal.BaseAdapter-class.html#ENDSWITH +web2py.gluon.dal.BaseAdapter.constraint_name web2py.gluon.dal.BaseAdapter-class.html#constraint_name +web2py.gluon.dal.BaseAdapter.DIV web2py.gluon.dal.BaseAdapter-class.html#DIV +web2py.gluon.dal.FireBirdEmbeddedAdapter.__init__ web2py.gluon.dal.FireBirdEmbeddedAdapter-class.html#__init__ +web2py.gluon.dal.BaseAdapter.filter_tenant web2py.gluon.dal.BaseAdapter-class.html#filter_tenant +web2py.gluon.dal.BaseAdapter.LE web2py.gluon.dal.BaseAdapter-class.html#LE +web2py.gluon.dal.BaseAdapter.file_open web2py.gluon.dal.BaseAdapter-class.html#file_open +web2py.gluon.dal.ConnectionPool.set_folder web2py.gluon.dal.ConnectionPool-class.html#set_folder +web2py.gluon.dal.BaseAdapter.LT web2py.gluon.dal.BaseAdapter-class.html#LT +web2py.gluon.dal.BaseAdapter.COMMA web2py.gluon.dal.BaseAdapter-class.html#COMMA +web2py.gluon.dal.BaseAdapter.create_table web2py.gluon.dal.BaseAdapter-class.html#create_table +web2py.gluon.dal.BaseAdapter.bulk_insert web2py.gluon.dal.BaseAdapter-class.html#bulk_insert +web2py.gluon.dal.BaseAdapter.AGGREGATE web2py.gluon.dal.BaseAdapter-class.html#AGGREGATE +web2py.gluon.dal.BaseAdapter.file_close web2py.gluon.dal.BaseAdapter-class.html#file_close +web2py.gluon.dal.FireBirdAdapter.RANDOM web2py.gluon.dal.FireBirdAdapter-class.html#RANDOM +web2py.gluon.dal.BaseAdapter.truncate web2py.gluon.dal.BaseAdapter-class.html#truncate +web2py.gluon.dal.FireBirdAdapter.driver web2py.gluon.dal.FireBirdAdapter-class.html#driver +web2py.gluon.dal.BaseAdapter.update web2py.gluon.dal.BaseAdapter-class.html#update +web2py.gluon.dal.BaseAdapter.AS web2py.gluon.dal.BaseAdapter-class.html#AS +web2py.gluon.dal.FireBirdAdapter.trigger_name web2py.gluon.dal.FireBirdAdapter-class.html#trigger_name +web2py.gluon.dal.FireBirdAdapter.types web2py.gluon.dal.FireBirdAdapter-class.html#types +web2py.gluon.dal.BaseAdapter._count web2py.gluon.dal.BaseAdapter-class.html#_count +web2py.gluon.dal.BaseAdapter.CONTAINS web2py.gluon.dal.BaseAdapter-class.html#CONTAINS +web2py.gluon.dal.BaseAdapter.INVERT web2py.gluon.dal.BaseAdapter-class.html#INVERT +web2py.gluon.dal.BaseAdapter.alias web2py.gluon.dal.BaseAdapter-class.html#alias +web2py.gluon.dal.FireBirdAdapter.lastrowid web2py.gluon.dal.FireBirdAdapter-class.html#lastrowid +web2py.gluon.dal.GAEDecimalProperty web2py.gluon.dal.GAEDecimalProperty-class.html +web2py.gluon.dal.GAEDecimalProperty.make_value_from_datastore web2py.gluon.dal.GAEDecimalProperty-class.html#make_value_from_datastore +web2py.gluon.dal.GAEDecimalProperty.validate web2py.gluon.dal.GAEDecimalProperty-class.html#validate +web2py.gluon.dal.GAEDecimalProperty.get_value_for_datastore web2py.gluon.dal.GAEDecimalProperty-class.html#get_value_for_datastore +web2py.gluon.dal.GAEDecimalProperty.__init__ web2py.gluon.dal.GAEDecimalProperty-class.html#__init__ +web2py.gluon.dal.GAEDecimalProperty.data_type web2py.gluon.dal.GAEDecimalProperty-class.html#data_type +web2py.gluon.dal.GAEF web2py.gluon.dal.GAEF-class.html +web2py.gluon.dal.GAEF.__repr__ web2py.gluon.dal.GAEF-class.html#__repr__ +web2py.gluon.dal.GAEF.__init__ web2py.gluon.dal.GAEF-class.html#__init__ +web2py.gluon.dal.GoogleDatastoreAdapter web2py.gluon.dal.GoogleDatastoreAdapter-class.html +web2py.gluon.dal.NoSQLAdapter.represent web2py.gluon.dal.NoSQLAdapter-class.html#represent +web2py.gluon.dal.NoSQLAdapter.log_execute web2py.gluon.dal.NoSQLAdapter-class.html#log_execute +web2py.gluon.dal.BaseAdapter.select_limitby web2py.gluon.dal.BaseAdapter-class.html#select_limitby +web2py.gluon.dal.BaseAdapter._truncate web2py.gluon.dal.BaseAdapter-class.html#_truncate +web2py.gluon.dal.NoSQLAdapter.SUB web2py.gluon.dal.NoSQLAdapter-class.html#SUB +web2py.gluon.dal.BaseAdapter.tables web2py.gluon.dal.BaseAdapter-class.html#tables +web2py.gluon.dal.NoSQLAdapter.LEFT_JOIN web2py.gluon.dal.NoSQLAdapter-class.html#LEFT_JOIN +web2py.gluon.dal.NoSQLAdapter.distributed_transaction_begin web2py.gluon.dal.NoSQLAdapter-class.html#distributed_transaction_begin +web2py.gluon.dal.GoogleDatastoreAdapter.GT web2py.gluon.dal.GoogleDatastoreAdapter-class.html#GT +web2py.gluon.dal.NoSQLAdapter.rollback web2py.gluon.dal.NoSQLAdapter-class.html#rollback +web2py.gluon.dal.NoSQLAdapter.ON web2py.gluon.dal.NoSQLAdapter-class.html#ON +web2py.gluon.dal.BaseAdapter.ALLOW_NULL web2py.gluon.dal.BaseAdapter-class.html#ALLOW_NULL +web2py.gluon.dal.BaseAdapter.maxcharlength web2py.gluon.dal.BaseAdapter-class.html#maxcharlength +web2py.gluon.dal.GoogleDatastoreAdapter.GE web2py.gluon.dal.GoogleDatastoreAdapter-class.html#GE +web2py.gluon.dal.BaseAdapter.get_table web2py.gluon.dal.BaseAdapter-class.html#get_table +web2py.gluon.dal.BaseAdapter._drop web2py.gluon.dal.BaseAdapter-class.html#_drop +web2py.gluon.dal.NoSQLAdapter.integrity_error_class web2py.gluon.dal.NoSQLAdapter-class.html#integrity_error_class +web2py.gluon.dal.ConnectionPool.pool_connection web2py.gluon.dal.ConnectionPool-class.html#pool_connection +web2py.gluon.dal.NoSQLAdapter._select web2py.gluon.dal.NoSQLAdapter-class.html#_select +web2py.gluon.dal.GoogleDatastoreAdapter.insert web2py.gluon.dal.GoogleDatastoreAdapter-class.html#insert +web2py.gluon.dal.NoSQLAdapter.execute web2py.gluon.dal.NoSQLAdapter-class.html#execute +web2py.gluon.dal.NoSQLAdapter.drop web2py.gluon.dal.NoSQLAdapter-class.html#drop +web2py.gluon.dal.NoSQLAdapter.migrate_table web2py.gluon.dal.NoSQLAdapter-class.html#migrate_table +web2py.gluon.dal.NoSQLAdapter.concat_add web2py.gluon.dal.NoSQLAdapter-class.html#concat_add +web2py.gluon.dal.NoSQLAdapter._insert web2py.gluon.dal.NoSQLAdapter-class.html#_insert +web2py.gluon.dal.ConnectionPool.pools web2py.gluon.dal.ConnectionPool-class.html#pools +web2py.gluon.dal.BaseAdapter.support_distributed_transaction web2py.gluon.dal.BaseAdapter-class.html#support_distributed_transaction +web2py.gluon.dal.NoSQLAdapter.OR web2py.gluon.dal.NoSQLAdapter-class.html#OR +web2py.gluon.dal.BaseAdapter.file_delete web2py.gluon.dal.BaseAdapter-class.html#file_delete +web2py.gluon.dal.BaseAdapter.NOT_NULL web2py.gluon.dal.BaseAdapter-class.html#NOT_NULL +web2py.gluon.dal.ConnectionPool.find_or_make_work_folder web2py.gluon.dal.ConnectionPool-class.html#find_or_make_work_folder +web2py.gluon.dal.NoSQLAdapter.close web2py.gluon.dal.NoSQLAdapter-class.html#close +web2py.gluon.dal.BaseAdapter.integrity_error web2py.gluon.dal.BaseAdapter-class.html#integrity_error +web2py.gluon.dal.NoSQLAdapter.rowslice web2py.gluon.dal.NoSQLAdapter-class.html#rowslice +web2py.gluon.dal.NoSQLAdapter.create_sequence_and_triggers web2py.gluon.dal.NoSQLAdapter-class.html#create_sequence_and_triggers +web2py.gluon.dal.NoSQLAdapter.rollback_prepared web2py.gluon.dal.NoSQLAdapter-class.html#rollback_prepared +web2py.gluon.dal.NoSQLAdapter.commit_prepared web2py.gluon.dal.NoSQLAdapter-class.html#commit_prepared +web2py.gluon.dal.GoogleDatastoreAdapter.EQ web2py.gluon.dal.GoogleDatastoreAdapter-class.html#EQ +web2py.gluon.dal.GoogleDatastoreAdapter.AND web2py.gluon.dal.GoogleDatastoreAdapter-class.html#AND +web2py.gluon.dal.GoogleDatastoreAdapter.uploads_in_blob web2py.gluon.dal.GoogleDatastoreAdapter-class.html#uploads_in_blob +web2py.gluon.dal.GoogleDatastoreAdapter.NOT web2py.gluon.dal.GoogleDatastoreAdapter-class.html#NOT +web2py.gluon.dal.NoSQLAdapter.commit web2py.gluon.dal.NoSQLAdapter-class.html#commit +web2py.gluon.dal.BaseAdapter.MOD web2py.gluon.dal.BaseAdapter-class.html#MOD +web2py.gluon.dal.NoSQLAdapter.STARTSWITH web2py.gluon.dal.NoSQLAdapter-class.html#STARTSWITH +web2py.gluon.dal.NoSQLAdapter.to_unicode web2py.gluon.dal.NoSQLAdapter-class.html#to_unicode +web2py.gluon.dal.BaseAdapter.COALESCE_ZERO web2py.gluon.dal.BaseAdapter-class.html#COALESCE_ZERO +web2py.gluon.dal.NoSQLAdapter._delete web2py.gluon.dal.NoSQLAdapter-class.html#_delete +web2py.gluon.dal.GoogleDatastoreAdapter.BELONGS web2py.gluon.dal.GoogleDatastoreAdapter-class.html#BELONGS +web2py.gluon.dal.NoSQLAdapter.MUL web2py.gluon.dal.NoSQLAdapter-class.html#MUL +web2py.gluon.dal.GoogleDatastoreAdapter.select web2py.gluon.dal.GoogleDatastoreAdapter-class.html#select +web2py.gluon.dal.NoSQLAdapter.PRIMARY_KEY web2py.gluon.dal.NoSQLAdapter-class.html#PRIMARY_KEY +web2py.gluon.dal.NoSQLAdapter.prepare web2py.gluon.dal.NoSQLAdapter-class.html#prepare +web2py.gluon.dal.GoogleDatastoreAdapter.NE web2py.gluon.dal.GoogleDatastoreAdapter-class.html#NE +web2py.gluon.dal.NoSQLAdapter.SUBSTRING web2py.gluon.dal.NoSQLAdapter-class.html#SUBSTRING +web2py.gluon.dal.GoogleDatastoreAdapter.types web2py.gluon.dal.GoogleDatastoreAdapter-class.html#types +web2py.gluon.dal.NoSQLAdapter.EXTRACT web2py.gluon.dal.NoSQLAdapter-class.html#EXTRACT +web2py.gluon.dal.BaseAdapter.commit_on_alter_table web2py.gluon.dal.BaseAdapter-class.html#commit_on_alter_table +web2py.gluon.dal.NoSQLAdapter.represent_exceptions web2py.gluon.dal.NoSQLAdapter-class.html#represent_exceptions +web2py.gluon.dal.BaseAdapter.sequence_name web2py.gluon.dal.BaseAdapter-class.html#sequence_name +web2py.gluon.dal.NoSQLAdapter.ADD web2py.gluon.dal.NoSQLAdapter-class.html#ADD +web2py.gluon.dal.GoogleDatastoreAdapter.count web2py.gluon.dal.GoogleDatastoreAdapter-class.html#count +web2py.gluon.dal.NoSQLAdapter.UPPER web2py.gluon.dal.NoSQLAdapter-class.html#UPPER +web2py.gluon.dal.BaseAdapter.JOIN web2py.gluon.dal.BaseAdapter-class.html#JOIN +web2py.gluon.dal.NoSQLAdapter.LIKE web2py.gluon.dal.NoSQLAdapter-class.html#LIKE +web2py.gluon.dal.ConnectionPool.close_all_instances web2py.gluon.dal.ConnectionPool-class.html#close_all_instances +web2py.gluon.dal.GoogleDatastoreAdapter.file_exists web2py.gluon.dal.GoogleDatastoreAdapter-class.html#file_exists +web2py.gluon.dal.GoogleDatastoreAdapter.delete web2py.gluon.dal.GoogleDatastoreAdapter-class.html#delete +web2py.gluon.dal.NoSQLAdapter.LOWER web2py.gluon.dal.NoSQLAdapter-class.html#LOWER +web2py.gluon.dal.NoSQLAdapter._update web2py.gluon.dal.NoSQLAdapter-class.html#_update +web2py.gluon.dal.BaseAdapter.parse web2py.gluon.dal.BaseAdapter-class.html#parse +web2py.gluon.dal.NoSQLAdapter.ENDSWITH web2py.gluon.dal.NoSQLAdapter-class.html#ENDSWITH +web2py.gluon.dal.NoSQLAdapter.constraint_name web2py.gluon.dal.NoSQLAdapter-class.html#constraint_name +web2py.gluon.dal.NoSQLAdapter.DIV web2py.gluon.dal.NoSQLAdapter-class.html#DIV +web2py.gluon.dal.GoogleDatastoreAdapter.__init__ web2py.gluon.dal.GoogleDatastoreAdapter-class.html#__init__ +web2py.gluon.dal.BaseAdapter.filter_tenant web2py.gluon.dal.BaseAdapter-class.html#filter_tenant +web2py.gluon.dal.GoogleDatastoreAdapter.LE web2py.gluon.dal.GoogleDatastoreAdapter-class.html#LE +web2py.gluon.dal.GoogleDatastoreAdapter.file_open web2py.gluon.dal.GoogleDatastoreAdapter-class.html#file_open +web2py.gluon.dal.ConnectionPool.set_folder web2py.gluon.dal.ConnectionPool-class.html#set_folder +web2py.gluon.dal.GoogleDatastoreAdapter.select_raw web2py.gluon.dal.GoogleDatastoreAdapter-class.html#select_raw +web2py.gluon.dal.GoogleDatastoreAdapter.LT web2py.gluon.dal.GoogleDatastoreAdapter-class.html#LT +web2py.gluon.dal.GoogleDatastoreAdapter.COMMA web2py.gluon.dal.GoogleDatastoreAdapter-class.html#COMMA +web2py.gluon.dal.GoogleDatastoreAdapter.create_table web2py.gluon.dal.GoogleDatastoreAdapter-class.html#create_table +web2py.gluon.dal.GoogleDatastoreAdapter.bulk_insert web2py.gluon.dal.GoogleDatastoreAdapter-class.html#bulk_insert +web2py.gluon.dal.NoSQLAdapter.AGGREGATE web2py.gluon.dal.NoSQLAdapter-class.html#AGGREGATE +web2py.gluon.dal.GoogleDatastoreAdapter.file_close web2py.gluon.dal.GoogleDatastoreAdapter-class.html#file_close +web2py.gluon.dal.NoSQLAdapter.RANDOM web2py.gluon.dal.NoSQLAdapter-class.html#RANDOM +web2py.gluon.dal.GoogleDatastoreAdapter.truncate web2py.gluon.dal.GoogleDatastoreAdapter-class.html#truncate +web2py.gluon.dal.BaseAdapter.driver web2py.gluon.dal.BaseAdapter-class.html#driver +web2py.gluon.dal.GoogleDatastoreAdapter.update web2py.gluon.dal.GoogleDatastoreAdapter-class.html#update +web2py.gluon.dal.NoSQLAdapter.AS web2py.gluon.dal.NoSQLAdapter-class.html#AS +web2py.gluon.dal.BaseAdapter.trigger_name web2py.gluon.dal.BaseAdapter-class.html#trigger_name +web2py.gluon.dal.GoogleDatastoreAdapter.expand web2py.gluon.dal.GoogleDatastoreAdapter-class.html#expand +web2py.gluon.dal.NoSQLAdapter._count web2py.gluon.dal.NoSQLAdapter-class.html#_count +web2py.gluon.dal.GoogleDatastoreAdapter.CONTAINS web2py.gluon.dal.GoogleDatastoreAdapter-class.html#CONTAINS +web2py.gluon.dal.GoogleDatastoreAdapter.INVERT web2py.gluon.dal.GoogleDatastoreAdapter-class.html#INVERT +web2py.gluon.dal.NoSQLAdapter.alias web2py.gluon.dal.NoSQLAdapter-class.html#alias +web2py.gluon.dal.NoSQLAdapter.lastrowid web2py.gluon.dal.NoSQLAdapter-class.html#lastrowid +web2py.gluon.dal.GoogleSQLAdapter web2py.gluon.dal.GoogleSQLAdapter-class.html +web2py.gluon.dal.BaseAdapter.represent web2py.gluon.dal.BaseAdapter-class.html#represent +web2py.gluon.dal.BaseAdapter.log_execute web2py.gluon.dal.BaseAdapter-class.html#log_execute +web2py.gluon.dal.BaseAdapter.select_limitby web2py.gluon.dal.BaseAdapter-class.html#select_limitby +web2py.gluon.dal.BaseAdapter._truncate web2py.gluon.dal.BaseAdapter-class.html#_truncate +web2py.gluon.dal.BaseAdapter.tables web2py.gluon.dal.BaseAdapter-class.html#tables +web2py.gluon.dal.BaseAdapter.LEFT_JOIN web2py.gluon.dal.BaseAdapter-class.html#LEFT_JOIN +web2py.gluon.dal.MySQLAdapter.distributed_transaction_begin web2py.gluon.dal.MySQLAdapter-class.html#distributed_transaction_begin +web2py.gluon.dal.BaseAdapter.GT web2py.gluon.dal.BaseAdapter-class.html#GT +web2py.gluon.dal.BaseAdapter.rollback web2py.gluon.dal.BaseAdapter-class.html#rollback +web2py.gluon.dal.BaseAdapter.ON web2py.gluon.dal.BaseAdapter-class.html#ON +web2py.gluon.dal.BaseAdapter.ALLOW_NULL web2py.gluon.dal.BaseAdapter-class.html#ALLOW_NULL +web2py.gluon.dal.MySQLAdapter.maxcharlength web2py.gluon.dal.MySQLAdapter-class.html#maxcharlength +web2py.gluon.dal.BaseAdapter.GE web2py.gluon.dal.BaseAdapter-class.html#GE +web2py.gluon.dal.BaseAdapter.get_table web2py.gluon.dal.BaseAdapter-class.html#get_table +web2py.gluon.dal.MySQLAdapter._drop web2py.gluon.dal.MySQLAdapter-class.html#_drop +web2py.gluon.dal.BaseAdapter.integrity_error_class web2py.gluon.dal.BaseAdapter-class.html#integrity_error_class +web2py.gluon.dal.ConnectionPool.pool_connection web2py.gluon.dal.ConnectionPool-class.html#pool_connection +web2py.gluon.dal.BaseAdapter._select web2py.gluon.dal.BaseAdapter-class.html#_select +web2py.gluon.dal.BaseAdapter.insert web2py.gluon.dal.BaseAdapter-class.html#insert +web2py.gluon.dal.BaseAdapter.execute web2py.gluon.dal.BaseAdapter-class.html#execute +web2py.gluon.dal.BaseAdapter.drop web2py.gluon.dal.BaseAdapter-class.html#drop +web2py.gluon.dal.BaseAdapter.migrate_table web2py.gluon.dal.BaseAdapter-class.html#migrate_table +web2py.gluon.dal.MySQLAdapter.concat_add web2py.gluon.dal.MySQLAdapter-class.html#concat_add +web2py.gluon.dal.BaseAdapter._insert web2py.gluon.dal.BaseAdapter-class.html#_insert +web2py.gluon.dal.ConnectionPool.pools web2py.gluon.dal.ConnectionPool-class.html#pools +web2py.gluon.dal.MySQLAdapter.support_distributed_transaction web2py.gluon.dal.MySQLAdapter-class.html#support_distributed_transaction +web2py.gluon.dal.BaseAdapter.OR web2py.gluon.dal.BaseAdapter-class.html#OR +web2py.gluon.dal.UseDatabaseStoredFile.file_delete web2py.gluon.dal.UseDatabaseStoredFile-class.html#file_delete +web2py.gluon.dal.BaseAdapter.NOT_NULL web2py.gluon.dal.BaseAdapter-class.html#NOT_NULL +web2py.gluon.dal.ConnectionPool.find_or_make_work_folder web2py.gluon.dal.ConnectionPool-class.html#find_or_make_work_folder +web2py.gluon.dal.BaseAdapter.close web2py.gluon.dal.BaseAdapter-class.html#close +web2py.gluon.dal.BaseAdapter.integrity_error web2py.gluon.dal.BaseAdapter-class.html#integrity_error +web2py.gluon.dal.BaseAdapter.rowslice web2py.gluon.dal.BaseAdapter-class.html#rowslice +web2py.gluon.dal.BaseAdapter.create_sequence_and_triggers web2py.gluon.dal.BaseAdapter-class.html#create_sequence_and_triggers +web2py.gluon.dal.MySQLAdapter.rollback_prepared web2py.gluon.dal.MySQLAdapter-class.html#rollback_prepared +web2py.gluon.dal.MySQLAdapter.commit_prepared web2py.gluon.dal.MySQLAdapter-class.html#commit_prepared +web2py.gluon.dal.BaseAdapter.EQ web2py.gluon.dal.BaseAdapter-class.html#EQ +web2py.gluon.dal.BaseAdapter.AND web2py.gluon.dal.BaseAdapter-class.html#AND +web2py.gluon.dal.BaseAdapter.uploads_in_blob web2py.gluon.dal.BaseAdapter-class.html#uploads_in_blob +web2py.gluon.dal.BaseAdapter.NOT web2py.gluon.dal.BaseAdapter-class.html#NOT +web2py.gluon.dal.BaseAdapter.commit web2py.gluon.dal.BaseAdapter-class.html#commit +web2py.gluon.dal.BaseAdapter.MOD web2py.gluon.dal.BaseAdapter-class.html#MOD +web2py.gluon.dal.BaseAdapter.STARTSWITH web2py.gluon.dal.BaseAdapter-class.html#STARTSWITH +web2py.gluon.dal.BaseAdapter.SUB web2py.gluon.dal.BaseAdapter-class.html#SUB +web2py.gluon.dal.BaseAdapter.COALESCE_ZERO web2py.gluon.dal.BaseAdapter-class.html#COALESCE_ZERO +web2py.gluon.dal.BaseAdapter._delete web2py.gluon.dal.BaseAdapter-class.html#_delete +web2py.gluon.dal.BaseAdapter.BELONGS web2py.gluon.dal.BaseAdapter-class.html#BELONGS +web2py.gluon.dal.BaseAdapter.MUL web2py.gluon.dal.BaseAdapter-class.html#MUL +web2py.gluon.dal.BaseAdapter.select web2py.gluon.dal.BaseAdapter-class.html#select +web2py.gluon.dal.BaseAdapter.PRIMARY_KEY web2py.gluon.dal.BaseAdapter-class.html#PRIMARY_KEY +web2py.gluon.dal.MySQLAdapter.prepare web2py.gluon.dal.MySQLAdapter-class.html#prepare +web2py.gluon.dal.BaseAdapter.NE web2py.gluon.dal.BaseAdapter-class.html#NE +web2py.gluon.dal.MySQLAdapter.SUBSTRING web2py.gluon.dal.MySQLAdapter-class.html#SUBSTRING +web2py.gluon.dal.BaseAdapter.expand web2py.gluon.dal.BaseAdapter-class.html#expand +web2py.gluon.dal.BaseAdapter.EXTRACT web2py.gluon.dal.BaseAdapter-class.html#EXTRACT +web2py.gluon.dal.MySQLAdapter.commit_on_alter_table web2py.gluon.dal.MySQLAdapter-class.html#commit_on_alter_table +web2py.gluon.dal.BaseAdapter.represent_exceptions web2py.gluon.dal.BaseAdapter-class.html#represent_exceptions +web2py.gluon.dal.BaseAdapter.sequence_name web2py.gluon.dal.BaseAdapter-class.html#sequence_name +web2py.gluon.dal.BaseAdapter.ADD web2py.gluon.dal.BaseAdapter-class.html#ADD +web2py.gluon.dal.BaseAdapter.count web2py.gluon.dal.BaseAdapter-class.html#count +web2py.gluon.dal.BaseAdapter.UPPER web2py.gluon.dal.BaseAdapter-class.html#UPPER +web2py.gluon.dal.BaseAdapter.JOIN web2py.gluon.dal.BaseAdapter-class.html#JOIN +web2py.gluon.dal.BaseAdapter.LIKE web2py.gluon.dal.BaseAdapter-class.html#LIKE +web2py.gluon.dal.ConnectionPool.close_all_instances web2py.gluon.dal.ConnectionPool-class.html#close_all_instances +web2py.gluon.dal.UseDatabaseStoredFile.file_exists web2py.gluon.dal.UseDatabaseStoredFile-class.html#file_exists +web2py.gluon.dal.BaseAdapter.delete web2py.gluon.dal.BaseAdapter-class.html#delete +web2py.gluon.dal.BaseAdapter.LOWER web2py.gluon.dal.BaseAdapter-class.html#LOWER +web2py.gluon.dal.BaseAdapter._update web2py.gluon.dal.BaseAdapter-class.html#_update +web2py.gluon.dal.BaseAdapter.parse web2py.gluon.dal.BaseAdapter-class.html#parse +web2py.gluon.dal.BaseAdapter.ENDSWITH web2py.gluon.dal.BaseAdapter-class.html#ENDSWITH +web2py.gluon.dal.BaseAdapter.constraint_name web2py.gluon.dal.BaseAdapter-class.html#constraint_name +web2py.gluon.dal.BaseAdapter.DIV web2py.gluon.dal.BaseAdapter-class.html#DIV +web2py.gluon.dal.GoogleSQLAdapter.__init__ web2py.gluon.dal.GoogleSQLAdapter-class.html#__init__ +web2py.gluon.dal.BaseAdapter.filter_tenant web2py.gluon.dal.BaseAdapter-class.html#filter_tenant +web2py.gluon.dal.BaseAdapter.LE web2py.gluon.dal.BaseAdapter-class.html#LE +web2py.gluon.dal.UseDatabaseStoredFile.file_open web2py.gluon.dal.UseDatabaseStoredFile-class.html#file_open +web2py.gluon.dal.ConnectionPool.set_folder web2py.gluon.dal.ConnectionPool-class.html#set_folder +web2py.gluon.dal.BaseAdapter.LT web2py.gluon.dal.BaseAdapter-class.html#LT +web2py.gluon.dal.BaseAdapter.COMMA web2py.gluon.dal.BaseAdapter-class.html#COMMA +web2py.gluon.dal.BaseAdapter.create_table web2py.gluon.dal.BaseAdapter-class.html#create_table +web2py.gluon.dal.BaseAdapter.bulk_insert web2py.gluon.dal.BaseAdapter-class.html#bulk_insert +web2py.gluon.dal.BaseAdapter.AGGREGATE web2py.gluon.dal.BaseAdapter-class.html#AGGREGATE +web2py.gluon.dal.UseDatabaseStoredFile.file_close web2py.gluon.dal.UseDatabaseStoredFile-class.html#file_close +web2py.gluon.dal.MySQLAdapter.RANDOM web2py.gluon.dal.MySQLAdapter-class.html#RANDOM +web2py.gluon.dal.BaseAdapter.truncate web2py.gluon.dal.BaseAdapter-class.html#truncate +web2py.gluon.dal.MySQLAdapter.driver web2py.gluon.contrib.pymysql-module.html +web2py.gluon.dal.BaseAdapter.update web2py.gluon.dal.BaseAdapter-class.html#update +web2py.gluon.dal.BaseAdapter.AS web2py.gluon.dal.BaseAdapter-class.html#AS +web2py.gluon.dal.BaseAdapter.trigger_name web2py.gluon.dal.BaseAdapter-class.html#trigger_name +web2py.gluon.dal.MySQLAdapter.types web2py.gluon.dal.MySQLAdapter-class.html#types +web2py.gluon.dal.BaseAdapter._count web2py.gluon.dal.BaseAdapter-class.html#_count +web2py.gluon.dal.BaseAdapter.CONTAINS web2py.gluon.dal.BaseAdapter-class.html#CONTAINS +web2py.gluon.dal.BaseAdapter.INVERT web2py.gluon.dal.BaseAdapter-class.html#INVERT +web2py.gluon.dal.BaseAdapter.alias web2py.gluon.dal.BaseAdapter-class.html#alias +web2py.gluon.dal.MySQLAdapter.lastrowid web2py.gluon.dal.MySQLAdapter-class.html#lastrowid +web2py.gluon.dal.InformixAdapter web2py.gluon.dal.InformixAdapter-class.html +web2py.gluon.dal.BaseAdapter.represent web2py.gluon.dal.BaseAdapter-class.html#represent +web2py.gluon.dal.BaseAdapter.log_execute web2py.gluon.dal.BaseAdapter-class.html#log_execute +web2py.gluon.dal.InformixAdapter.select_limitby web2py.gluon.dal.InformixAdapter-class.html#select_limitby +web2py.gluon.dal.BaseAdapter._truncate web2py.gluon.dal.BaseAdapter-class.html#_truncate +web2py.gluon.dal.BaseAdapter.tables web2py.gluon.dal.BaseAdapter-class.html#tables +web2py.gluon.dal.BaseAdapter.LEFT_JOIN web2py.gluon.dal.BaseAdapter-class.html#LEFT_JOIN +web2py.gluon.dal.BaseAdapter.distributed_transaction_begin web2py.gluon.dal.BaseAdapter-class.html#distributed_transaction_begin +web2py.gluon.dal.BaseAdapter.GT web2py.gluon.dal.BaseAdapter-class.html#GT +web2py.gluon.dal.BaseAdapter.rollback web2py.gluon.dal.BaseAdapter-class.html#rollback +web2py.gluon.dal.BaseAdapter.ON web2py.gluon.dal.BaseAdapter-class.html#ON +web2py.gluon.dal.BaseAdapter.ALLOW_NULL web2py.gluon.dal.BaseAdapter-class.html#ALLOW_NULL +web2py.gluon.dal.BaseAdapter.maxcharlength web2py.gluon.dal.BaseAdapter-class.html#maxcharlength +web2py.gluon.dal.BaseAdapter.GE web2py.gluon.dal.BaseAdapter-class.html#GE +web2py.gluon.dal.BaseAdapter.get_table web2py.gluon.dal.BaseAdapter-class.html#get_table +web2py.gluon.dal.BaseAdapter._drop web2py.gluon.dal.BaseAdapter-class.html#_drop +web2py.gluon.dal.InformixAdapter.integrity_error_class web2py.gluon.dal.InformixAdapter-class.html#integrity_error_class +web2py.gluon.dal.ConnectionPool.pool_connection web2py.gluon.dal.ConnectionPool-class.html#pool_connection +web2py.gluon.dal.BaseAdapter._select web2py.gluon.dal.BaseAdapter-class.html#_select +web2py.gluon.dal.BaseAdapter.insert web2py.gluon.dal.BaseAdapter-class.html#insert +web2py.gluon.dal.InformixAdapter.execute web2py.gluon.dal.InformixAdapter-class.html#execute +web2py.gluon.dal.BaseAdapter.drop web2py.gluon.dal.BaseAdapter-class.html#drop +web2py.gluon.dal.BaseAdapter.migrate_table web2py.gluon.dal.BaseAdapter-class.html#migrate_table +web2py.gluon.dal.BaseAdapter.concat_add web2py.gluon.dal.BaseAdapter-class.html#concat_add +web2py.gluon.dal.BaseAdapter._insert web2py.gluon.dal.BaseAdapter-class.html#_insert +web2py.gluon.dal.ConnectionPool.pools web2py.gluon.dal.ConnectionPool-class.html#pools +web2py.gluon.dal.BaseAdapter.support_distributed_transaction web2py.gluon.dal.BaseAdapter-class.html#support_distributed_transaction +web2py.gluon.dal.BaseAdapter.OR web2py.gluon.dal.BaseAdapter-class.html#OR +web2py.gluon.dal.BaseAdapter.file_delete web2py.gluon.dal.BaseAdapter-class.html#file_delete +web2py.gluon.dal.InformixAdapter.NOT_NULL web2py.gluon.dal.InformixAdapter-class.html#NOT_NULL +web2py.gluon.dal.ConnectionPool.find_or_make_work_folder web2py.gluon.dal.ConnectionPool-class.html#find_or_make_work_folder +web2py.gluon.dal.BaseAdapter.close web2py.gluon.dal.BaseAdapter-class.html#close +web2py.gluon.dal.BaseAdapter.integrity_error web2py.gluon.dal.BaseAdapter-class.html#integrity_error +web2py.gluon.dal.BaseAdapter.rowslice web2py.gluon.dal.BaseAdapter-class.html#rowslice +web2py.gluon.dal.BaseAdapter.create_sequence_and_triggers web2py.gluon.dal.BaseAdapter-class.html#create_sequence_and_triggers +web2py.gluon.dal.BaseAdapter.rollback_prepared web2py.gluon.dal.BaseAdapter-class.html#rollback_prepared +web2py.gluon.dal.BaseAdapter.commit_prepared web2py.gluon.dal.BaseAdapter-class.html#commit_prepared +web2py.gluon.dal.BaseAdapter.EQ web2py.gluon.dal.BaseAdapter-class.html#EQ +web2py.gluon.dal.BaseAdapter.AND web2py.gluon.dal.BaseAdapter-class.html#AND +web2py.gluon.dal.BaseAdapter.uploads_in_blob web2py.gluon.dal.BaseAdapter-class.html#uploads_in_blob +web2py.gluon.dal.BaseAdapter.NOT web2py.gluon.dal.BaseAdapter-class.html#NOT +web2py.gluon.dal.BaseAdapter.commit web2py.gluon.dal.BaseAdapter-class.html#commit +web2py.gluon.dal.BaseAdapter.MOD web2py.gluon.dal.BaseAdapter-class.html#MOD +web2py.gluon.dal.BaseAdapter.STARTSWITH web2py.gluon.dal.BaseAdapter-class.html#STARTSWITH +web2py.gluon.dal.BaseAdapter.SUB web2py.gluon.dal.BaseAdapter-class.html#SUB +web2py.gluon.dal.BaseAdapter.COALESCE_ZERO web2py.gluon.dal.BaseAdapter-class.html#COALESCE_ZERO +web2py.gluon.dal.BaseAdapter._delete web2py.gluon.dal.BaseAdapter-class.html#_delete +web2py.gluon.dal.BaseAdapter.BELONGS web2py.gluon.dal.BaseAdapter-class.html#BELONGS +web2py.gluon.dal.BaseAdapter.MUL web2py.gluon.dal.BaseAdapter-class.html#MUL +web2py.gluon.dal.BaseAdapter.select web2py.gluon.dal.BaseAdapter-class.html#select +web2py.gluon.dal.BaseAdapter.PRIMARY_KEY web2py.gluon.dal.BaseAdapter-class.html#PRIMARY_KEY +web2py.gluon.dal.BaseAdapter.prepare web2py.gluon.dal.BaseAdapter-class.html#prepare +web2py.gluon.dal.BaseAdapter.NE web2py.gluon.dal.BaseAdapter-class.html#NE +web2py.gluon.dal.BaseAdapter.SUBSTRING web2py.gluon.dal.BaseAdapter-class.html#SUBSTRING +web2py.gluon.dal.InformixAdapter.types web2py.gluon.dal.InformixAdapter-class.html#types +web2py.gluon.dal.BaseAdapter.EXTRACT web2py.gluon.dal.BaseAdapter-class.html#EXTRACT +web2py.gluon.dal.BaseAdapter.commit_on_alter_table web2py.gluon.dal.BaseAdapter-class.html#commit_on_alter_table +web2py.gluon.dal.InformixAdapter.represent_exceptions web2py.gluon.dal.InformixAdapter-class.html#represent_exceptions +web2py.gluon.dal.BaseAdapter.sequence_name web2py.gluon.dal.BaseAdapter-class.html#sequence_name +web2py.gluon.dal.BaseAdapter.ADD web2py.gluon.dal.BaseAdapter-class.html#ADD +web2py.gluon.dal.BaseAdapter.count web2py.gluon.dal.BaseAdapter-class.html#count +web2py.gluon.dal.BaseAdapter.UPPER web2py.gluon.dal.BaseAdapter-class.html#UPPER +web2py.gluon.dal.BaseAdapter.JOIN web2py.gluon.dal.BaseAdapter-class.html#JOIN +web2py.gluon.dal.BaseAdapter.LIKE web2py.gluon.dal.BaseAdapter-class.html#LIKE +web2py.gluon.dal.ConnectionPool.close_all_instances web2py.gluon.dal.ConnectionPool-class.html#close_all_instances +web2py.gluon.dal.BaseAdapter.file_exists web2py.gluon.dal.BaseAdapter-class.html#file_exists +web2py.gluon.dal.BaseAdapter.delete web2py.gluon.dal.BaseAdapter-class.html#delete +web2py.gluon.dal.BaseAdapter.LOWER web2py.gluon.dal.BaseAdapter-class.html#LOWER +web2py.gluon.dal.BaseAdapter._update web2py.gluon.dal.BaseAdapter-class.html#_update +web2py.gluon.dal.BaseAdapter.parse web2py.gluon.dal.BaseAdapter-class.html#parse +web2py.gluon.dal.BaseAdapter.ENDSWITH web2py.gluon.dal.BaseAdapter-class.html#ENDSWITH +web2py.gluon.dal.BaseAdapter.constraint_name web2py.gluon.dal.BaseAdapter-class.html#constraint_name +web2py.gluon.dal.BaseAdapter.DIV web2py.gluon.dal.BaseAdapter-class.html#DIV +web2py.gluon.dal.InformixAdapter.__init__ web2py.gluon.dal.InformixAdapter-class.html#__init__ +web2py.gluon.dal.BaseAdapter.filter_tenant web2py.gluon.dal.BaseAdapter-class.html#filter_tenant +web2py.gluon.dal.BaseAdapter.LE web2py.gluon.dal.BaseAdapter-class.html#LE +web2py.gluon.dal.BaseAdapter.file_open web2py.gluon.dal.BaseAdapter-class.html#file_open +web2py.gluon.dal.ConnectionPool.set_folder web2py.gluon.dal.ConnectionPool-class.html#set_folder +web2py.gluon.dal.BaseAdapter.LT web2py.gluon.dal.BaseAdapter-class.html#LT +web2py.gluon.dal.BaseAdapter.COMMA web2py.gluon.dal.BaseAdapter-class.html#COMMA +web2py.gluon.dal.BaseAdapter.create_table web2py.gluon.dal.BaseAdapter-class.html#create_table +web2py.gluon.dal.BaseAdapter.bulk_insert web2py.gluon.dal.BaseAdapter-class.html#bulk_insert +web2py.gluon.dal.BaseAdapter.AGGREGATE web2py.gluon.dal.BaseAdapter-class.html#AGGREGATE +web2py.gluon.dal.BaseAdapter.file_close web2py.gluon.dal.BaseAdapter-class.html#file_close +web2py.gluon.dal.InformixAdapter.RANDOM web2py.gluon.dal.InformixAdapter-class.html#RANDOM +web2py.gluon.dal.BaseAdapter.truncate web2py.gluon.dal.BaseAdapter-class.html#truncate +web2py.gluon.dal.InformixAdapter.driver web2py.gluon.dal.InformixAdapter-class.html#driver +web2py.gluon.dal.BaseAdapter.update web2py.gluon.dal.BaseAdapter-class.html#update +web2py.gluon.dal.BaseAdapter.AS web2py.gluon.dal.BaseAdapter-class.html#AS +web2py.gluon.dal.BaseAdapter.trigger_name web2py.gluon.dal.BaseAdapter-class.html#trigger_name +web2py.gluon.dal.BaseAdapter.expand web2py.gluon.dal.BaseAdapter-class.html#expand +web2py.gluon.dal.BaseAdapter._count web2py.gluon.dal.BaseAdapter-class.html#_count +web2py.gluon.dal.BaseAdapter.CONTAINS web2py.gluon.dal.BaseAdapter-class.html#CONTAINS +web2py.gluon.dal.BaseAdapter.INVERT web2py.gluon.dal.BaseAdapter-class.html#INVERT +web2py.gluon.dal.BaseAdapter.alias web2py.gluon.dal.BaseAdapter-class.html#alias +web2py.gluon.dal.InformixAdapter.lastrowid web2py.gluon.dal.InformixAdapter-class.html#lastrowid +web2py.gluon.dal.IngresAdapter web2py.gluon.dal.IngresAdapter-class.html +web2py.gluon.dal.BaseAdapter.represent web2py.gluon.dal.BaseAdapter-class.html#represent +web2py.gluon.dal.BaseAdapter.log_execute web2py.gluon.dal.BaseAdapter-class.html#log_execute +web2py.gluon.dal.IngresAdapter.select_limitby web2py.gluon.dal.IngresAdapter-class.html#select_limitby +web2py.gluon.dal.BaseAdapter._truncate web2py.gluon.dal.BaseAdapter-class.html#_truncate +web2py.gluon.dal.BaseAdapter.tables web2py.gluon.dal.BaseAdapter-class.html#tables +web2py.gluon.dal.IngresAdapter.LEFT_JOIN web2py.gluon.dal.IngresAdapter-class.html#LEFT_JOIN +web2py.gluon.dal.BaseAdapter.distributed_transaction_begin web2py.gluon.dal.BaseAdapter-class.html#distributed_transaction_begin +web2py.gluon.dal.BaseAdapter.GT web2py.gluon.dal.BaseAdapter-class.html#GT +web2py.gluon.dal.BaseAdapter.rollback web2py.gluon.dal.BaseAdapter-class.html#rollback +web2py.gluon.dal.BaseAdapter.ON web2py.gluon.dal.BaseAdapter-class.html#ON +web2py.gluon.dal.IngresAdapter.RANDOM web2py.gluon.dal.IngresAdapter-class.html#RANDOM +web2py.gluon.dal.BaseAdapter.maxcharlength web2py.gluon.dal.BaseAdapter-class.html#maxcharlength +web2py.gluon.dal.BaseAdapter.GE web2py.gluon.dal.BaseAdapter-class.html#GE +web2py.gluon.dal.BaseAdapter.get_table web2py.gluon.dal.BaseAdapter-class.html#get_table +web2py.gluon.dal.BaseAdapter._drop web2py.gluon.dal.BaseAdapter-class.html#_drop +web2py.gluon.dal.IngresAdapter.integrity_error_class web2py.gluon.dal.IngresAdapter-class.html#integrity_error_class +web2py.gluon.dal.ConnectionPool.pool_connection web2py.gluon.dal.ConnectionPool-class.html#pool_connection +web2py.gluon.dal.BaseAdapter._select web2py.gluon.dal.BaseAdapter-class.html#_select +web2py.gluon.dal.BaseAdapter.insert web2py.gluon.dal.BaseAdapter-class.html#insert +web2py.gluon.dal.BaseAdapter.execute web2py.gluon.dal.BaseAdapter-class.html#execute +web2py.gluon.dal.BaseAdapter.drop web2py.gluon.dal.BaseAdapter-class.html#drop +web2py.gluon.dal.BaseAdapter.migrate_table web2py.gluon.dal.BaseAdapter-class.html#migrate_table +web2py.gluon.dal.BaseAdapter.concat_add web2py.gluon.dal.BaseAdapter-class.html#concat_add +web2py.gluon.dal.BaseAdapter._insert web2py.gluon.dal.BaseAdapter-class.html#_insert +web2py.gluon.dal.ConnectionPool.pools web2py.gluon.dal.ConnectionPool-class.html#pools +web2py.gluon.dal.BaseAdapter.support_distributed_transaction web2py.gluon.dal.BaseAdapter-class.html#support_distributed_transaction +web2py.gluon.dal.BaseAdapter.OR web2py.gluon.dal.BaseAdapter-class.html#OR +web2py.gluon.dal.BaseAdapter.file_delete web2py.gluon.dal.BaseAdapter-class.html#file_delete +web2py.gluon.dal.BaseAdapter.NOT_NULL web2py.gluon.dal.BaseAdapter-class.html#NOT_NULL +web2py.gluon.dal.ConnectionPool.find_or_make_work_folder web2py.gluon.dal.ConnectionPool-class.html#find_or_make_work_folder +web2py.gluon.dal.BaseAdapter.close web2py.gluon.dal.BaseAdapter-class.html#close +web2py.gluon.dal.BaseAdapter.integrity_error web2py.gluon.dal.BaseAdapter-class.html#integrity_error +web2py.gluon.dal.BaseAdapter.rowslice web2py.gluon.dal.BaseAdapter-class.html#rowslice +web2py.gluon.dal.IngresAdapter.create_sequence_and_triggers web2py.gluon.dal.IngresAdapter-class.html#create_sequence_and_triggers +web2py.gluon.dal.BaseAdapter.rollback_prepared web2py.gluon.dal.BaseAdapter-class.html#rollback_prepared +web2py.gluon.dal.BaseAdapter.commit_prepared web2py.gluon.dal.BaseAdapter-class.html#commit_prepared +web2py.gluon.dal.BaseAdapter.EQ web2py.gluon.dal.BaseAdapter-class.html#EQ +web2py.gluon.dal.BaseAdapter.AND web2py.gluon.dal.BaseAdapter-class.html#AND +web2py.gluon.dal.BaseAdapter.uploads_in_blob web2py.gluon.dal.BaseAdapter-class.html#uploads_in_blob +web2py.gluon.dal.BaseAdapter.NOT web2py.gluon.dal.BaseAdapter-class.html#NOT +web2py.gluon.dal.BaseAdapter.commit web2py.gluon.dal.BaseAdapter-class.html#commit +web2py.gluon.dal.BaseAdapter.MOD web2py.gluon.dal.BaseAdapter-class.html#MOD +web2py.gluon.dal.BaseAdapter.STARTSWITH web2py.gluon.dal.BaseAdapter-class.html#STARTSWITH +web2py.gluon.dal.BaseAdapter.SUB web2py.gluon.dal.BaseAdapter-class.html#SUB +web2py.gluon.dal.BaseAdapter.COALESCE_ZERO web2py.gluon.dal.BaseAdapter-class.html#COALESCE_ZERO +web2py.gluon.dal.BaseAdapter._delete web2py.gluon.dal.BaseAdapter-class.html#_delete +web2py.gluon.dal.BaseAdapter.BELONGS web2py.gluon.dal.BaseAdapter-class.html#BELONGS +web2py.gluon.dal.BaseAdapter.MUL web2py.gluon.dal.BaseAdapter-class.html#MUL +web2py.gluon.dal.BaseAdapter.select web2py.gluon.dal.BaseAdapter-class.html#select +web2py.gluon.dal.BaseAdapter.PRIMARY_KEY web2py.gluon.dal.BaseAdapter-class.html#PRIMARY_KEY +web2py.gluon.dal.BaseAdapter.prepare web2py.gluon.dal.BaseAdapter-class.html#prepare +web2py.gluon.dal.BaseAdapter.NE web2py.gluon.dal.BaseAdapter-class.html#NE +web2py.gluon.dal.BaseAdapter.SUBSTRING web2py.gluon.dal.BaseAdapter-class.html#SUBSTRING +web2py.gluon.dal.IngresAdapter.types web2py.gluon.dal.IngresAdapter-class.html#types +web2py.gluon.dal.BaseAdapter.EXTRACT web2py.gluon.dal.BaseAdapter-class.html#EXTRACT +web2py.gluon.dal.BaseAdapter.commit_on_alter_table web2py.gluon.dal.BaseAdapter-class.html#commit_on_alter_table +web2py.gluon.dal.BaseAdapter.represent_exceptions web2py.gluon.dal.BaseAdapter-class.html#represent_exceptions +web2py.gluon.dal.BaseAdapter.sequence_name web2py.gluon.dal.BaseAdapter-class.html#sequence_name +web2py.gluon.dal.BaseAdapter.ADD web2py.gluon.dal.BaseAdapter-class.html#ADD +web2py.gluon.dal.BaseAdapter.count web2py.gluon.dal.BaseAdapter-class.html#count +web2py.gluon.dal.BaseAdapter.UPPER web2py.gluon.dal.BaseAdapter-class.html#UPPER +web2py.gluon.dal.BaseAdapter.JOIN web2py.gluon.dal.BaseAdapter-class.html#JOIN +web2py.gluon.dal.BaseAdapter.LIKE web2py.gluon.dal.BaseAdapter-class.html#LIKE +web2py.gluon.dal.ConnectionPool.close_all_instances web2py.gluon.dal.ConnectionPool-class.html#close_all_instances +web2py.gluon.dal.BaseAdapter.file_exists web2py.gluon.dal.BaseAdapter-class.html#file_exists +web2py.gluon.dal.BaseAdapter.delete web2py.gluon.dal.BaseAdapter-class.html#delete +web2py.gluon.dal.BaseAdapter.LOWER web2py.gluon.dal.BaseAdapter-class.html#LOWER +web2py.gluon.dal.BaseAdapter._update web2py.gluon.dal.BaseAdapter-class.html#_update +web2py.gluon.dal.BaseAdapter.parse web2py.gluon.dal.BaseAdapter-class.html#parse +web2py.gluon.dal.BaseAdapter.ENDSWITH web2py.gluon.dal.BaseAdapter-class.html#ENDSWITH +web2py.gluon.dal.BaseAdapter.constraint_name web2py.gluon.dal.BaseAdapter-class.html#constraint_name +web2py.gluon.dal.BaseAdapter.DIV web2py.gluon.dal.BaseAdapter-class.html#DIV +web2py.gluon.dal.IngresAdapter.__init__ web2py.gluon.dal.IngresAdapter-class.html#__init__ +web2py.gluon.dal.BaseAdapter.filter_tenant web2py.gluon.dal.BaseAdapter-class.html#filter_tenant +web2py.gluon.dal.BaseAdapter.LE web2py.gluon.dal.BaseAdapter-class.html#LE +web2py.gluon.dal.BaseAdapter.file_open web2py.gluon.dal.BaseAdapter-class.html#file_open +web2py.gluon.dal.ConnectionPool.set_folder web2py.gluon.dal.ConnectionPool-class.html#set_folder +web2py.gluon.dal.BaseAdapter.LT web2py.gluon.dal.BaseAdapter-class.html#LT +web2py.gluon.dal.BaseAdapter.COMMA web2py.gluon.dal.BaseAdapter-class.html#COMMA +web2py.gluon.dal.BaseAdapter.create_table web2py.gluon.dal.BaseAdapter-class.html#create_table +web2py.gluon.dal.BaseAdapter.bulk_insert web2py.gluon.dal.BaseAdapter-class.html#bulk_insert +web2py.gluon.dal.BaseAdapter.AGGREGATE web2py.gluon.dal.BaseAdapter-class.html#AGGREGATE +web2py.gluon.dal.BaseAdapter.file_close web2py.gluon.dal.BaseAdapter-class.html#file_close +web2py.gluon.dal.BaseAdapter.ALLOW_NULL web2py.gluon.dal.BaseAdapter-class.html#ALLOW_NULL +web2py.gluon.dal.BaseAdapter.truncate web2py.gluon.dal.BaseAdapter-class.html#truncate +web2py.gluon.dal.IngresAdapter.driver web2py.gluon.dal.IngresAdapter-class.html#driver +web2py.gluon.dal.BaseAdapter.update web2py.gluon.dal.BaseAdapter-class.html#update +web2py.gluon.dal.BaseAdapter.AS web2py.gluon.dal.BaseAdapter-class.html#AS +web2py.gluon.dal.BaseAdapter.trigger_name web2py.gluon.dal.BaseAdapter-class.html#trigger_name +web2py.gluon.dal.BaseAdapter.expand web2py.gluon.dal.BaseAdapter-class.html#expand +web2py.gluon.dal.BaseAdapter._count web2py.gluon.dal.BaseAdapter-class.html#_count +web2py.gluon.dal.BaseAdapter.CONTAINS web2py.gluon.dal.BaseAdapter-class.html#CONTAINS +web2py.gluon.dal.BaseAdapter.INVERT web2py.gluon.dal.BaseAdapter-class.html#INVERT +web2py.gluon.dal.BaseAdapter.alias web2py.gluon.dal.BaseAdapter-class.html#alias +web2py.gluon.dal.IngresAdapter.lastrowid web2py.gluon.dal.IngresAdapter-class.html#lastrowid +web2py.gluon.dal.IngresUnicodeAdapter web2py.gluon.dal.IngresUnicodeAdapter-class.html +web2py.gluon.dal.BaseAdapter.represent web2py.gluon.dal.BaseAdapter-class.html#represent +web2py.gluon.dal.BaseAdapter.log_execute web2py.gluon.dal.BaseAdapter-class.html#log_execute +web2py.gluon.dal.IngresAdapter.select_limitby web2py.gluon.dal.IngresAdapter-class.html#select_limitby +web2py.gluon.dal.BaseAdapter._truncate web2py.gluon.dal.BaseAdapter-class.html#_truncate +web2py.gluon.dal.BaseAdapter.tables web2py.gluon.dal.BaseAdapter-class.html#tables +web2py.gluon.dal.IngresAdapter.LEFT_JOIN web2py.gluon.dal.IngresAdapter-class.html#LEFT_JOIN +web2py.gluon.dal.BaseAdapter.distributed_transaction_begin web2py.gluon.dal.BaseAdapter-class.html#distributed_transaction_begin +web2py.gluon.dal.BaseAdapter.GT web2py.gluon.dal.BaseAdapter-class.html#GT +web2py.gluon.dal.BaseAdapter.rollback web2py.gluon.dal.BaseAdapter-class.html#rollback +web2py.gluon.dal.BaseAdapter.ON web2py.gluon.dal.BaseAdapter-class.html#ON +web2py.gluon.dal.IngresAdapter.RANDOM web2py.gluon.dal.IngresAdapter-class.html#RANDOM +web2py.gluon.dal.BaseAdapter.maxcharlength web2py.gluon.dal.BaseAdapter-class.html#maxcharlength +web2py.gluon.dal.BaseAdapter.GE web2py.gluon.dal.BaseAdapter-class.html#GE +web2py.gluon.dal.BaseAdapter.get_table web2py.gluon.dal.BaseAdapter-class.html#get_table +web2py.gluon.dal.BaseAdapter._drop web2py.gluon.dal.BaseAdapter-class.html#_drop +web2py.gluon.dal.IngresAdapter.integrity_error_class web2py.gluon.dal.IngresAdapter-class.html#integrity_error_class +web2py.gluon.dal.ConnectionPool.pool_connection web2py.gluon.dal.ConnectionPool-class.html#pool_connection +web2py.gluon.dal.BaseAdapter._select web2py.gluon.dal.BaseAdapter-class.html#_select +web2py.gluon.dal.BaseAdapter.insert web2py.gluon.dal.BaseAdapter-class.html#insert +web2py.gluon.dal.BaseAdapter.execute web2py.gluon.dal.BaseAdapter-class.html#execute +web2py.gluon.dal.BaseAdapter.drop web2py.gluon.dal.BaseAdapter-class.html#drop +web2py.gluon.dal.BaseAdapter.migrate_table web2py.gluon.dal.BaseAdapter-class.html#migrate_table +web2py.gluon.dal.BaseAdapter.concat_add web2py.gluon.dal.BaseAdapter-class.html#concat_add +web2py.gluon.dal.BaseAdapter._insert web2py.gluon.dal.BaseAdapter-class.html#_insert +web2py.gluon.dal.ConnectionPool.pools web2py.gluon.dal.ConnectionPool-class.html#pools +web2py.gluon.dal.BaseAdapter.support_distributed_transaction web2py.gluon.dal.BaseAdapter-class.html#support_distributed_transaction +web2py.gluon.dal.BaseAdapter.OR web2py.gluon.dal.BaseAdapter-class.html#OR +web2py.gluon.dal.BaseAdapter.file_delete web2py.gluon.dal.BaseAdapter-class.html#file_delete +web2py.gluon.dal.BaseAdapter.NOT_NULL web2py.gluon.dal.BaseAdapter-class.html#NOT_NULL +web2py.gluon.dal.ConnectionPool.find_or_make_work_folder web2py.gluon.dal.ConnectionPool-class.html#find_or_make_work_folder +web2py.gluon.dal.BaseAdapter.close web2py.gluon.dal.BaseAdapter-class.html#close +web2py.gluon.dal.BaseAdapter.integrity_error web2py.gluon.dal.BaseAdapter-class.html#integrity_error +web2py.gluon.dal.BaseAdapter.rowslice web2py.gluon.dal.BaseAdapter-class.html#rowslice +web2py.gluon.dal.IngresAdapter.create_sequence_and_triggers web2py.gluon.dal.IngresAdapter-class.html#create_sequence_and_triggers +web2py.gluon.dal.BaseAdapter.rollback_prepared web2py.gluon.dal.BaseAdapter-class.html#rollback_prepared +web2py.gluon.dal.BaseAdapter.commit_prepared web2py.gluon.dal.BaseAdapter-class.html#commit_prepared +web2py.gluon.dal.BaseAdapter.EQ web2py.gluon.dal.BaseAdapter-class.html#EQ +web2py.gluon.dal.BaseAdapter.AND web2py.gluon.dal.BaseAdapter-class.html#AND +web2py.gluon.dal.BaseAdapter.uploads_in_blob web2py.gluon.dal.BaseAdapter-class.html#uploads_in_blob +web2py.gluon.dal.BaseAdapter.NOT web2py.gluon.dal.BaseAdapter-class.html#NOT +web2py.gluon.dal.BaseAdapter.commit web2py.gluon.dal.BaseAdapter-class.html#commit +web2py.gluon.dal.BaseAdapter.MOD web2py.gluon.dal.BaseAdapter-class.html#MOD +web2py.gluon.dal.BaseAdapter.STARTSWITH web2py.gluon.dal.BaseAdapter-class.html#STARTSWITH +web2py.gluon.dal.BaseAdapter.SUB web2py.gluon.dal.BaseAdapter-class.html#SUB +web2py.gluon.dal.BaseAdapter.COALESCE_ZERO web2py.gluon.dal.BaseAdapter-class.html#COALESCE_ZERO +web2py.gluon.dal.BaseAdapter._delete web2py.gluon.dal.BaseAdapter-class.html#_delete +web2py.gluon.dal.BaseAdapter.BELONGS web2py.gluon.dal.BaseAdapter-class.html#BELONGS +web2py.gluon.dal.BaseAdapter.MUL web2py.gluon.dal.BaseAdapter-class.html#MUL +web2py.gluon.dal.BaseAdapter.select web2py.gluon.dal.BaseAdapter-class.html#select +web2py.gluon.dal.BaseAdapter.PRIMARY_KEY web2py.gluon.dal.BaseAdapter-class.html#PRIMARY_KEY +web2py.gluon.dal.BaseAdapter.prepare web2py.gluon.dal.BaseAdapter-class.html#prepare +web2py.gluon.dal.BaseAdapter.NE web2py.gluon.dal.BaseAdapter-class.html#NE +web2py.gluon.dal.BaseAdapter.SUBSTRING web2py.gluon.dal.BaseAdapter-class.html#SUBSTRING +web2py.gluon.dal.IngresUnicodeAdapter.types web2py.gluon.dal.IngresUnicodeAdapter-class.html#types +web2py.gluon.dal.BaseAdapter.EXTRACT web2py.gluon.dal.BaseAdapter-class.html#EXTRACT +web2py.gluon.dal.BaseAdapter.commit_on_alter_table web2py.gluon.dal.BaseAdapter-class.html#commit_on_alter_table +web2py.gluon.dal.BaseAdapter.represent_exceptions web2py.gluon.dal.BaseAdapter-class.html#represent_exceptions +web2py.gluon.dal.BaseAdapter.sequence_name web2py.gluon.dal.BaseAdapter-class.html#sequence_name +web2py.gluon.dal.BaseAdapter.ADD web2py.gluon.dal.BaseAdapter-class.html#ADD +web2py.gluon.dal.BaseAdapter.count web2py.gluon.dal.BaseAdapter-class.html#count +web2py.gluon.dal.BaseAdapter.UPPER web2py.gluon.dal.BaseAdapter-class.html#UPPER +web2py.gluon.dal.BaseAdapter.JOIN web2py.gluon.dal.BaseAdapter-class.html#JOIN +web2py.gluon.dal.BaseAdapter.LIKE web2py.gluon.dal.BaseAdapter-class.html#LIKE +web2py.gluon.dal.ConnectionPool.close_all_instances web2py.gluon.dal.ConnectionPool-class.html#close_all_instances +web2py.gluon.dal.BaseAdapter.file_exists web2py.gluon.dal.BaseAdapter-class.html#file_exists +web2py.gluon.dal.BaseAdapter.delete web2py.gluon.dal.BaseAdapter-class.html#delete +web2py.gluon.dal.BaseAdapter.LOWER web2py.gluon.dal.BaseAdapter-class.html#LOWER +web2py.gluon.dal.BaseAdapter._update web2py.gluon.dal.BaseAdapter-class.html#_update +web2py.gluon.dal.BaseAdapter.parse web2py.gluon.dal.BaseAdapter-class.html#parse +web2py.gluon.dal.BaseAdapter.ENDSWITH web2py.gluon.dal.BaseAdapter-class.html#ENDSWITH +web2py.gluon.dal.BaseAdapter.constraint_name web2py.gluon.dal.BaseAdapter-class.html#constraint_name +web2py.gluon.dal.BaseAdapter.DIV web2py.gluon.dal.BaseAdapter-class.html#DIV +web2py.gluon.dal.IngresAdapter.__init__ web2py.gluon.dal.IngresAdapter-class.html#__init__ +web2py.gluon.dal.BaseAdapter.filter_tenant web2py.gluon.dal.BaseAdapter-class.html#filter_tenant +web2py.gluon.dal.BaseAdapter.LE web2py.gluon.dal.BaseAdapter-class.html#LE +web2py.gluon.dal.BaseAdapter.file_open web2py.gluon.dal.BaseAdapter-class.html#file_open +web2py.gluon.dal.ConnectionPool.set_folder web2py.gluon.dal.ConnectionPool-class.html#set_folder +web2py.gluon.dal.BaseAdapter.LT web2py.gluon.dal.BaseAdapter-class.html#LT +web2py.gluon.dal.BaseAdapter.COMMA web2py.gluon.dal.BaseAdapter-class.html#COMMA +web2py.gluon.dal.BaseAdapter.create_table web2py.gluon.dal.BaseAdapter-class.html#create_table +web2py.gluon.dal.BaseAdapter.bulk_insert web2py.gluon.dal.BaseAdapter-class.html#bulk_insert +web2py.gluon.dal.BaseAdapter.AGGREGATE web2py.gluon.dal.BaseAdapter-class.html#AGGREGATE +web2py.gluon.dal.BaseAdapter.file_close web2py.gluon.dal.BaseAdapter-class.html#file_close +web2py.gluon.dal.BaseAdapter.ALLOW_NULL web2py.gluon.dal.BaseAdapter-class.html#ALLOW_NULL +web2py.gluon.dal.BaseAdapter.truncate web2py.gluon.dal.BaseAdapter-class.html#truncate +web2py.gluon.dal.IngresAdapter.driver web2py.gluon.dal.IngresAdapter-class.html#driver +web2py.gluon.dal.BaseAdapter.update web2py.gluon.dal.BaseAdapter-class.html#update +web2py.gluon.dal.BaseAdapter.AS web2py.gluon.dal.BaseAdapter-class.html#AS +web2py.gluon.dal.BaseAdapter.trigger_name web2py.gluon.dal.BaseAdapter-class.html#trigger_name +web2py.gluon.dal.BaseAdapter.expand web2py.gluon.dal.BaseAdapter-class.html#expand +web2py.gluon.dal.BaseAdapter._count web2py.gluon.dal.BaseAdapter-class.html#_count +web2py.gluon.dal.BaseAdapter.CONTAINS web2py.gluon.dal.BaseAdapter-class.html#CONTAINS +web2py.gluon.dal.BaseAdapter.INVERT web2py.gluon.dal.BaseAdapter-class.html#INVERT +web2py.gluon.dal.BaseAdapter.alias web2py.gluon.dal.BaseAdapter-class.html#alias +web2py.gluon.dal.IngresAdapter.lastrowid web2py.gluon.dal.IngresAdapter-class.html#lastrowid +web2py.gluon.dal.JDBCPostgreSQLAdapter web2py.gluon.dal.JDBCPostgreSQLAdapter-class.html +web2py.gluon.dal.BaseAdapter.represent web2py.gluon.dal.BaseAdapter-class.html#represent +web2py.gluon.dal.BaseAdapter.log_execute web2py.gluon.dal.BaseAdapter-class.html#log_execute +web2py.gluon.dal.BaseAdapter.select_limitby web2py.gluon.dal.BaseAdapter-class.html#select_limitby +web2py.gluon.dal.BaseAdapter._truncate web2py.gluon.dal.BaseAdapter-class.html#_truncate +web2py.gluon.dal.BaseAdapter.tables web2py.gluon.dal.BaseAdapter-class.html#tables +web2py.gluon.dal.BaseAdapter.LEFT_JOIN web2py.gluon.dal.BaseAdapter-class.html#LEFT_JOIN +web2py.gluon.dal.PostgreSQLAdapter.distributed_transaction_begin web2py.gluon.dal.PostgreSQLAdapter-class.html#distributed_transaction_begin +web2py.gluon.dal.BaseAdapter.GT web2py.gluon.dal.BaseAdapter-class.html#GT +web2py.gluon.dal.BaseAdapter.rollback web2py.gluon.dal.BaseAdapter-class.html#rollback +web2py.gluon.dal.BaseAdapter.ON web2py.gluon.dal.BaseAdapter-class.html#ON +web2py.gluon.dal.BaseAdapter.ALLOW_NULL web2py.gluon.dal.BaseAdapter-class.html#ALLOW_NULL +web2py.gluon.dal.BaseAdapter.maxcharlength web2py.gluon.dal.BaseAdapter-class.html#maxcharlength +web2py.gluon.dal.BaseAdapter.GE web2py.gluon.dal.BaseAdapter-class.html#GE +web2py.gluon.dal.BaseAdapter.get_table web2py.gluon.dal.BaseAdapter-class.html#get_table +web2py.gluon.dal.BaseAdapter._drop web2py.gluon.dal.BaseAdapter-class.html#_drop +web2py.gluon.dal.BaseAdapter.integrity_error_class web2py.gluon.dal.BaseAdapter-class.html#integrity_error_class +web2py.gluon.dal.ConnectionPool.pool_connection web2py.gluon.dal.ConnectionPool-class.html#pool_connection +web2py.gluon.dal.BaseAdapter._select web2py.gluon.dal.BaseAdapter-class.html#_select +web2py.gluon.dal.BaseAdapter.insert web2py.gluon.dal.BaseAdapter-class.html#insert +web2py.gluon.dal.BaseAdapter.execute web2py.gluon.dal.BaseAdapter-class.html#execute +web2py.gluon.dal.BaseAdapter.drop web2py.gluon.dal.BaseAdapter-class.html#drop +web2py.gluon.dal.BaseAdapter.migrate_table web2py.gluon.dal.BaseAdapter-class.html#migrate_table +web2py.gluon.dal.BaseAdapter.concat_add web2py.gluon.dal.BaseAdapter-class.html#concat_add +web2py.gluon.dal.BaseAdapter._insert web2py.gluon.dal.BaseAdapter-class.html#_insert +web2py.gluon.dal.ConnectionPool.pools web2py.gluon.dal.ConnectionPool-class.html#pools +web2py.gluon.dal.PostgreSQLAdapter.support_distributed_transaction web2py.gluon.dal.PostgreSQLAdapter-class.html#support_distributed_transaction +web2py.gluon.dal.BaseAdapter.OR web2py.gluon.dal.BaseAdapter-class.html#OR +web2py.gluon.dal.BaseAdapter.file_delete web2py.gluon.dal.BaseAdapter-class.html#file_delete +web2py.gluon.dal.BaseAdapter.NOT_NULL web2py.gluon.dal.BaseAdapter-class.html#NOT_NULL +web2py.gluon.dal.ConnectionPool.find_or_make_work_folder web2py.gluon.dal.ConnectionPool-class.html#find_or_make_work_folder +web2py.gluon.dal.BaseAdapter.close web2py.gluon.dal.BaseAdapter-class.html#close +web2py.gluon.dal.BaseAdapter.integrity_error web2py.gluon.dal.BaseAdapter-class.html#integrity_error +web2py.gluon.dal.BaseAdapter.rowslice web2py.gluon.dal.BaseAdapter-class.html#rowslice +web2py.gluon.dal.PostgreSQLAdapter.create_sequence_and_triggers web2py.gluon.dal.PostgreSQLAdapter-class.html#create_sequence_and_triggers +web2py.gluon.dal.PostgreSQLAdapter.rollback_prepared web2py.gluon.dal.PostgreSQLAdapter-class.html#rollback_prepared +web2py.gluon.dal.PostgreSQLAdapter.commit_prepared web2py.gluon.dal.PostgreSQLAdapter-class.html#commit_prepared +web2py.gluon.dal.BaseAdapter.EQ web2py.gluon.dal.BaseAdapter-class.html#EQ +web2py.gluon.dal.BaseAdapter.AND web2py.gluon.dal.BaseAdapter-class.html#AND +web2py.gluon.dal.BaseAdapter.uploads_in_blob web2py.gluon.dal.BaseAdapter-class.html#uploads_in_blob +web2py.gluon.dal.BaseAdapter.NOT web2py.gluon.dal.BaseAdapter-class.html#NOT +web2py.gluon.dal.BaseAdapter.commit web2py.gluon.dal.BaseAdapter-class.html#commit +web2py.gluon.dal.BaseAdapter.MOD web2py.gluon.dal.BaseAdapter-class.html#MOD +web2py.gluon.dal.PostgreSQLAdapter.STARTSWITH web2py.gluon.dal.PostgreSQLAdapter-class.html#STARTSWITH +web2py.gluon.dal.BaseAdapter.SUB web2py.gluon.dal.BaseAdapter-class.html#SUB +web2py.gluon.dal.BaseAdapter.COALESCE_ZERO web2py.gluon.dal.BaseAdapter-class.html#COALESCE_ZERO +web2py.gluon.dal.BaseAdapter._delete web2py.gluon.dal.BaseAdapter-class.html#_delete +web2py.gluon.dal.BaseAdapter.BELONGS web2py.gluon.dal.BaseAdapter-class.html#BELONGS +web2py.gluon.dal.BaseAdapter.MUL web2py.gluon.dal.BaseAdapter-class.html#MUL +web2py.gluon.dal.BaseAdapter.select web2py.gluon.dal.BaseAdapter-class.html#select +web2py.gluon.dal.BaseAdapter.PRIMARY_KEY web2py.gluon.dal.BaseAdapter-class.html#PRIMARY_KEY +web2py.gluon.dal.PostgreSQLAdapter.prepare web2py.gluon.dal.PostgreSQLAdapter-class.html#prepare +web2py.gluon.dal.BaseAdapter.NE web2py.gluon.dal.BaseAdapter-class.html#NE +web2py.gluon.dal.BaseAdapter.SUBSTRING web2py.gluon.dal.BaseAdapter-class.html#SUBSTRING +web2py.gluon.dal.PostgreSQLAdapter.types web2py.gluon.dal.PostgreSQLAdapter-class.html#types +web2py.gluon.dal.BaseAdapter.EXTRACT web2py.gluon.dal.BaseAdapter-class.html#EXTRACT +web2py.gluon.dal.BaseAdapter.commit_on_alter_table web2py.gluon.dal.BaseAdapter-class.html#commit_on_alter_table +web2py.gluon.dal.BaseAdapter.represent_exceptions web2py.gluon.dal.BaseAdapter-class.html#represent_exceptions +web2py.gluon.dal.PostgreSQLAdapter.sequence_name web2py.gluon.dal.PostgreSQLAdapter-class.html#sequence_name +web2py.gluon.dal.BaseAdapter.ADD web2py.gluon.dal.BaseAdapter-class.html#ADD +web2py.gluon.dal.BaseAdapter.count web2py.gluon.dal.BaseAdapter-class.html#count +web2py.gluon.dal.BaseAdapter.UPPER web2py.gluon.dal.BaseAdapter-class.html#UPPER +web2py.gluon.dal.BaseAdapter.JOIN web2py.gluon.dal.BaseAdapter-class.html#JOIN +web2py.gluon.dal.PostgreSQLAdapter.LIKE web2py.gluon.dal.PostgreSQLAdapter-class.html#LIKE +web2py.gluon.dal.ConnectionPool.close_all_instances web2py.gluon.dal.ConnectionPool-class.html#close_all_instances +web2py.gluon.dal.BaseAdapter.file_exists web2py.gluon.dal.BaseAdapter-class.html#file_exists +web2py.gluon.dal.BaseAdapter.delete web2py.gluon.dal.BaseAdapter-class.html#delete +web2py.gluon.dal.BaseAdapter.LOWER web2py.gluon.dal.BaseAdapter-class.html#LOWER +web2py.gluon.dal.BaseAdapter._update web2py.gluon.dal.BaseAdapter-class.html#_update +web2py.gluon.dal.BaseAdapter.parse web2py.gluon.dal.BaseAdapter-class.html#parse +web2py.gluon.dal.PostgreSQLAdapter.ENDSWITH web2py.gluon.dal.PostgreSQLAdapter-class.html#ENDSWITH +web2py.gluon.dal.BaseAdapter.constraint_name web2py.gluon.dal.BaseAdapter-class.html#constraint_name +web2py.gluon.dal.BaseAdapter.DIV web2py.gluon.dal.BaseAdapter-class.html#DIV +web2py.gluon.dal.JDBCPostgreSQLAdapter.__init__ web2py.gluon.dal.JDBCPostgreSQLAdapter-class.html#__init__ +web2py.gluon.dal.BaseAdapter.filter_tenant web2py.gluon.dal.BaseAdapter-class.html#filter_tenant +web2py.gluon.dal.BaseAdapter.LE web2py.gluon.dal.BaseAdapter-class.html#LE +web2py.gluon.dal.BaseAdapter.file_open web2py.gluon.dal.BaseAdapter-class.html#file_open +web2py.gluon.dal.ConnectionPool.set_folder web2py.gluon.dal.ConnectionPool-class.html#set_folder +web2py.gluon.dal.BaseAdapter.LT web2py.gluon.dal.BaseAdapter-class.html#LT +web2py.gluon.dal.BaseAdapter.COMMA web2py.gluon.dal.BaseAdapter-class.html#COMMA +web2py.gluon.dal.BaseAdapter.create_table web2py.gluon.dal.BaseAdapter-class.html#create_table +web2py.gluon.dal.BaseAdapter.bulk_insert web2py.gluon.dal.BaseAdapter-class.html#bulk_insert +web2py.gluon.dal.BaseAdapter.AGGREGATE web2py.gluon.dal.BaseAdapter-class.html#AGGREGATE +web2py.gluon.dal.BaseAdapter.file_close web2py.gluon.dal.BaseAdapter-class.html#file_close +web2py.gluon.dal.PostgreSQLAdapter.RANDOM web2py.gluon.dal.PostgreSQLAdapter-class.html#RANDOM +web2py.gluon.dal.BaseAdapter.truncate web2py.gluon.dal.BaseAdapter-class.html#truncate +web2py.gluon.dal.PostgreSQLAdapter.driver psycopg2-module.html +web2py.gluon.dal.BaseAdapter.update web2py.gluon.dal.BaseAdapter-class.html#update +web2py.gluon.dal.BaseAdapter.AS web2py.gluon.dal.BaseAdapter-class.html#AS +web2py.gluon.dal.BaseAdapter.trigger_name web2py.gluon.dal.BaseAdapter-class.html#trigger_name +web2py.gluon.dal.BaseAdapter.expand web2py.gluon.dal.BaseAdapter-class.html#expand +web2py.gluon.dal.BaseAdapter._count web2py.gluon.dal.BaseAdapter-class.html#_count +web2py.gluon.dal.PostgreSQLAdapter.CONTAINS web2py.gluon.dal.PostgreSQLAdapter-class.html#CONTAINS +web2py.gluon.dal.BaseAdapter.INVERT web2py.gluon.dal.BaseAdapter-class.html#INVERT +web2py.gluon.dal.BaseAdapter.alias web2py.gluon.dal.BaseAdapter-class.html#alias +web2py.gluon.dal.PostgreSQLAdapter.lastrowid web2py.gluon.dal.PostgreSQLAdapter-class.html#lastrowid +web2py.gluon.dal.JDBCSQLiteAdapter web2py.gluon.dal.JDBCSQLiteAdapter-class.html +web2py.gluon.dal.BaseAdapter.represent web2py.gluon.dal.BaseAdapter-class.html#represent +web2py.gluon.dal.BaseAdapter.log_execute web2py.gluon.dal.BaseAdapter-class.html#log_execute +web2py.gluon.dal.BaseAdapter.select_limitby web2py.gluon.dal.BaseAdapter-class.html#select_limitby +web2py.gluon.dal.SQLiteAdapter._truncate web2py.gluon.dal.SQLiteAdapter-class.html#_truncate +web2py.gluon.dal.BaseAdapter.tables web2py.gluon.dal.BaseAdapter-class.html#tables +web2py.gluon.dal.BaseAdapter.LEFT_JOIN web2py.gluon.dal.BaseAdapter-class.html#LEFT_JOIN +web2py.gluon.dal.BaseAdapter.distributed_transaction_begin web2py.gluon.dal.BaseAdapter-class.html#distributed_transaction_begin +web2py.gluon.dal.BaseAdapter.GT web2py.gluon.dal.BaseAdapter-class.html#GT +web2py.gluon.dal.BaseAdapter.rollback web2py.gluon.dal.BaseAdapter-class.html#rollback +web2py.gluon.dal.BaseAdapter.ON web2py.gluon.dal.BaseAdapter-class.html#ON +web2py.gluon.dal.BaseAdapter.ALLOW_NULL web2py.gluon.dal.BaseAdapter-class.html#ALLOW_NULL +web2py.gluon.dal.BaseAdapter.maxcharlength web2py.gluon.dal.BaseAdapter-class.html#maxcharlength +web2py.gluon.dal.BaseAdapter.GE web2py.gluon.dal.BaseAdapter-class.html#GE +web2py.gluon.dal.BaseAdapter.get_table web2py.gluon.dal.BaseAdapter-class.html#get_table +web2py.gluon.dal.BaseAdapter._drop web2py.gluon.dal.BaseAdapter-class.html#_drop +web2py.gluon.dal.BaseAdapter.integrity_error_class web2py.gluon.dal.BaseAdapter-class.html#integrity_error_class +web2py.gluon.dal.ConnectionPool.pool_connection web2py.gluon.dal.ConnectionPool-class.html#pool_connection +web2py.gluon.dal.BaseAdapter._select web2py.gluon.dal.BaseAdapter-class.html#_select +web2py.gluon.dal.BaseAdapter.insert web2py.gluon.dal.BaseAdapter-class.html#insert +web2py.gluon.dal.JDBCSQLiteAdapter.execute web2py.gluon.dal.JDBCSQLiteAdapter-class.html#execute +web2py.gluon.dal.BaseAdapter.drop web2py.gluon.dal.BaseAdapter-class.html#drop +web2py.gluon.dal.BaseAdapter.migrate_table web2py.gluon.dal.BaseAdapter-class.html#migrate_table +web2py.gluon.dal.BaseAdapter.concat_add web2py.gluon.dal.BaseAdapter-class.html#concat_add +web2py.gluon.dal.BaseAdapter._insert web2py.gluon.dal.BaseAdapter-class.html#_insert +web2py.gluon.dal.ConnectionPool.pools web2py.gluon.dal.ConnectionPool-class.html#pools +web2py.gluon.dal.BaseAdapter.support_distributed_transaction web2py.gluon.dal.BaseAdapter-class.html#support_distributed_transaction +web2py.gluon.dal.BaseAdapter.OR web2py.gluon.dal.BaseAdapter-class.html#OR +web2py.gluon.dal.BaseAdapter.file_delete web2py.gluon.dal.BaseAdapter-class.html#file_delete +web2py.gluon.dal.BaseAdapter.NOT_NULL web2py.gluon.dal.BaseAdapter-class.html#NOT_NULL +web2py.gluon.dal.ConnectionPool.find_or_make_work_folder web2py.gluon.dal.ConnectionPool-class.html#find_or_make_work_folder +web2py.gluon.dal.BaseAdapter.close web2py.gluon.dal.BaseAdapter-class.html#close +web2py.gluon.dal.BaseAdapter.integrity_error web2py.gluon.dal.BaseAdapter-class.html#integrity_error +web2py.gluon.dal.BaseAdapter.rowslice web2py.gluon.dal.BaseAdapter-class.html#rowslice +web2py.gluon.dal.BaseAdapter.create_sequence_and_triggers web2py.gluon.dal.BaseAdapter-class.html#create_sequence_and_triggers +web2py.gluon.dal.BaseAdapter.rollback_prepared web2py.gluon.dal.BaseAdapter-class.html#rollback_prepared +web2py.gluon.dal.BaseAdapter.commit_prepared web2py.gluon.dal.BaseAdapter-class.html#commit_prepared +web2py.gluon.dal.SQLiteAdapter.web2py_extract web2py.gluon.dal.SQLiteAdapter-class.html#web2py_extract +web2py.gluon.dal.BaseAdapter.EQ web2py.gluon.dal.BaseAdapter-class.html#EQ +web2py.gluon.dal.BaseAdapter.AND web2py.gluon.dal.BaseAdapter-class.html#AND +web2py.gluon.dal.BaseAdapter.uploads_in_blob web2py.gluon.dal.BaseAdapter-class.html#uploads_in_blob +web2py.gluon.dal.BaseAdapter.NOT web2py.gluon.dal.BaseAdapter-class.html#NOT +web2py.gluon.dal.BaseAdapter.commit web2py.gluon.dal.BaseAdapter-class.html#commit +web2py.gluon.dal.BaseAdapter.MOD web2py.gluon.dal.BaseAdapter-class.html#MOD +web2py.gluon.dal.BaseAdapter.STARTSWITH web2py.gluon.dal.BaseAdapter-class.html#STARTSWITH +web2py.gluon.dal.BaseAdapter.SUB web2py.gluon.dal.BaseAdapter-class.html#SUB +web2py.gluon.dal.BaseAdapter.COALESCE_ZERO web2py.gluon.dal.BaseAdapter-class.html#COALESCE_ZERO +web2py.gluon.dal.BaseAdapter._delete web2py.gluon.dal.BaseAdapter-class.html#_delete +web2py.gluon.dal.BaseAdapter.BELONGS web2py.gluon.dal.BaseAdapter-class.html#BELONGS +web2py.gluon.dal.BaseAdapter.MUL web2py.gluon.dal.BaseAdapter-class.html#MUL +web2py.gluon.dal.BaseAdapter.select web2py.gluon.dal.BaseAdapter-class.html#select +web2py.gluon.dal.BaseAdapter.PRIMARY_KEY web2py.gluon.dal.BaseAdapter-class.html#PRIMARY_KEY +web2py.gluon.dal.BaseAdapter.prepare web2py.gluon.dal.BaseAdapter-class.html#prepare +web2py.gluon.dal.BaseAdapter.NE web2py.gluon.dal.BaseAdapter-class.html#NE +web2py.gluon.dal.BaseAdapter.SUBSTRING web2py.gluon.dal.BaseAdapter-class.html#SUBSTRING +web2py.gluon.dal.BaseAdapter.types web2py.gluon.dal.BaseAdapter-class.html#types +web2py.gluon.dal.SQLiteAdapter.EXTRACT web2py.gluon.dal.SQLiteAdapter-class.html#EXTRACT +web2py.gluon.dal.BaseAdapter.commit_on_alter_table web2py.gluon.dal.BaseAdapter-class.html#commit_on_alter_table +web2py.gluon.dal.BaseAdapter.represent_exceptions web2py.gluon.dal.BaseAdapter-class.html#represent_exceptions +web2py.gluon.dal.BaseAdapter.sequence_name web2py.gluon.dal.BaseAdapter-class.html#sequence_name +web2py.gluon.dal.BaseAdapter.ADD web2py.gluon.dal.BaseAdapter-class.html#ADD +web2py.gluon.dal.BaseAdapter.count web2py.gluon.dal.BaseAdapter-class.html#count +web2py.gluon.dal.BaseAdapter.UPPER web2py.gluon.dal.BaseAdapter-class.html#UPPER +web2py.gluon.dal.BaseAdapter.JOIN web2py.gluon.dal.BaseAdapter-class.html#JOIN +web2py.gluon.dal.BaseAdapter.LIKE web2py.gluon.dal.BaseAdapter-class.html#LIKE +web2py.gluon.dal.ConnectionPool.close_all_instances web2py.gluon.dal.ConnectionPool-class.html#close_all_instances +web2py.gluon.dal.BaseAdapter.file_exists web2py.gluon.dal.BaseAdapter-class.html#file_exists +web2py.gluon.dal.BaseAdapter.delete web2py.gluon.dal.BaseAdapter-class.html#delete +web2py.gluon.dal.BaseAdapter.LOWER web2py.gluon.dal.BaseAdapter-class.html#LOWER +web2py.gluon.dal.BaseAdapter._update web2py.gluon.dal.BaseAdapter-class.html#_update +web2py.gluon.dal.BaseAdapter.parse web2py.gluon.dal.BaseAdapter-class.html#parse +web2py.gluon.dal.BaseAdapter.ENDSWITH web2py.gluon.dal.BaseAdapter-class.html#ENDSWITH +web2py.gluon.dal.BaseAdapter.constraint_name web2py.gluon.dal.BaseAdapter-class.html#constraint_name +web2py.gluon.dal.BaseAdapter.DIV web2py.gluon.dal.BaseAdapter-class.html#DIV +web2py.gluon.dal.JDBCSQLiteAdapter.__init__ web2py.gluon.dal.JDBCSQLiteAdapter-class.html#__init__ +web2py.gluon.dal.BaseAdapter.filter_tenant web2py.gluon.dal.BaseAdapter-class.html#filter_tenant +web2py.gluon.dal.BaseAdapter.LE web2py.gluon.dal.BaseAdapter-class.html#LE +web2py.gluon.dal.BaseAdapter.file_open web2py.gluon.dal.BaseAdapter-class.html#file_open +web2py.gluon.dal.ConnectionPool.set_folder web2py.gluon.dal.ConnectionPool-class.html#set_folder +web2py.gluon.dal.BaseAdapter.LT web2py.gluon.dal.BaseAdapter-class.html#LT +web2py.gluon.dal.BaseAdapter.COMMA web2py.gluon.dal.BaseAdapter-class.html#COMMA +web2py.gluon.dal.BaseAdapter.create_table web2py.gluon.dal.BaseAdapter-class.html#create_table +web2py.gluon.dal.BaseAdapter.bulk_insert web2py.gluon.dal.BaseAdapter-class.html#bulk_insert +web2py.gluon.dal.BaseAdapter.AGGREGATE web2py.gluon.dal.BaseAdapter-class.html#AGGREGATE +web2py.gluon.dal.BaseAdapter.file_close web2py.gluon.dal.BaseAdapter-class.html#file_close +web2py.gluon.dal.BaseAdapter.RANDOM web2py.gluon.dal.BaseAdapter-class.html#RANDOM +web2py.gluon.dal.BaseAdapter.truncate web2py.gluon.dal.BaseAdapter-class.html#truncate +web2py.gluon.dal.JDBCSQLiteAdapter.driver web2py.gluon.dal.JDBCSQLiteAdapter-class.html#driver +web2py.gluon.dal.BaseAdapter.update web2py.gluon.dal.BaseAdapter-class.html#update +web2py.gluon.dal.BaseAdapter.AS web2py.gluon.dal.BaseAdapter-class.html#AS +web2py.gluon.dal.BaseAdapter.trigger_name web2py.gluon.dal.BaseAdapter-class.html#trigger_name +web2py.gluon.dal.BaseAdapter.expand web2py.gluon.dal.BaseAdapter-class.html#expand +web2py.gluon.dal.BaseAdapter._count web2py.gluon.dal.BaseAdapter-class.html#_count +web2py.gluon.dal.BaseAdapter.CONTAINS web2py.gluon.dal.BaseAdapter-class.html#CONTAINS +web2py.gluon.dal.BaseAdapter.INVERT web2py.gluon.dal.BaseAdapter-class.html#INVERT +web2py.gluon.dal.BaseAdapter.alias web2py.gluon.dal.BaseAdapter-class.html#alias +web2py.gluon.dal.SQLiteAdapter.lastrowid web2py.gluon.dal.SQLiteAdapter-class.html#lastrowid +web2py.gluon.dal.MSSQL2Adapter web2py.gluon.dal.MSSQL2Adapter-class.html +web2py.gluon.dal.MSSQL2Adapter.represent web2py.gluon.dal.MSSQL2Adapter-class.html#represent +web2py.gluon.dal.BaseAdapter.log_execute web2py.gluon.dal.BaseAdapter-class.html#log_execute +web2py.gluon.dal.MSSQLAdapter.select_limitby web2py.gluon.dal.MSSQLAdapter-class.html#select_limitby +web2py.gluon.dal.BaseAdapter._truncate web2py.gluon.dal.BaseAdapter-class.html#_truncate +web2py.gluon.dal.BaseAdapter.tables web2py.gluon.dal.BaseAdapter-class.html#tables +web2py.gluon.dal.MSSQLAdapter.LEFT_JOIN web2py.gluon.dal.MSSQLAdapter-class.html#LEFT_JOIN +web2py.gluon.dal.BaseAdapter.distributed_transaction_begin web2py.gluon.dal.BaseAdapter-class.html#distributed_transaction_begin +web2py.gluon.dal.BaseAdapter.GT web2py.gluon.dal.BaseAdapter-class.html#GT +web2py.gluon.dal.BaseAdapter.rollback web2py.gluon.dal.BaseAdapter-class.html#rollback +web2py.gluon.dal.BaseAdapter.ON web2py.gluon.dal.BaseAdapter-class.html#ON +web2py.gluon.dal.MSSQLAdapter.RANDOM web2py.gluon.dal.MSSQLAdapter-class.html#RANDOM +web2py.gluon.dal.BaseAdapter.maxcharlength web2py.gluon.dal.BaseAdapter-class.html#maxcharlength +web2py.gluon.dal.BaseAdapter.GE web2py.gluon.dal.BaseAdapter-class.html#GE +web2py.gluon.dal.BaseAdapter.get_table web2py.gluon.dal.BaseAdapter-class.html#get_table +web2py.gluon.dal.BaseAdapter._drop web2py.gluon.dal.BaseAdapter-class.html#_drop +web2py.gluon.dal.MSSQLAdapter.integrity_error_class web2py.gluon.dal.MSSQLAdapter-class.html#integrity_error_class +web2py.gluon.dal.ConnectionPool.pool_connection web2py.gluon.dal.ConnectionPool-class.html#pool_connection +web2py.gluon.dal.BaseAdapter._select web2py.gluon.dal.BaseAdapter-class.html#_select +web2py.gluon.dal.BaseAdapter.insert web2py.gluon.dal.BaseAdapter-class.html#insert +web2py.gluon.dal.MSSQL2Adapter.execute web2py.gluon.dal.MSSQL2Adapter-class.html#execute +web2py.gluon.dal.BaseAdapter.drop web2py.gluon.dal.BaseAdapter-class.html#drop +web2py.gluon.dal.BaseAdapter.migrate_table web2py.gluon.dal.BaseAdapter-class.html#migrate_table +web2py.gluon.dal.BaseAdapter.concat_add web2py.gluon.dal.BaseAdapter-class.html#concat_add +web2py.gluon.dal.BaseAdapter._insert web2py.gluon.dal.BaseAdapter-class.html#_insert +web2py.gluon.dal.ConnectionPool.pools web2py.gluon.dal.ConnectionPool-class.html#pools +web2py.gluon.dal.BaseAdapter.support_distributed_transaction web2py.gluon.dal.BaseAdapter-class.html#support_distributed_transaction +web2py.gluon.dal.BaseAdapter.OR web2py.gluon.dal.BaseAdapter-class.html#OR +web2py.gluon.dal.BaseAdapter.file_delete web2py.gluon.dal.BaseAdapter-class.html#file_delete +web2py.gluon.dal.BaseAdapter.NOT_NULL web2py.gluon.dal.BaseAdapter-class.html#NOT_NULL +web2py.gluon.dal.ConnectionPool.find_or_make_work_folder web2py.gluon.dal.ConnectionPool-class.html#find_or_make_work_folder +web2py.gluon.dal.BaseAdapter.close web2py.gluon.dal.BaseAdapter-class.html#close +web2py.gluon.dal.BaseAdapter.integrity_error web2py.gluon.dal.BaseAdapter-class.html#integrity_error +web2py.gluon.dal.MSSQLAdapter.rowslice web2py.gluon.dal.MSSQLAdapter-class.html#rowslice +web2py.gluon.dal.BaseAdapter.create_sequence_and_triggers web2py.gluon.dal.BaseAdapter-class.html#create_sequence_and_triggers +web2py.gluon.dal.BaseAdapter.rollback_prepared web2py.gluon.dal.BaseAdapter-class.html#rollback_prepared +web2py.gluon.dal.BaseAdapter.commit_prepared web2py.gluon.dal.BaseAdapter-class.html#commit_prepared +web2py.gluon.dal.BaseAdapter.EQ web2py.gluon.dal.BaseAdapter-class.html#EQ +web2py.gluon.dal.BaseAdapter.AND web2py.gluon.dal.BaseAdapter-class.html#AND +web2py.gluon.dal.BaseAdapter.uploads_in_blob web2py.gluon.dal.BaseAdapter-class.html#uploads_in_blob +web2py.gluon.dal.BaseAdapter.NOT web2py.gluon.dal.BaseAdapter-class.html#NOT +web2py.gluon.dal.BaseAdapter.commit web2py.gluon.dal.BaseAdapter-class.html#commit +web2py.gluon.dal.BaseAdapter.MOD web2py.gluon.dal.BaseAdapter-class.html#MOD +web2py.gluon.dal.BaseAdapter.STARTSWITH web2py.gluon.dal.BaseAdapter-class.html#STARTSWITH +web2py.gluon.dal.BaseAdapter.SUB web2py.gluon.dal.BaseAdapter-class.html#SUB +web2py.gluon.dal.BaseAdapter.COALESCE_ZERO web2py.gluon.dal.BaseAdapter-class.html#COALESCE_ZERO +web2py.gluon.dal.BaseAdapter._delete web2py.gluon.dal.BaseAdapter-class.html#_delete +web2py.gluon.dal.BaseAdapter.BELONGS web2py.gluon.dal.BaseAdapter-class.html#BELONGS +web2py.gluon.dal.BaseAdapter.MUL web2py.gluon.dal.BaseAdapter-class.html#MUL +web2py.gluon.dal.BaseAdapter.select web2py.gluon.dal.BaseAdapter-class.html#select +web2py.gluon.dal.MSSQLAdapter.PRIMARY_KEY web2py.gluon.dal.MSSQLAdapter-class.html#PRIMARY_KEY +web2py.gluon.dal.BaseAdapter.prepare web2py.gluon.dal.BaseAdapter-class.html#prepare +web2py.gluon.dal.BaseAdapter.NE web2py.gluon.dal.BaseAdapter-class.html#NE +web2py.gluon.dal.MSSQLAdapter.SUBSTRING web2py.gluon.dal.MSSQLAdapter-class.html#SUBSTRING +web2py.gluon.dal.MSSQL2Adapter.types web2py.gluon.dal.MSSQL2Adapter-class.html#types +web2py.gluon.dal.MSSQLAdapter.EXTRACT web2py.gluon.dal.MSSQLAdapter-class.html#EXTRACT +web2py.gluon.dal.BaseAdapter.commit_on_alter_table web2py.gluon.dal.BaseAdapter-class.html#commit_on_alter_table +web2py.gluon.dal.MSSQLAdapter.represent_exceptions web2py.gluon.dal.MSSQLAdapter-class.html#represent_exceptions +web2py.gluon.dal.BaseAdapter.sequence_name web2py.gluon.dal.BaseAdapter-class.html#sequence_name +web2py.gluon.dal.BaseAdapter.ADD web2py.gluon.dal.BaseAdapter-class.html#ADD +web2py.gluon.dal.BaseAdapter.count web2py.gluon.dal.BaseAdapter-class.html#count +web2py.gluon.dal.BaseAdapter.UPPER web2py.gluon.dal.BaseAdapter-class.html#UPPER +web2py.gluon.dal.BaseAdapter.JOIN web2py.gluon.dal.BaseAdapter-class.html#JOIN +web2py.gluon.dal.BaseAdapter.LIKE web2py.gluon.dal.BaseAdapter-class.html#LIKE +web2py.gluon.dal.ConnectionPool.close_all_instances web2py.gluon.dal.ConnectionPool-class.html#close_all_instances +web2py.gluon.dal.BaseAdapter.file_exists web2py.gluon.dal.BaseAdapter-class.html#file_exists +web2py.gluon.dal.BaseAdapter.delete web2py.gluon.dal.BaseAdapter-class.html#delete +web2py.gluon.dal.BaseAdapter.LOWER web2py.gluon.dal.BaseAdapter-class.html#LOWER +web2py.gluon.dal.BaseAdapter._update web2py.gluon.dal.BaseAdapter-class.html#_update +web2py.gluon.dal.BaseAdapter.parse web2py.gluon.dal.BaseAdapter-class.html#parse +web2py.gluon.dal.BaseAdapter.ENDSWITH web2py.gluon.dal.BaseAdapter-class.html#ENDSWITH +web2py.gluon.dal.BaseAdapter.constraint_name web2py.gluon.dal.BaseAdapter-class.html#constraint_name +web2py.gluon.dal.BaseAdapter.DIV web2py.gluon.dal.BaseAdapter-class.html#DIV +web2py.gluon.dal.MSSQLAdapter.__init__ web2py.gluon.dal.MSSQLAdapter-class.html#__init__ +web2py.gluon.dal.BaseAdapter.filter_tenant web2py.gluon.dal.BaseAdapter-class.html#filter_tenant +web2py.gluon.dal.BaseAdapter.LE web2py.gluon.dal.BaseAdapter-class.html#LE +web2py.gluon.dal.BaseAdapter.file_open web2py.gluon.dal.BaseAdapter-class.html#file_open +web2py.gluon.dal.ConnectionPool.set_folder web2py.gluon.dal.ConnectionPool-class.html#set_folder +web2py.gluon.dal.BaseAdapter.LT web2py.gluon.dal.BaseAdapter-class.html#LT +web2py.gluon.dal.BaseAdapter.COMMA web2py.gluon.dal.BaseAdapter-class.html#COMMA +web2py.gluon.dal.BaseAdapter.create_table web2py.gluon.dal.BaseAdapter-class.html#create_table +web2py.gluon.dal.BaseAdapter.bulk_insert web2py.gluon.dal.BaseAdapter-class.html#bulk_insert +web2py.gluon.dal.BaseAdapter.AGGREGATE web2py.gluon.dal.BaseAdapter-class.html#AGGREGATE +web2py.gluon.dal.BaseAdapter.file_close web2py.gluon.dal.BaseAdapter-class.html#file_close +web2py.gluon.dal.MSSQLAdapter.ALLOW_NULL web2py.gluon.dal.MSSQLAdapter-class.html#ALLOW_NULL +web2py.gluon.dal.BaseAdapter.truncate web2py.gluon.dal.BaseAdapter-class.html#truncate +web2py.gluon.dal.MSSQLAdapter.driver web2py.gluon.dal.MSSQLAdapter-class.html#driver +web2py.gluon.dal.BaseAdapter.update web2py.gluon.dal.BaseAdapter-class.html#update +web2py.gluon.dal.BaseAdapter.AS web2py.gluon.dal.BaseAdapter-class.html#AS +web2py.gluon.dal.BaseAdapter.trigger_name web2py.gluon.dal.BaseAdapter-class.html#trigger_name +web2py.gluon.dal.BaseAdapter.expand web2py.gluon.dal.BaseAdapter-class.html#expand +web2py.gluon.dal.BaseAdapter._count web2py.gluon.dal.BaseAdapter-class.html#_count +web2py.gluon.dal.BaseAdapter.CONTAINS web2py.gluon.dal.BaseAdapter-class.html#CONTAINS +web2py.gluon.dal.BaseAdapter.INVERT web2py.gluon.dal.BaseAdapter-class.html#INVERT +web2py.gluon.dal.BaseAdapter.alias web2py.gluon.dal.BaseAdapter-class.html#alias +web2py.gluon.dal.MSSQLAdapter.lastrowid web2py.gluon.dal.MSSQLAdapter-class.html#lastrowid +web2py.gluon.dal.MSSQLAdapter web2py.gluon.dal.MSSQLAdapter-class.html +web2py.gluon.dal.BaseAdapter.represent web2py.gluon.dal.BaseAdapter-class.html#represent +web2py.gluon.dal.BaseAdapter.log_execute web2py.gluon.dal.BaseAdapter-class.html#log_execute +web2py.gluon.dal.MSSQLAdapter.select_limitby web2py.gluon.dal.MSSQLAdapter-class.html#select_limitby +web2py.gluon.dal.BaseAdapter._truncate web2py.gluon.dal.BaseAdapter-class.html#_truncate +web2py.gluon.dal.BaseAdapter.tables web2py.gluon.dal.BaseAdapter-class.html#tables +web2py.gluon.dal.MSSQLAdapter.LEFT_JOIN web2py.gluon.dal.MSSQLAdapter-class.html#LEFT_JOIN +web2py.gluon.dal.BaseAdapter.distributed_transaction_begin web2py.gluon.dal.BaseAdapter-class.html#distributed_transaction_begin +web2py.gluon.dal.BaseAdapter.GT web2py.gluon.dal.BaseAdapter-class.html#GT +web2py.gluon.dal.BaseAdapter.rollback web2py.gluon.dal.BaseAdapter-class.html#rollback +web2py.gluon.dal.BaseAdapter.ON web2py.gluon.dal.BaseAdapter-class.html#ON +web2py.gluon.dal.MSSQLAdapter.RANDOM web2py.gluon.dal.MSSQLAdapter-class.html#RANDOM +web2py.gluon.dal.BaseAdapter.maxcharlength web2py.gluon.dal.BaseAdapter-class.html#maxcharlength +web2py.gluon.dal.BaseAdapter.GE web2py.gluon.dal.BaseAdapter-class.html#GE +web2py.gluon.dal.BaseAdapter.get_table web2py.gluon.dal.BaseAdapter-class.html#get_table +web2py.gluon.dal.BaseAdapter._drop web2py.gluon.dal.BaseAdapter-class.html#_drop +web2py.gluon.dal.MSSQLAdapter.integrity_error_class web2py.gluon.dal.MSSQLAdapter-class.html#integrity_error_class +web2py.gluon.dal.ConnectionPool.pool_connection web2py.gluon.dal.ConnectionPool-class.html#pool_connection +web2py.gluon.dal.BaseAdapter._select web2py.gluon.dal.BaseAdapter-class.html#_select +web2py.gluon.dal.BaseAdapter.insert web2py.gluon.dal.BaseAdapter-class.html#insert +web2py.gluon.dal.BaseAdapter.execute web2py.gluon.dal.BaseAdapter-class.html#execute +web2py.gluon.dal.BaseAdapter.drop web2py.gluon.dal.BaseAdapter-class.html#drop +web2py.gluon.dal.BaseAdapter.migrate_table web2py.gluon.dal.BaseAdapter-class.html#migrate_table +web2py.gluon.dal.BaseAdapter.concat_add web2py.gluon.dal.BaseAdapter-class.html#concat_add +web2py.gluon.dal.BaseAdapter._insert web2py.gluon.dal.BaseAdapter-class.html#_insert +web2py.gluon.dal.ConnectionPool.pools web2py.gluon.dal.ConnectionPool-class.html#pools +web2py.gluon.dal.BaseAdapter.support_distributed_transaction web2py.gluon.dal.BaseAdapter-class.html#support_distributed_transaction +web2py.gluon.dal.BaseAdapter.OR web2py.gluon.dal.BaseAdapter-class.html#OR +web2py.gluon.dal.BaseAdapter.file_delete web2py.gluon.dal.BaseAdapter-class.html#file_delete +web2py.gluon.dal.BaseAdapter.NOT_NULL web2py.gluon.dal.BaseAdapter-class.html#NOT_NULL +web2py.gluon.dal.ConnectionPool.find_or_make_work_folder web2py.gluon.dal.ConnectionPool-class.html#find_or_make_work_folder +web2py.gluon.dal.BaseAdapter.close web2py.gluon.dal.BaseAdapter-class.html#close +web2py.gluon.dal.BaseAdapter.integrity_error web2py.gluon.dal.BaseAdapter-class.html#integrity_error +web2py.gluon.dal.MSSQLAdapter.rowslice web2py.gluon.dal.MSSQLAdapter-class.html#rowslice +web2py.gluon.dal.BaseAdapter.create_sequence_and_triggers web2py.gluon.dal.BaseAdapter-class.html#create_sequence_and_triggers +web2py.gluon.dal.BaseAdapter.rollback_prepared web2py.gluon.dal.BaseAdapter-class.html#rollback_prepared +web2py.gluon.dal.BaseAdapter.commit_prepared web2py.gluon.dal.BaseAdapter-class.html#commit_prepared +web2py.gluon.dal.BaseAdapter.EQ web2py.gluon.dal.BaseAdapter-class.html#EQ +web2py.gluon.dal.BaseAdapter.AND web2py.gluon.dal.BaseAdapter-class.html#AND +web2py.gluon.dal.BaseAdapter.uploads_in_blob web2py.gluon.dal.BaseAdapter-class.html#uploads_in_blob +web2py.gluon.dal.BaseAdapter.NOT web2py.gluon.dal.BaseAdapter-class.html#NOT +web2py.gluon.dal.BaseAdapter.commit web2py.gluon.dal.BaseAdapter-class.html#commit +web2py.gluon.dal.BaseAdapter.MOD web2py.gluon.dal.BaseAdapter-class.html#MOD +web2py.gluon.dal.BaseAdapter.STARTSWITH web2py.gluon.dal.BaseAdapter-class.html#STARTSWITH +web2py.gluon.dal.BaseAdapter.SUB web2py.gluon.dal.BaseAdapter-class.html#SUB +web2py.gluon.dal.BaseAdapter.COALESCE_ZERO web2py.gluon.dal.BaseAdapter-class.html#COALESCE_ZERO +web2py.gluon.dal.BaseAdapter._delete web2py.gluon.dal.BaseAdapter-class.html#_delete +web2py.gluon.dal.BaseAdapter.BELONGS web2py.gluon.dal.BaseAdapter-class.html#BELONGS +web2py.gluon.dal.BaseAdapter.MUL web2py.gluon.dal.BaseAdapter-class.html#MUL +web2py.gluon.dal.BaseAdapter.select web2py.gluon.dal.BaseAdapter-class.html#select +web2py.gluon.dal.MSSQLAdapter.PRIMARY_KEY web2py.gluon.dal.MSSQLAdapter-class.html#PRIMARY_KEY +web2py.gluon.dal.BaseAdapter.prepare web2py.gluon.dal.BaseAdapter-class.html#prepare +web2py.gluon.dal.BaseAdapter.NE web2py.gluon.dal.BaseAdapter-class.html#NE +web2py.gluon.dal.MSSQLAdapter.SUBSTRING web2py.gluon.dal.MSSQLAdapter-class.html#SUBSTRING +web2py.gluon.dal.MSSQLAdapter.types web2py.gluon.dal.MSSQLAdapter-class.html#types +web2py.gluon.dal.MSSQLAdapter.EXTRACT web2py.gluon.dal.MSSQLAdapter-class.html#EXTRACT +web2py.gluon.dal.BaseAdapter.commit_on_alter_table web2py.gluon.dal.BaseAdapter-class.html#commit_on_alter_table +web2py.gluon.dal.MSSQLAdapter.represent_exceptions web2py.gluon.dal.MSSQLAdapter-class.html#represent_exceptions +web2py.gluon.dal.BaseAdapter.sequence_name web2py.gluon.dal.BaseAdapter-class.html#sequence_name +web2py.gluon.dal.BaseAdapter.ADD web2py.gluon.dal.BaseAdapter-class.html#ADD +web2py.gluon.dal.BaseAdapter.count web2py.gluon.dal.BaseAdapter-class.html#count +web2py.gluon.dal.BaseAdapter.UPPER web2py.gluon.dal.BaseAdapter-class.html#UPPER +web2py.gluon.dal.BaseAdapter.JOIN web2py.gluon.dal.BaseAdapter-class.html#JOIN +web2py.gluon.dal.BaseAdapter.LIKE web2py.gluon.dal.BaseAdapter-class.html#LIKE +web2py.gluon.dal.ConnectionPool.close_all_instances web2py.gluon.dal.ConnectionPool-class.html#close_all_instances +web2py.gluon.dal.BaseAdapter.file_exists web2py.gluon.dal.BaseAdapter-class.html#file_exists +web2py.gluon.dal.BaseAdapter.delete web2py.gluon.dal.BaseAdapter-class.html#delete +web2py.gluon.dal.BaseAdapter.LOWER web2py.gluon.dal.BaseAdapter-class.html#LOWER +web2py.gluon.dal.BaseAdapter._update web2py.gluon.dal.BaseAdapter-class.html#_update +web2py.gluon.dal.BaseAdapter.parse web2py.gluon.dal.BaseAdapter-class.html#parse +web2py.gluon.dal.BaseAdapter.ENDSWITH web2py.gluon.dal.BaseAdapter-class.html#ENDSWITH +web2py.gluon.dal.BaseAdapter.constraint_name web2py.gluon.dal.BaseAdapter-class.html#constraint_name +web2py.gluon.dal.BaseAdapter.DIV web2py.gluon.dal.BaseAdapter-class.html#DIV +web2py.gluon.dal.MSSQLAdapter.__init__ web2py.gluon.dal.MSSQLAdapter-class.html#__init__ +web2py.gluon.dal.BaseAdapter.filter_tenant web2py.gluon.dal.BaseAdapter-class.html#filter_tenant +web2py.gluon.dal.BaseAdapter.LE web2py.gluon.dal.BaseAdapter-class.html#LE +web2py.gluon.dal.BaseAdapter.file_open web2py.gluon.dal.BaseAdapter-class.html#file_open +web2py.gluon.dal.ConnectionPool.set_folder web2py.gluon.dal.ConnectionPool-class.html#set_folder +web2py.gluon.dal.BaseAdapter.LT web2py.gluon.dal.BaseAdapter-class.html#LT +web2py.gluon.dal.BaseAdapter.COMMA web2py.gluon.dal.BaseAdapter-class.html#COMMA +web2py.gluon.dal.BaseAdapter.create_table web2py.gluon.dal.BaseAdapter-class.html#create_table +web2py.gluon.dal.BaseAdapter.bulk_insert web2py.gluon.dal.BaseAdapter-class.html#bulk_insert +web2py.gluon.dal.BaseAdapter.AGGREGATE web2py.gluon.dal.BaseAdapter-class.html#AGGREGATE +web2py.gluon.dal.BaseAdapter.file_close web2py.gluon.dal.BaseAdapter-class.html#file_close +web2py.gluon.dal.MSSQLAdapter.ALLOW_NULL web2py.gluon.dal.MSSQLAdapter-class.html#ALLOW_NULL +web2py.gluon.dal.BaseAdapter.truncate web2py.gluon.dal.BaseAdapter-class.html#truncate +web2py.gluon.dal.MSSQLAdapter.driver web2py.gluon.dal.MSSQLAdapter-class.html#driver +web2py.gluon.dal.BaseAdapter.update web2py.gluon.dal.BaseAdapter-class.html#update +web2py.gluon.dal.BaseAdapter.AS web2py.gluon.dal.BaseAdapter-class.html#AS +web2py.gluon.dal.BaseAdapter.trigger_name web2py.gluon.dal.BaseAdapter-class.html#trigger_name +web2py.gluon.dal.BaseAdapter.expand web2py.gluon.dal.BaseAdapter-class.html#expand +web2py.gluon.dal.BaseAdapter._count web2py.gluon.dal.BaseAdapter-class.html#_count +web2py.gluon.dal.BaseAdapter.CONTAINS web2py.gluon.dal.BaseAdapter-class.html#CONTAINS +web2py.gluon.dal.BaseAdapter.INVERT web2py.gluon.dal.BaseAdapter-class.html#INVERT +web2py.gluon.dal.BaseAdapter.alias web2py.gluon.dal.BaseAdapter-class.html#alias +web2py.gluon.dal.MSSQLAdapter.lastrowid web2py.gluon.dal.MSSQLAdapter-class.html#lastrowid +web2py.gluon.dal.MongoDBAdapter web2py.gluon.dal.MongoDBAdapter-class.html +web2py.gluon.dal.NoSQLAdapter.represent web2py.gluon.dal.NoSQLAdapter-class.html#represent +web2py.gluon.dal.NoSQLAdapter.log_execute web2py.gluon.dal.NoSQLAdapter-class.html#log_execute +web2py.gluon.dal.BaseAdapter.select_limitby web2py.gluon.dal.BaseAdapter-class.html#select_limitby +web2py.gluon.dal.BaseAdapter._truncate web2py.gluon.dal.BaseAdapter-class.html#_truncate +web2py.gluon.dal.NoSQLAdapter.SUB web2py.gluon.dal.NoSQLAdapter-class.html#SUB +web2py.gluon.dal.BaseAdapter.tables web2py.gluon.dal.BaseAdapter-class.html#tables +web2py.gluon.dal.NoSQLAdapter.LEFT_JOIN web2py.gluon.dal.NoSQLAdapter-class.html#LEFT_JOIN +web2py.gluon.dal.NoSQLAdapter.distributed_transaction_begin web2py.gluon.dal.NoSQLAdapter-class.html#distributed_transaction_begin +web2py.gluon.dal.BaseAdapter.GT web2py.gluon.dal.BaseAdapter-class.html#GT +web2py.gluon.dal.NoSQLAdapter.rollback web2py.gluon.dal.NoSQLAdapter-class.html#rollback +web2py.gluon.dal.NoSQLAdapter.ON web2py.gluon.dal.NoSQLAdapter-class.html#ON +web2py.gluon.dal.BaseAdapter.ALLOW_NULL web2py.gluon.dal.BaseAdapter-class.html#ALLOW_NULL +web2py.gluon.dal.BaseAdapter.maxcharlength web2py.gluon.dal.BaseAdapter-class.html#maxcharlength +web2py.gluon.dal.BaseAdapter.GE web2py.gluon.dal.BaseAdapter-class.html#GE +web2py.gluon.dal.BaseAdapter.get_table web2py.gluon.dal.BaseAdapter-class.html#get_table +web2py.gluon.dal.BaseAdapter._drop web2py.gluon.dal.BaseAdapter-class.html#_drop +web2py.gluon.dal.NoSQLAdapter.integrity_error_class web2py.gluon.dal.NoSQLAdapter-class.html#integrity_error_class +web2py.gluon.dal.ConnectionPool.pool_connection web2py.gluon.dal.ConnectionPool-class.html#pool_connection +web2py.gluon.dal.NoSQLAdapter._select web2py.gluon.dal.NoSQLAdapter-class.html#_select +web2py.gluon.dal.MongoDBAdapter.insert web2py.gluon.dal.MongoDBAdapter-class.html#insert +web2py.gluon.dal.NoSQLAdapter.execute web2py.gluon.dal.NoSQLAdapter-class.html#execute +web2py.gluon.dal.NoSQLAdapter.drop web2py.gluon.dal.NoSQLAdapter-class.html#drop +web2py.gluon.dal.NoSQLAdapter.migrate_table web2py.gluon.dal.NoSQLAdapter-class.html#migrate_table +web2py.gluon.dal.NoSQLAdapter.concat_add web2py.gluon.dal.NoSQLAdapter-class.html#concat_add +web2py.gluon.dal.NoSQLAdapter._insert web2py.gluon.dal.NoSQLAdapter-class.html#_insert +web2py.gluon.dal.ConnectionPool.pools web2py.gluon.dal.ConnectionPool-class.html#pools +web2py.gluon.dal.BaseAdapter.support_distributed_transaction web2py.gluon.dal.BaseAdapter-class.html#support_distributed_transaction +web2py.gluon.dal.NoSQLAdapter.OR web2py.gluon.dal.NoSQLAdapter-class.html#OR +web2py.gluon.dal.BaseAdapter.file_delete web2py.gluon.dal.BaseAdapter-class.html#file_delete +web2py.gluon.dal.BaseAdapter.NOT_NULL web2py.gluon.dal.BaseAdapter-class.html#NOT_NULL +web2py.gluon.dal.ConnectionPool.find_or_make_work_folder web2py.gluon.dal.ConnectionPool-class.html#find_or_make_work_folder +web2py.gluon.dal.NoSQLAdapter.close web2py.gluon.dal.NoSQLAdapter-class.html#close +web2py.gluon.dal.BaseAdapter.integrity_error web2py.gluon.dal.BaseAdapter-class.html#integrity_error +web2py.gluon.dal.NoSQLAdapter.rowslice web2py.gluon.dal.NoSQLAdapter-class.html#rowslice +web2py.gluon.dal.NoSQLAdapter.create_sequence_and_triggers web2py.gluon.dal.NoSQLAdapter-class.html#create_sequence_and_triggers +web2py.gluon.dal.NoSQLAdapter.rollback_prepared web2py.gluon.dal.NoSQLAdapter-class.html#rollback_prepared +web2py.gluon.dal.NoSQLAdapter.commit_prepared web2py.gluon.dal.NoSQLAdapter-class.html#commit_prepared +web2py.gluon.dal.BaseAdapter.EQ web2py.gluon.dal.BaseAdapter-class.html#EQ +web2py.gluon.dal.NoSQLAdapter.AND web2py.gluon.dal.NoSQLAdapter-class.html#AND +web2py.gluon.dal.MongoDBAdapter.uploads_in_blob web2py.gluon.dal.MongoDBAdapter-class.html#uploads_in_blob +web2py.gluon.dal.BaseAdapter.NOT web2py.gluon.dal.BaseAdapter-class.html#NOT +web2py.gluon.dal.NoSQLAdapter.commit web2py.gluon.dal.NoSQLAdapter-class.html#commit +web2py.gluon.dal.BaseAdapter.MOD web2py.gluon.dal.BaseAdapter-class.html#MOD +web2py.gluon.dal.NoSQLAdapter.STARTSWITH web2py.gluon.dal.NoSQLAdapter-class.html#STARTSWITH +web2py.gluon.dal.NoSQLAdapter.to_unicode web2py.gluon.dal.NoSQLAdapter-class.html#to_unicode +web2py.gluon.dal.BaseAdapter.COALESCE_ZERO web2py.gluon.dal.BaseAdapter-class.html#COALESCE_ZERO +web2py.gluon.dal.NoSQLAdapter._delete web2py.gluon.dal.NoSQLAdapter-class.html#_delete +web2py.gluon.dal.BaseAdapter.BELONGS web2py.gluon.dal.BaseAdapter-class.html#BELONGS +web2py.gluon.dal.NoSQLAdapter.MUL web2py.gluon.dal.NoSQLAdapter-class.html#MUL +web2py.gluon.dal.MongoDBAdapter.select web2py.gluon.dal.MongoDBAdapter-class.html#select +web2py.gluon.dal.NoSQLAdapter.PRIMARY_KEY web2py.gluon.dal.NoSQLAdapter-class.html#PRIMARY_KEY +web2py.gluon.dal.NoSQLAdapter.prepare web2py.gluon.dal.NoSQLAdapter-class.html#prepare +web2py.gluon.dal.BaseAdapter.NE web2py.gluon.dal.BaseAdapter-class.html#NE +web2py.gluon.dal.NoSQLAdapter.SUBSTRING web2py.gluon.dal.NoSQLAdapter-class.html#SUBSTRING +web2py.gluon.dal.MongoDBAdapter.types web2py.gluon.dal.MongoDBAdapter-class.html#types +web2py.gluon.dal.NoSQLAdapter.EXTRACT web2py.gluon.dal.NoSQLAdapter-class.html#EXTRACT +web2py.gluon.dal.BaseAdapter.commit_on_alter_table web2py.gluon.dal.BaseAdapter-class.html#commit_on_alter_table +web2py.gluon.dal.NoSQLAdapter.represent_exceptions web2py.gluon.dal.NoSQLAdapter-class.html#represent_exceptions +web2py.gluon.dal.BaseAdapter.sequence_name web2py.gluon.dal.BaseAdapter-class.html#sequence_name +web2py.gluon.dal.NoSQLAdapter.ADD web2py.gluon.dal.NoSQLAdapter-class.html#ADD +web2py.gluon.dal.MongoDBAdapter.count web2py.gluon.dal.MongoDBAdapter-class.html#count +web2py.gluon.dal.NoSQLAdapter.UPPER web2py.gluon.dal.NoSQLAdapter-class.html#UPPER +web2py.gluon.dal.BaseAdapter.JOIN web2py.gluon.dal.BaseAdapter-class.html#JOIN +web2py.gluon.dal.NoSQLAdapter.LIKE web2py.gluon.dal.NoSQLAdapter-class.html#LIKE +web2py.gluon.dal.ConnectionPool.close_all_instances web2py.gluon.dal.ConnectionPool-class.html#close_all_instances +web2py.gluon.dal.BaseAdapter.file_exists web2py.gluon.dal.BaseAdapter-class.html#file_exists +web2py.gluon.dal.MongoDBAdapter.delete web2py.gluon.dal.MongoDBAdapter-class.html#delete +web2py.gluon.dal.NoSQLAdapter.LOWER web2py.gluon.dal.NoSQLAdapter-class.html#LOWER +web2py.gluon.dal.NoSQLAdapter._update web2py.gluon.dal.NoSQLAdapter-class.html#_update +web2py.gluon.dal.BaseAdapter.parse web2py.gluon.dal.BaseAdapter-class.html#parse +web2py.gluon.dal.NoSQLAdapter.ENDSWITH web2py.gluon.dal.NoSQLAdapter-class.html#ENDSWITH +web2py.gluon.dal.NoSQLAdapter.constraint_name web2py.gluon.dal.NoSQLAdapter-class.html#constraint_name +web2py.gluon.dal.NoSQLAdapter.DIV web2py.gluon.dal.NoSQLAdapter-class.html#DIV +web2py.gluon.dal.MongoDBAdapter.__init__ web2py.gluon.dal.MongoDBAdapter-class.html#__init__ +web2py.gluon.dal.BaseAdapter.filter_tenant web2py.gluon.dal.BaseAdapter-class.html#filter_tenant +web2py.gluon.dal.BaseAdapter.LE web2py.gluon.dal.BaseAdapter-class.html#LE +web2py.gluon.dal.BaseAdapter.file_open web2py.gluon.dal.BaseAdapter-class.html#file_open +web2py.gluon.dal.ConnectionPool.set_folder web2py.gluon.dal.ConnectionPool-class.html#set_folder +web2py.gluon.dal.BaseAdapter.LT web2py.gluon.dal.BaseAdapter-class.html#LT +web2py.gluon.dal.BaseAdapter.COMMA web2py.gluon.dal.BaseAdapter-class.html#COMMA +web2py.gluon.dal.BaseAdapter.create_table web2py.gluon.dal.BaseAdapter-class.html#create_table +web2py.gluon.dal.BaseAdapter.bulk_insert web2py.gluon.dal.BaseAdapter-class.html#bulk_insert +web2py.gluon.dal.NoSQLAdapter.AGGREGATE web2py.gluon.dal.NoSQLAdapter-class.html#AGGREGATE +web2py.gluon.dal.BaseAdapter.file_close web2py.gluon.dal.BaseAdapter-class.html#file_close +web2py.gluon.dal.NoSQLAdapter.RANDOM web2py.gluon.dal.NoSQLAdapter-class.html#RANDOM +web2py.gluon.dal.BaseAdapter.truncate web2py.gluon.dal.BaseAdapter-class.html#truncate +web2py.gluon.dal.BaseAdapter.driver web2py.gluon.dal.BaseAdapter-class.html#driver +web2py.gluon.dal.MongoDBAdapter.update web2py.gluon.dal.MongoDBAdapter-class.html#update +web2py.gluon.dal.NoSQLAdapter.AS web2py.gluon.dal.NoSQLAdapter-class.html#AS +web2py.gluon.dal.BaseAdapter.trigger_name web2py.gluon.dal.BaseAdapter-class.html#trigger_name +web2py.gluon.dal.BaseAdapter.expand web2py.gluon.dal.BaseAdapter-class.html#expand +web2py.gluon.dal.NoSQLAdapter._count web2py.gluon.dal.NoSQLAdapter-class.html#_count +web2py.gluon.dal.BaseAdapter.CONTAINS web2py.gluon.dal.BaseAdapter-class.html#CONTAINS +web2py.gluon.dal.BaseAdapter.INVERT web2py.gluon.dal.BaseAdapter-class.html#INVERT +web2py.gluon.dal.NoSQLAdapter.alias web2py.gluon.dal.NoSQLAdapter-class.html#alias +web2py.gluon.dal.NoSQLAdapter.lastrowid web2py.gluon.dal.NoSQLAdapter-class.html#lastrowid +web2py.gluon.dal.MySQLAdapter web2py.gluon.dal.MySQLAdapter-class.html +web2py.gluon.dal.BaseAdapter.represent web2py.gluon.dal.BaseAdapter-class.html#represent +web2py.gluon.dal.BaseAdapter.log_execute web2py.gluon.dal.BaseAdapter-class.html#log_execute +web2py.gluon.dal.BaseAdapter.select_limitby web2py.gluon.dal.BaseAdapter-class.html#select_limitby +web2py.gluon.dal.BaseAdapter._truncate web2py.gluon.dal.BaseAdapter-class.html#_truncate +web2py.gluon.dal.BaseAdapter.tables web2py.gluon.dal.BaseAdapter-class.html#tables +web2py.gluon.dal.BaseAdapter.LEFT_JOIN web2py.gluon.dal.BaseAdapter-class.html#LEFT_JOIN +web2py.gluon.dal.MySQLAdapter.distributed_transaction_begin web2py.gluon.dal.MySQLAdapter-class.html#distributed_transaction_begin +web2py.gluon.dal.BaseAdapter.GT web2py.gluon.dal.BaseAdapter-class.html#GT +web2py.gluon.dal.BaseAdapter.rollback web2py.gluon.dal.BaseAdapter-class.html#rollback +web2py.gluon.dal.BaseAdapter.ON web2py.gluon.dal.BaseAdapter-class.html#ON +web2py.gluon.dal.BaseAdapter.ALLOW_NULL web2py.gluon.dal.BaseAdapter-class.html#ALLOW_NULL +web2py.gluon.dal.MySQLAdapter.maxcharlength web2py.gluon.dal.MySQLAdapter-class.html#maxcharlength +web2py.gluon.dal.BaseAdapter.GE web2py.gluon.dal.BaseAdapter-class.html#GE +web2py.gluon.dal.BaseAdapter.get_table web2py.gluon.dal.BaseAdapter-class.html#get_table +web2py.gluon.dal.MySQLAdapter._drop web2py.gluon.dal.MySQLAdapter-class.html#_drop +web2py.gluon.dal.BaseAdapter.integrity_error_class web2py.gluon.dal.BaseAdapter-class.html#integrity_error_class +web2py.gluon.dal.ConnectionPool.pool_connection web2py.gluon.dal.ConnectionPool-class.html#pool_connection +web2py.gluon.dal.BaseAdapter._select web2py.gluon.dal.BaseAdapter-class.html#_select +web2py.gluon.dal.BaseAdapter.insert web2py.gluon.dal.BaseAdapter-class.html#insert +web2py.gluon.dal.BaseAdapter.execute web2py.gluon.dal.BaseAdapter-class.html#execute +web2py.gluon.dal.BaseAdapter.drop web2py.gluon.dal.BaseAdapter-class.html#drop +web2py.gluon.dal.BaseAdapter.migrate_table web2py.gluon.dal.BaseAdapter-class.html#migrate_table +web2py.gluon.dal.MySQLAdapter.concat_add web2py.gluon.dal.MySQLAdapter-class.html#concat_add +web2py.gluon.dal.BaseAdapter._insert web2py.gluon.dal.BaseAdapter-class.html#_insert +web2py.gluon.dal.ConnectionPool.pools web2py.gluon.dal.ConnectionPool-class.html#pools +web2py.gluon.dal.MySQLAdapter.support_distributed_transaction web2py.gluon.dal.MySQLAdapter-class.html#support_distributed_transaction +web2py.gluon.dal.BaseAdapter.OR web2py.gluon.dal.BaseAdapter-class.html#OR +web2py.gluon.dal.BaseAdapter.file_delete web2py.gluon.dal.BaseAdapter-class.html#file_delete +web2py.gluon.dal.BaseAdapter.NOT_NULL web2py.gluon.dal.BaseAdapter-class.html#NOT_NULL +web2py.gluon.dal.ConnectionPool.find_or_make_work_folder web2py.gluon.dal.ConnectionPool-class.html#find_or_make_work_folder +web2py.gluon.dal.BaseAdapter.close web2py.gluon.dal.BaseAdapter-class.html#close +web2py.gluon.dal.BaseAdapter.integrity_error web2py.gluon.dal.BaseAdapter-class.html#integrity_error +web2py.gluon.dal.BaseAdapter.rowslice web2py.gluon.dal.BaseAdapter-class.html#rowslice +web2py.gluon.dal.BaseAdapter.create_sequence_and_triggers web2py.gluon.dal.BaseAdapter-class.html#create_sequence_and_triggers +web2py.gluon.dal.MySQLAdapter.rollback_prepared web2py.gluon.dal.MySQLAdapter-class.html#rollback_prepared +web2py.gluon.dal.MySQLAdapter.commit_prepared web2py.gluon.dal.MySQLAdapter-class.html#commit_prepared +web2py.gluon.dal.BaseAdapter.EQ web2py.gluon.dal.BaseAdapter-class.html#EQ +web2py.gluon.dal.BaseAdapter.AND web2py.gluon.dal.BaseAdapter-class.html#AND +web2py.gluon.dal.BaseAdapter.uploads_in_blob web2py.gluon.dal.BaseAdapter-class.html#uploads_in_blob +web2py.gluon.dal.BaseAdapter.NOT web2py.gluon.dal.BaseAdapter-class.html#NOT +web2py.gluon.dal.BaseAdapter.commit web2py.gluon.dal.BaseAdapter-class.html#commit +web2py.gluon.dal.BaseAdapter.MOD web2py.gluon.dal.BaseAdapter-class.html#MOD +web2py.gluon.dal.BaseAdapter.STARTSWITH web2py.gluon.dal.BaseAdapter-class.html#STARTSWITH +web2py.gluon.dal.BaseAdapter.SUB web2py.gluon.dal.BaseAdapter-class.html#SUB +web2py.gluon.dal.BaseAdapter.COALESCE_ZERO web2py.gluon.dal.BaseAdapter-class.html#COALESCE_ZERO +web2py.gluon.dal.BaseAdapter._delete web2py.gluon.dal.BaseAdapter-class.html#_delete +web2py.gluon.dal.BaseAdapter.BELONGS web2py.gluon.dal.BaseAdapter-class.html#BELONGS +web2py.gluon.dal.BaseAdapter.MUL web2py.gluon.dal.BaseAdapter-class.html#MUL +web2py.gluon.dal.BaseAdapter.select web2py.gluon.dal.BaseAdapter-class.html#select +web2py.gluon.dal.BaseAdapter.PRIMARY_KEY web2py.gluon.dal.BaseAdapter-class.html#PRIMARY_KEY +web2py.gluon.dal.MySQLAdapter.prepare web2py.gluon.dal.MySQLAdapter-class.html#prepare +web2py.gluon.dal.BaseAdapter.NE web2py.gluon.dal.BaseAdapter-class.html#NE +web2py.gluon.dal.MySQLAdapter.SUBSTRING web2py.gluon.dal.MySQLAdapter-class.html#SUBSTRING +web2py.gluon.dal.BaseAdapter.expand web2py.gluon.dal.BaseAdapter-class.html#expand +web2py.gluon.dal.BaseAdapter.EXTRACT web2py.gluon.dal.BaseAdapter-class.html#EXTRACT +web2py.gluon.dal.MySQLAdapter.commit_on_alter_table web2py.gluon.dal.MySQLAdapter-class.html#commit_on_alter_table +web2py.gluon.dal.BaseAdapter.represent_exceptions web2py.gluon.dal.BaseAdapter-class.html#represent_exceptions +web2py.gluon.dal.BaseAdapter.sequence_name web2py.gluon.dal.BaseAdapter-class.html#sequence_name +web2py.gluon.dal.BaseAdapter.ADD web2py.gluon.dal.BaseAdapter-class.html#ADD +web2py.gluon.dal.BaseAdapter.count web2py.gluon.dal.BaseAdapter-class.html#count +web2py.gluon.dal.BaseAdapter.UPPER web2py.gluon.dal.BaseAdapter-class.html#UPPER +web2py.gluon.dal.BaseAdapter.JOIN web2py.gluon.dal.BaseAdapter-class.html#JOIN +web2py.gluon.dal.BaseAdapter.LIKE web2py.gluon.dal.BaseAdapter-class.html#LIKE +web2py.gluon.dal.ConnectionPool.close_all_instances web2py.gluon.dal.ConnectionPool-class.html#close_all_instances +web2py.gluon.dal.BaseAdapter.file_exists web2py.gluon.dal.BaseAdapter-class.html#file_exists +web2py.gluon.dal.BaseAdapter.delete web2py.gluon.dal.BaseAdapter-class.html#delete +web2py.gluon.dal.BaseAdapter.LOWER web2py.gluon.dal.BaseAdapter-class.html#LOWER +web2py.gluon.dal.BaseAdapter._update web2py.gluon.dal.BaseAdapter-class.html#_update +web2py.gluon.dal.BaseAdapter.parse web2py.gluon.dal.BaseAdapter-class.html#parse +web2py.gluon.dal.BaseAdapter.ENDSWITH web2py.gluon.dal.BaseAdapter-class.html#ENDSWITH +web2py.gluon.dal.BaseAdapter.constraint_name web2py.gluon.dal.BaseAdapter-class.html#constraint_name +web2py.gluon.dal.BaseAdapter.DIV web2py.gluon.dal.BaseAdapter-class.html#DIV +web2py.gluon.dal.MySQLAdapter.__init__ web2py.gluon.dal.MySQLAdapter-class.html#__init__ +web2py.gluon.dal.BaseAdapter.filter_tenant web2py.gluon.dal.BaseAdapter-class.html#filter_tenant +web2py.gluon.dal.BaseAdapter.LE web2py.gluon.dal.BaseAdapter-class.html#LE +web2py.gluon.dal.BaseAdapter.file_open web2py.gluon.dal.BaseAdapter-class.html#file_open +web2py.gluon.dal.ConnectionPool.set_folder web2py.gluon.dal.ConnectionPool-class.html#set_folder +web2py.gluon.dal.BaseAdapter.LT web2py.gluon.dal.BaseAdapter-class.html#LT +web2py.gluon.dal.BaseAdapter.COMMA web2py.gluon.dal.BaseAdapter-class.html#COMMA +web2py.gluon.dal.BaseAdapter.create_table web2py.gluon.dal.BaseAdapter-class.html#create_table +web2py.gluon.dal.BaseAdapter.bulk_insert web2py.gluon.dal.BaseAdapter-class.html#bulk_insert +web2py.gluon.dal.BaseAdapter.AGGREGATE web2py.gluon.dal.BaseAdapter-class.html#AGGREGATE +web2py.gluon.dal.BaseAdapter.file_close web2py.gluon.dal.BaseAdapter-class.html#file_close +web2py.gluon.dal.MySQLAdapter.RANDOM web2py.gluon.dal.MySQLAdapter-class.html#RANDOM +web2py.gluon.dal.BaseAdapter.truncate web2py.gluon.dal.BaseAdapter-class.html#truncate +web2py.gluon.dal.MySQLAdapter.driver web2py.gluon.contrib.pymysql-module.html +web2py.gluon.dal.BaseAdapter.update web2py.gluon.dal.BaseAdapter-class.html#update +web2py.gluon.dal.BaseAdapter.AS web2py.gluon.dal.BaseAdapter-class.html#AS +web2py.gluon.dal.BaseAdapter.trigger_name web2py.gluon.dal.BaseAdapter-class.html#trigger_name +web2py.gluon.dal.MySQLAdapter.types web2py.gluon.dal.MySQLAdapter-class.html#types +web2py.gluon.dal.BaseAdapter._count web2py.gluon.dal.BaseAdapter-class.html#_count +web2py.gluon.dal.BaseAdapter.CONTAINS web2py.gluon.dal.BaseAdapter-class.html#CONTAINS +web2py.gluon.dal.BaseAdapter.INVERT web2py.gluon.dal.BaseAdapter-class.html#INVERT +web2py.gluon.dal.BaseAdapter.alias web2py.gluon.dal.BaseAdapter-class.html#alias +web2py.gluon.dal.MySQLAdapter.lastrowid web2py.gluon.dal.MySQLAdapter-class.html#lastrowid +web2py.gluon.dal.NoSQLAdapter web2py.gluon.dal.NoSQLAdapter-class.html +web2py.gluon.dal.NoSQLAdapter.represent web2py.gluon.dal.NoSQLAdapter-class.html#represent +web2py.gluon.dal.NoSQLAdapter.log_execute web2py.gluon.dal.NoSQLAdapter-class.html#log_execute +web2py.gluon.dal.BaseAdapter.select_limitby web2py.gluon.dal.BaseAdapter-class.html#select_limitby +web2py.gluon.dal.BaseAdapter._truncate web2py.gluon.dal.BaseAdapter-class.html#_truncate +web2py.gluon.dal.NoSQLAdapter.SUB web2py.gluon.dal.NoSQLAdapter-class.html#SUB +web2py.gluon.dal.BaseAdapter.tables web2py.gluon.dal.BaseAdapter-class.html#tables +web2py.gluon.dal.NoSQLAdapter.LEFT_JOIN web2py.gluon.dal.NoSQLAdapter-class.html#LEFT_JOIN +web2py.gluon.dal.NoSQLAdapter.distributed_transaction_begin web2py.gluon.dal.NoSQLAdapter-class.html#distributed_transaction_begin +web2py.gluon.dal.BaseAdapter.GT web2py.gluon.dal.BaseAdapter-class.html#GT +web2py.gluon.dal.NoSQLAdapter.rollback web2py.gluon.dal.NoSQLAdapter-class.html#rollback +web2py.gluon.dal.NoSQLAdapter.ON web2py.gluon.dal.NoSQLAdapter-class.html#ON +web2py.gluon.dal.BaseAdapter.ALLOW_NULL web2py.gluon.dal.BaseAdapter-class.html#ALLOW_NULL +web2py.gluon.dal.BaseAdapter.maxcharlength web2py.gluon.dal.BaseAdapter-class.html#maxcharlength +web2py.gluon.dal.BaseAdapter.GE web2py.gluon.dal.BaseAdapter-class.html#GE +web2py.gluon.dal.BaseAdapter.get_table web2py.gluon.dal.BaseAdapter-class.html#get_table +web2py.gluon.dal.BaseAdapter._drop web2py.gluon.dal.BaseAdapter-class.html#_drop +web2py.gluon.dal.NoSQLAdapter.integrity_error_class web2py.gluon.dal.NoSQLAdapter-class.html#integrity_error_class +web2py.gluon.dal.ConnectionPool.pool_connection web2py.gluon.dal.ConnectionPool-class.html#pool_connection +web2py.gluon.dal.NoSQLAdapter._select web2py.gluon.dal.NoSQLAdapter-class.html#_select +web2py.gluon.dal.BaseAdapter.insert web2py.gluon.dal.BaseAdapter-class.html#insert +web2py.gluon.dal.NoSQLAdapter.execute web2py.gluon.dal.NoSQLAdapter-class.html#execute +web2py.gluon.dal.NoSQLAdapter.drop web2py.gluon.dal.NoSQLAdapter-class.html#drop +web2py.gluon.dal.NoSQLAdapter.migrate_table web2py.gluon.dal.NoSQLAdapter-class.html#migrate_table +web2py.gluon.dal.NoSQLAdapter.concat_add web2py.gluon.dal.NoSQLAdapter-class.html#concat_add +web2py.gluon.dal.NoSQLAdapter._insert web2py.gluon.dal.NoSQLAdapter-class.html#_insert +web2py.gluon.dal.ConnectionPool.pools web2py.gluon.dal.ConnectionPool-class.html#pools +web2py.gluon.dal.BaseAdapter.support_distributed_transaction web2py.gluon.dal.BaseAdapter-class.html#support_distributed_transaction +web2py.gluon.dal.NoSQLAdapter.OR web2py.gluon.dal.NoSQLAdapter-class.html#OR +web2py.gluon.dal.BaseAdapter.file_delete web2py.gluon.dal.BaseAdapter-class.html#file_delete +web2py.gluon.dal.BaseAdapter.NOT_NULL web2py.gluon.dal.BaseAdapter-class.html#NOT_NULL +web2py.gluon.dal.ConnectionPool.find_or_make_work_folder web2py.gluon.dal.ConnectionPool-class.html#find_or_make_work_folder +web2py.gluon.dal.NoSQLAdapter.close web2py.gluon.dal.NoSQLAdapter-class.html#close +web2py.gluon.dal.BaseAdapter.integrity_error web2py.gluon.dal.BaseAdapter-class.html#integrity_error +web2py.gluon.dal.NoSQLAdapter.rowslice web2py.gluon.dal.NoSQLAdapter-class.html#rowslice +web2py.gluon.dal.NoSQLAdapter.create_sequence_and_triggers web2py.gluon.dal.NoSQLAdapter-class.html#create_sequence_and_triggers +web2py.gluon.dal.NoSQLAdapter.rollback_prepared web2py.gluon.dal.NoSQLAdapter-class.html#rollback_prepared +web2py.gluon.dal.NoSQLAdapter.commit_prepared web2py.gluon.dal.NoSQLAdapter-class.html#commit_prepared +web2py.gluon.dal.BaseAdapter.EQ web2py.gluon.dal.BaseAdapter-class.html#EQ +web2py.gluon.dal.NoSQLAdapter.AND web2py.gluon.dal.NoSQLAdapter-class.html#AND +web2py.gluon.dal.BaseAdapter.uploads_in_blob web2py.gluon.dal.BaseAdapter-class.html#uploads_in_blob +web2py.gluon.dal.BaseAdapter.NOT web2py.gluon.dal.BaseAdapter-class.html#NOT +web2py.gluon.dal.NoSQLAdapter.commit web2py.gluon.dal.NoSQLAdapter-class.html#commit +web2py.gluon.dal.BaseAdapter.MOD web2py.gluon.dal.BaseAdapter-class.html#MOD +web2py.gluon.dal.NoSQLAdapter.STARTSWITH web2py.gluon.dal.NoSQLAdapter-class.html#STARTSWITH +web2py.gluon.dal.NoSQLAdapter.to_unicode web2py.gluon.dal.NoSQLAdapter-class.html#to_unicode +web2py.gluon.dal.BaseAdapter.COALESCE_ZERO web2py.gluon.dal.BaseAdapter-class.html#COALESCE_ZERO +web2py.gluon.dal.NoSQLAdapter._delete web2py.gluon.dal.NoSQLAdapter-class.html#_delete +web2py.gluon.dal.BaseAdapter.BELONGS web2py.gluon.dal.BaseAdapter-class.html#BELONGS +web2py.gluon.dal.NoSQLAdapter.MUL web2py.gluon.dal.NoSQLAdapter-class.html#MUL +web2py.gluon.dal.BaseAdapter.select web2py.gluon.dal.BaseAdapter-class.html#select +web2py.gluon.dal.NoSQLAdapter.PRIMARY_KEY web2py.gluon.dal.NoSQLAdapter-class.html#PRIMARY_KEY +web2py.gluon.dal.NoSQLAdapter.prepare web2py.gluon.dal.NoSQLAdapter-class.html#prepare +web2py.gluon.dal.BaseAdapter.NE web2py.gluon.dal.BaseAdapter-class.html#NE +web2py.gluon.dal.NoSQLAdapter.SUBSTRING web2py.gluon.dal.NoSQLAdapter-class.html#SUBSTRING +web2py.gluon.dal.BaseAdapter.types web2py.gluon.dal.BaseAdapter-class.html#types +web2py.gluon.dal.NoSQLAdapter.EXTRACT web2py.gluon.dal.NoSQLAdapter-class.html#EXTRACT +web2py.gluon.dal.BaseAdapter.commit_on_alter_table web2py.gluon.dal.BaseAdapter-class.html#commit_on_alter_table +web2py.gluon.dal.NoSQLAdapter.represent_exceptions web2py.gluon.dal.NoSQLAdapter-class.html#represent_exceptions +web2py.gluon.dal.BaseAdapter.sequence_name web2py.gluon.dal.BaseAdapter-class.html#sequence_name +web2py.gluon.dal.NoSQLAdapter.ADD web2py.gluon.dal.NoSQLAdapter-class.html#ADD +web2py.gluon.dal.BaseAdapter.count web2py.gluon.dal.BaseAdapter-class.html#count +web2py.gluon.dal.NoSQLAdapter.UPPER web2py.gluon.dal.NoSQLAdapter-class.html#UPPER +web2py.gluon.dal.BaseAdapter.JOIN web2py.gluon.dal.BaseAdapter-class.html#JOIN +web2py.gluon.dal.NoSQLAdapter.LIKE web2py.gluon.dal.NoSQLAdapter-class.html#LIKE +web2py.gluon.dal.ConnectionPool.close_all_instances web2py.gluon.dal.ConnectionPool-class.html#close_all_instances +web2py.gluon.dal.BaseAdapter.file_exists web2py.gluon.dal.BaseAdapter-class.html#file_exists +web2py.gluon.dal.BaseAdapter.delete web2py.gluon.dal.BaseAdapter-class.html#delete +web2py.gluon.dal.NoSQLAdapter.LOWER web2py.gluon.dal.NoSQLAdapter-class.html#LOWER +web2py.gluon.dal.NoSQLAdapter._update web2py.gluon.dal.NoSQLAdapter-class.html#_update +web2py.gluon.dal.BaseAdapter.parse web2py.gluon.dal.BaseAdapter-class.html#parse +web2py.gluon.dal.NoSQLAdapter.ENDSWITH web2py.gluon.dal.NoSQLAdapter-class.html#ENDSWITH +web2py.gluon.dal.NoSQLAdapter.constraint_name web2py.gluon.dal.NoSQLAdapter-class.html#constraint_name +web2py.gluon.dal.NoSQLAdapter.DIV web2py.gluon.dal.NoSQLAdapter-class.html#DIV +web2py.gluon.dal.BaseAdapter.__init__ web2py.gluon.dal.BaseAdapter-class.html#__init__ +web2py.gluon.dal.BaseAdapter.filter_tenant web2py.gluon.dal.BaseAdapter-class.html#filter_tenant +web2py.gluon.dal.BaseAdapter.LE web2py.gluon.dal.BaseAdapter-class.html#LE +web2py.gluon.dal.BaseAdapter.file_open web2py.gluon.dal.BaseAdapter-class.html#file_open +web2py.gluon.dal.ConnectionPool.set_folder web2py.gluon.dal.ConnectionPool-class.html#set_folder +web2py.gluon.dal.BaseAdapter.LT web2py.gluon.dal.BaseAdapter-class.html#LT +web2py.gluon.dal.BaseAdapter.COMMA web2py.gluon.dal.BaseAdapter-class.html#COMMA +web2py.gluon.dal.BaseAdapter.create_table web2py.gluon.dal.BaseAdapter-class.html#create_table +web2py.gluon.dal.BaseAdapter.bulk_insert web2py.gluon.dal.BaseAdapter-class.html#bulk_insert +web2py.gluon.dal.NoSQLAdapter.AGGREGATE web2py.gluon.dal.NoSQLAdapter-class.html#AGGREGATE +web2py.gluon.dal.BaseAdapter.file_close web2py.gluon.dal.BaseAdapter-class.html#file_close +web2py.gluon.dal.NoSQLAdapter.RANDOM web2py.gluon.dal.NoSQLAdapter-class.html#RANDOM +web2py.gluon.dal.BaseAdapter.truncate web2py.gluon.dal.BaseAdapter-class.html#truncate +web2py.gluon.dal.BaseAdapter.driver web2py.gluon.dal.BaseAdapter-class.html#driver +web2py.gluon.dal.BaseAdapter.update web2py.gluon.dal.BaseAdapter-class.html#update +web2py.gluon.dal.NoSQLAdapter.AS web2py.gluon.dal.NoSQLAdapter-class.html#AS +web2py.gluon.dal.BaseAdapter.trigger_name web2py.gluon.dal.BaseAdapter-class.html#trigger_name +web2py.gluon.dal.BaseAdapter.expand web2py.gluon.dal.BaseAdapter-class.html#expand +web2py.gluon.dal.NoSQLAdapter._count web2py.gluon.dal.NoSQLAdapter-class.html#_count +web2py.gluon.dal.BaseAdapter.CONTAINS web2py.gluon.dal.BaseAdapter-class.html#CONTAINS +web2py.gluon.dal.BaseAdapter.INVERT web2py.gluon.dal.BaseAdapter-class.html#INVERT +web2py.gluon.dal.NoSQLAdapter.alias web2py.gluon.dal.NoSQLAdapter-class.html#alias +web2py.gluon.dal.NoSQLAdapter.lastrowid web2py.gluon.dal.NoSQLAdapter-class.html#lastrowid +web2py.gluon.dal.OracleAdapter web2py.gluon.dal.OracleAdapter-class.html +web2py.gluon.dal.BaseAdapter.represent web2py.gluon.dal.BaseAdapter-class.html#represent +web2py.gluon.dal.BaseAdapter.log_execute web2py.gluon.dal.BaseAdapter-class.html#log_execute +web2py.gluon.dal.OracleAdapter.select_limitby web2py.gluon.dal.OracleAdapter-class.html#select_limitby +web2py.gluon.dal.BaseAdapter._truncate web2py.gluon.dal.BaseAdapter-class.html#_truncate +web2py.gluon.dal.BaseAdapter.tables web2py.gluon.dal.BaseAdapter-class.html#tables +web2py.gluon.dal.OracleAdapter.LEFT_JOIN web2py.gluon.dal.OracleAdapter-class.html#LEFT_JOIN +web2py.gluon.dal.BaseAdapter.distributed_transaction_begin web2py.gluon.dal.BaseAdapter-class.html#distributed_transaction_begin +web2py.gluon.dal.BaseAdapter.GT web2py.gluon.dal.BaseAdapter-class.html#GT +web2py.gluon.dal.BaseAdapter.rollback web2py.gluon.dal.BaseAdapter-class.html#rollback +web2py.gluon.dal.BaseAdapter.ON web2py.gluon.dal.BaseAdapter-class.html#ON +web2py.gluon.dal.BaseAdapter.ALLOW_NULL web2py.gluon.dal.BaseAdapter-class.html#ALLOW_NULL +web2py.gluon.dal.BaseAdapter.maxcharlength web2py.gluon.dal.BaseAdapter-class.html#maxcharlength +web2py.gluon.dal.BaseAdapter.GE web2py.gluon.dal.BaseAdapter-class.html#GE +web2py.gluon.dal.BaseAdapter.get_table web2py.gluon.dal.BaseAdapter-class.html#get_table +web2py.gluon.dal.OracleAdapter._drop web2py.gluon.dal.OracleAdapter-class.html#_drop +web2py.gluon.dal.BaseAdapter.integrity_error_class web2py.gluon.dal.BaseAdapter-class.html#integrity_error_class +web2py.gluon.dal.ConnectionPool.pool_connection web2py.gluon.dal.ConnectionPool-class.html#pool_connection +web2py.gluon.dal.BaseAdapter._select web2py.gluon.dal.BaseAdapter-class.html#_select +web2py.gluon.dal.BaseAdapter.insert web2py.gluon.dal.BaseAdapter-class.html#insert +web2py.gluon.dal.OracleAdapter.execute web2py.gluon.dal.OracleAdapter-class.html#execute +web2py.gluon.dal.BaseAdapter.drop web2py.gluon.dal.BaseAdapter-class.html#drop +web2py.gluon.dal.BaseAdapter.migrate_table web2py.gluon.dal.BaseAdapter-class.html#migrate_table +web2py.gluon.dal.BaseAdapter.concat_add web2py.gluon.dal.BaseAdapter-class.html#concat_add +web2py.gluon.dal.BaseAdapter._insert web2py.gluon.dal.BaseAdapter-class.html#_insert +web2py.gluon.dal.ConnectionPool.pools web2py.gluon.dal.ConnectionPool-class.html#pools +web2py.gluon.dal.BaseAdapter.support_distributed_transaction web2py.gluon.dal.BaseAdapter-class.html#support_distributed_transaction +web2py.gluon.dal.BaseAdapter.OR web2py.gluon.dal.BaseAdapter-class.html#OR +web2py.gluon.dal.BaseAdapter.file_delete web2py.gluon.dal.BaseAdapter-class.html#file_delete +web2py.gluon.dal.OracleAdapter.NOT_NULL web2py.gluon.dal.OracleAdapter-class.html#NOT_NULL +web2py.gluon.dal.ConnectionPool.find_or_make_work_folder web2py.gluon.dal.ConnectionPool-class.html#find_or_make_work_folder +web2py.gluon.dal.BaseAdapter.close web2py.gluon.dal.BaseAdapter-class.html#close +web2py.gluon.dal.BaseAdapter.integrity_error web2py.gluon.dal.BaseAdapter-class.html#integrity_error +web2py.gluon.dal.BaseAdapter.rowslice web2py.gluon.dal.BaseAdapter-class.html#rowslice +web2py.gluon.dal.OracleAdapter.create_sequence_and_triggers web2py.gluon.dal.OracleAdapter-class.html#create_sequence_and_triggers +web2py.gluon.dal.BaseAdapter.rollback_prepared web2py.gluon.dal.BaseAdapter-class.html#rollback_prepared +web2py.gluon.dal.BaseAdapter.commit_prepared web2py.gluon.dal.BaseAdapter-class.html#commit_prepared +web2py.gluon.dal.BaseAdapter.EQ web2py.gluon.dal.BaseAdapter-class.html#EQ +web2py.gluon.dal.BaseAdapter.AND web2py.gluon.dal.BaseAdapter-class.html#AND +web2py.gluon.dal.BaseAdapter.uploads_in_blob web2py.gluon.dal.BaseAdapter-class.html#uploads_in_blob +web2py.gluon.dal.BaseAdapter.NOT web2py.gluon.dal.BaseAdapter-class.html#NOT +web2py.gluon.dal.BaseAdapter.commit web2py.gluon.dal.BaseAdapter-class.html#commit +web2py.gluon.dal.BaseAdapter.MOD web2py.gluon.dal.BaseAdapter-class.html#MOD +web2py.gluon.dal.BaseAdapter.STARTSWITH web2py.gluon.dal.BaseAdapter-class.html#STARTSWITH +web2py.gluon.dal.BaseAdapter.SUB web2py.gluon.dal.BaseAdapter-class.html#SUB +web2py.gluon.dal.BaseAdapter.COALESCE_ZERO web2py.gluon.dal.BaseAdapter-class.html#COALESCE_ZERO +web2py.gluon.dal.BaseAdapter._delete web2py.gluon.dal.BaseAdapter-class.html#_delete +web2py.gluon.dal.BaseAdapter.BELONGS web2py.gluon.dal.BaseAdapter-class.html#BELONGS +web2py.gluon.dal.BaseAdapter.MUL web2py.gluon.dal.BaseAdapter-class.html#MUL +web2py.gluon.dal.BaseAdapter.select web2py.gluon.dal.BaseAdapter-class.html#select +web2py.gluon.dal.OracleAdapter.oracle_fix web2py.gluon.dal.OracleAdapter-class.html#oracle_fix +web2py.gluon.dal.BaseAdapter.PRIMARY_KEY web2py.gluon.dal.BaseAdapter-class.html#PRIMARY_KEY +web2py.gluon.dal.BaseAdapter.prepare web2py.gluon.dal.BaseAdapter-class.html#prepare +web2py.gluon.dal.BaseAdapter.NE web2py.gluon.dal.BaseAdapter-class.html#NE +web2py.gluon.dal.BaseAdapter.SUBSTRING web2py.gluon.dal.BaseAdapter-class.html#SUBSTRING +web2py.gluon.dal.BaseAdapter.expand web2py.gluon.dal.BaseAdapter-class.html#expand +web2py.gluon.dal.BaseAdapter.EXTRACT web2py.gluon.dal.BaseAdapter-class.html#EXTRACT +web2py.gluon.dal.OracleAdapter.commit_on_alter_table web2py.gluon.dal.OracleAdapter-class.html#commit_on_alter_table +web2py.gluon.dal.OracleAdapter.represent_exceptions web2py.gluon.dal.OracleAdapter-class.html#represent_exceptions +web2py.gluon.dal.OracleAdapter.sequence_name web2py.gluon.dal.OracleAdapter-class.html#sequence_name +web2py.gluon.dal.BaseAdapter.ADD web2py.gluon.dal.BaseAdapter-class.html#ADD +web2py.gluon.dal.BaseAdapter.count web2py.gluon.dal.BaseAdapter-class.html#count +web2py.gluon.dal.BaseAdapter.UPPER web2py.gluon.dal.BaseAdapter-class.html#UPPER +web2py.gluon.dal.BaseAdapter.JOIN web2py.gluon.dal.BaseAdapter-class.html#JOIN +web2py.gluon.dal.BaseAdapter.LIKE web2py.gluon.dal.BaseAdapter-class.html#LIKE +web2py.gluon.dal.ConnectionPool.close_all_instances web2py.gluon.dal.ConnectionPool-class.html#close_all_instances +web2py.gluon.dal.BaseAdapter.file_exists web2py.gluon.dal.BaseAdapter-class.html#file_exists +web2py.gluon.dal.BaseAdapter.delete web2py.gluon.dal.BaseAdapter-class.html#delete +web2py.gluon.dal.BaseAdapter.LOWER web2py.gluon.dal.BaseAdapter-class.html#LOWER +web2py.gluon.dal.BaseAdapter._update web2py.gluon.dal.BaseAdapter-class.html#_update +web2py.gluon.dal.BaseAdapter.parse web2py.gluon.dal.BaseAdapter-class.html#parse +web2py.gluon.dal.BaseAdapter.ENDSWITH web2py.gluon.dal.BaseAdapter-class.html#ENDSWITH +web2py.gluon.dal.OracleAdapter.constraint_name web2py.gluon.dal.OracleAdapter-class.html#constraint_name +web2py.gluon.dal.BaseAdapter.DIV web2py.gluon.dal.BaseAdapter-class.html#DIV +web2py.gluon.dal.OracleAdapter.__init__ web2py.gluon.dal.OracleAdapter-class.html#__init__ +web2py.gluon.dal.BaseAdapter.filter_tenant web2py.gluon.dal.BaseAdapter-class.html#filter_tenant +web2py.gluon.dal.BaseAdapter.LE web2py.gluon.dal.BaseAdapter-class.html#LE +web2py.gluon.dal.BaseAdapter.file_open web2py.gluon.dal.BaseAdapter-class.html#file_open +web2py.gluon.dal.ConnectionPool.set_folder web2py.gluon.dal.ConnectionPool-class.html#set_folder +web2py.gluon.dal.BaseAdapter.LT web2py.gluon.dal.BaseAdapter-class.html#LT +web2py.gluon.dal.BaseAdapter.COMMA web2py.gluon.dal.BaseAdapter-class.html#COMMA +web2py.gluon.dal.BaseAdapter.create_table web2py.gluon.dal.BaseAdapter-class.html#create_table +web2py.gluon.dal.BaseAdapter.bulk_insert web2py.gluon.dal.BaseAdapter-class.html#bulk_insert +web2py.gluon.dal.BaseAdapter.AGGREGATE web2py.gluon.dal.BaseAdapter-class.html#AGGREGATE +web2py.gluon.dal.BaseAdapter.file_close web2py.gluon.dal.BaseAdapter-class.html#file_close +web2py.gluon.dal.OracleAdapter.RANDOM web2py.gluon.dal.OracleAdapter-class.html#RANDOM +web2py.gluon.dal.BaseAdapter.truncate web2py.gluon.dal.BaseAdapter-class.html#truncate +web2py.gluon.dal.OracleAdapter.driver web2py.gluon.dal.OracleAdapter-class.html#driver +web2py.gluon.dal.BaseAdapter.update web2py.gluon.dal.BaseAdapter-class.html#update +web2py.gluon.dal.BaseAdapter.AS web2py.gluon.dal.BaseAdapter-class.html#AS +web2py.gluon.dal.OracleAdapter.trigger_name web2py.gluon.dal.OracleAdapter-class.html#trigger_name +web2py.gluon.dal.OracleAdapter.types web2py.gluon.dal.OracleAdapter-class.html#types +web2py.gluon.dal.BaseAdapter._count web2py.gluon.dal.BaseAdapter-class.html#_count +web2py.gluon.dal.BaseAdapter.CONTAINS web2py.gluon.dal.BaseAdapter-class.html#CONTAINS +web2py.gluon.dal.BaseAdapter.INVERT web2py.gluon.dal.BaseAdapter-class.html#INVERT +web2py.gluon.dal.BaseAdapter.alias web2py.gluon.dal.BaseAdapter-class.html#alias +web2py.gluon.dal.OracleAdapter.lastrowid web2py.gluon.dal.OracleAdapter-class.html#lastrowid +web2py.gluon.dal.PostgreSQLAdapter web2py.gluon.dal.PostgreSQLAdapter-class.html +web2py.gluon.dal.BaseAdapter.represent web2py.gluon.dal.BaseAdapter-class.html#represent +web2py.gluon.dal.BaseAdapter.log_execute web2py.gluon.dal.BaseAdapter-class.html#log_execute +web2py.gluon.dal.BaseAdapter.select_limitby web2py.gluon.dal.BaseAdapter-class.html#select_limitby +web2py.gluon.dal.BaseAdapter._truncate web2py.gluon.dal.BaseAdapter-class.html#_truncate +web2py.gluon.dal.BaseAdapter.tables web2py.gluon.dal.BaseAdapter-class.html#tables +web2py.gluon.dal.BaseAdapter.LEFT_JOIN web2py.gluon.dal.BaseAdapter-class.html#LEFT_JOIN +web2py.gluon.dal.PostgreSQLAdapter.distributed_transaction_begin web2py.gluon.dal.PostgreSQLAdapter-class.html#distributed_transaction_begin +web2py.gluon.dal.BaseAdapter.GT web2py.gluon.dal.BaseAdapter-class.html#GT +web2py.gluon.dal.BaseAdapter.rollback web2py.gluon.dal.BaseAdapter-class.html#rollback +web2py.gluon.dal.BaseAdapter.ON web2py.gluon.dal.BaseAdapter-class.html#ON +web2py.gluon.dal.BaseAdapter.ALLOW_NULL web2py.gluon.dal.BaseAdapter-class.html#ALLOW_NULL +web2py.gluon.dal.BaseAdapter.maxcharlength web2py.gluon.dal.BaseAdapter-class.html#maxcharlength +web2py.gluon.dal.BaseAdapter.GE web2py.gluon.dal.BaseAdapter-class.html#GE +web2py.gluon.dal.BaseAdapter.get_table web2py.gluon.dal.BaseAdapter-class.html#get_table +web2py.gluon.dal.BaseAdapter._drop web2py.gluon.dal.BaseAdapter-class.html#_drop +web2py.gluon.dal.BaseAdapter.integrity_error_class web2py.gluon.dal.BaseAdapter-class.html#integrity_error_class +web2py.gluon.dal.ConnectionPool.pool_connection web2py.gluon.dal.ConnectionPool-class.html#pool_connection +web2py.gluon.dal.BaseAdapter._select web2py.gluon.dal.BaseAdapter-class.html#_select +web2py.gluon.dal.BaseAdapter.insert web2py.gluon.dal.BaseAdapter-class.html#insert +web2py.gluon.dal.BaseAdapter.execute web2py.gluon.dal.BaseAdapter-class.html#execute +web2py.gluon.dal.BaseAdapter.drop web2py.gluon.dal.BaseAdapter-class.html#drop +web2py.gluon.dal.BaseAdapter.migrate_table web2py.gluon.dal.BaseAdapter-class.html#migrate_table +web2py.gluon.dal.BaseAdapter.concat_add web2py.gluon.dal.BaseAdapter-class.html#concat_add +web2py.gluon.dal.BaseAdapter._insert web2py.gluon.dal.BaseAdapter-class.html#_insert +web2py.gluon.dal.ConnectionPool.pools web2py.gluon.dal.ConnectionPool-class.html#pools +web2py.gluon.dal.PostgreSQLAdapter.support_distributed_transaction web2py.gluon.dal.PostgreSQLAdapter-class.html#support_distributed_transaction +web2py.gluon.dal.BaseAdapter.OR web2py.gluon.dal.BaseAdapter-class.html#OR +web2py.gluon.dal.BaseAdapter.file_delete web2py.gluon.dal.BaseAdapter-class.html#file_delete +web2py.gluon.dal.BaseAdapter.NOT_NULL web2py.gluon.dal.BaseAdapter-class.html#NOT_NULL +web2py.gluon.dal.ConnectionPool.find_or_make_work_folder web2py.gluon.dal.ConnectionPool-class.html#find_or_make_work_folder +web2py.gluon.dal.BaseAdapter.close web2py.gluon.dal.BaseAdapter-class.html#close +web2py.gluon.dal.BaseAdapter.integrity_error web2py.gluon.dal.BaseAdapter-class.html#integrity_error +web2py.gluon.dal.BaseAdapter.rowslice web2py.gluon.dal.BaseAdapter-class.html#rowslice +web2py.gluon.dal.PostgreSQLAdapter.create_sequence_and_triggers web2py.gluon.dal.PostgreSQLAdapter-class.html#create_sequence_and_triggers +web2py.gluon.dal.PostgreSQLAdapter.rollback_prepared web2py.gluon.dal.PostgreSQLAdapter-class.html#rollback_prepared +web2py.gluon.dal.PostgreSQLAdapter.commit_prepared web2py.gluon.dal.PostgreSQLAdapter-class.html#commit_prepared +web2py.gluon.dal.BaseAdapter.EQ web2py.gluon.dal.BaseAdapter-class.html#EQ +web2py.gluon.dal.BaseAdapter.AND web2py.gluon.dal.BaseAdapter-class.html#AND +web2py.gluon.dal.BaseAdapter.uploads_in_blob web2py.gluon.dal.BaseAdapter-class.html#uploads_in_blob +web2py.gluon.dal.BaseAdapter.NOT web2py.gluon.dal.BaseAdapter-class.html#NOT +web2py.gluon.dal.BaseAdapter.commit web2py.gluon.dal.BaseAdapter-class.html#commit +web2py.gluon.dal.BaseAdapter.MOD web2py.gluon.dal.BaseAdapter-class.html#MOD +web2py.gluon.dal.PostgreSQLAdapter.STARTSWITH web2py.gluon.dal.PostgreSQLAdapter-class.html#STARTSWITH +web2py.gluon.dal.BaseAdapter.SUB web2py.gluon.dal.BaseAdapter-class.html#SUB +web2py.gluon.dal.BaseAdapter.COALESCE_ZERO web2py.gluon.dal.BaseAdapter-class.html#COALESCE_ZERO +web2py.gluon.dal.BaseAdapter._delete web2py.gluon.dal.BaseAdapter-class.html#_delete +web2py.gluon.dal.BaseAdapter.BELONGS web2py.gluon.dal.BaseAdapter-class.html#BELONGS +web2py.gluon.dal.BaseAdapter.MUL web2py.gluon.dal.BaseAdapter-class.html#MUL +web2py.gluon.dal.BaseAdapter.select web2py.gluon.dal.BaseAdapter-class.html#select +web2py.gluon.dal.BaseAdapter.PRIMARY_KEY web2py.gluon.dal.BaseAdapter-class.html#PRIMARY_KEY +web2py.gluon.dal.PostgreSQLAdapter.prepare web2py.gluon.dal.PostgreSQLAdapter-class.html#prepare +web2py.gluon.dal.BaseAdapter.NE web2py.gluon.dal.BaseAdapter-class.html#NE +web2py.gluon.dal.BaseAdapter.SUBSTRING web2py.gluon.dal.BaseAdapter-class.html#SUBSTRING +web2py.gluon.dal.PostgreSQLAdapter.types web2py.gluon.dal.PostgreSQLAdapter-class.html#types +web2py.gluon.dal.BaseAdapter.EXTRACT web2py.gluon.dal.BaseAdapter-class.html#EXTRACT +web2py.gluon.dal.BaseAdapter.commit_on_alter_table web2py.gluon.dal.BaseAdapter-class.html#commit_on_alter_table +web2py.gluon.dal.BaseAdapter.represent_exceptions web2py.gluon.dal.BaseAdapter-class.html#represent_exceptions +web2py.gluon.dal.PostgreSQLAdapter.sequence_name web2py.gluon.dal.PostgreSQLAdapter-class.html#sequence_name +web2py.gluon.dal.BaseAdapter.ADD web2py.gluon.dal.BaseAdapter-class.html#ADD +web2py.gluon.dal.BaseAdapter.count web2py.gluon.dal.BaseAdapter-class.html#count +web2py.gluon.dal.BaseAdapter.UPPER web2py.gluon.dal.BaseAdapter-class.html#UPPER +web2py.gluon.dal.BaseAdapter.JOIN web2py.gluon.dal.BaseAdapter-class.html#JOIN +web2py.gluon.dal.PostgreSQLAdapter.LIKE web2py.gluon.dal.PostgreSQLAdapter-class.html#LIKE +web2py.gluon.dal.ConnectionPool.close_all_instances web2py.gluon.dal.ConnectionPool-class.html#close_all_instances +web2py.gluon.dal.BaseAdapter.file_exists web2py.gluon.dal.BaseAdapter-class.html#file_exists +web2py.gluon.dal.BaseAdapter.delete web2py.gluon.dal.BaseAdapter-class.html#delete +web2py.gluon.dal.BaseAdapter.LOWER web2py.gluon.dal.BaseAdapter-class.html#LOWER +web2py.gluon.dal.BaseAdapter._update web2py.gluon.dal.BaseAdapter-class.html#_update +web2py.gluon.dal.BaseAdapter.parse web2py.gluon.dal.BaseAdapter-class.html#parse +web2py.gluon.dal.PostgreSQLAdapter.ENDSWITH web2py.gluon.dal.PostgreSQLAdapter-class.html#ENDSWITH +web2py.gluon.dal.BaseAdapter.constraint_name web2py.gluon.dal.BaseAdapter-class.html#constraint_name +web2py.gluon.dal.BaseAdapter.DIV web2py.gluon.dal.BaseAdapter-class.html#DIV +web2py.gluon.dal.PostgreSQLAdapter.__init__ web2py.gluon.dal.PostgreSQLAdapter-class.html#__init__ +web2py.gluon.dal.BaseAdapter.filter_tenant web2py.gluon.dal.BaseAdapter-class.html#filter_tenant +web2py.gluon.dal.BaseAdapter.LE web2py.gluon.dal.BaseAdapter-class.html#LE +web2py.gluon.dal.BaseAdapter.file_open web2py.gluon.dal.BaseAdapter-class.html#file_open +web2py.gluon.dal.ConnectionPool.set_folder web2py.gluon.dal.ConnectionPool-class.html#set_folder +web2py.gluon.dal.BaseAdapter.LT web2py.gluon.dal.BaseAdapter-class.html#LT +web2py.gluon.dal.BaseAdapter.COMMA web2py.gluon.dal.BaseAdapter-class.html#COMMA +web2py.gluon.dal.BaseAdapter.create_table web2py.gluon.dal.BaseAdapter-class.html#create_table +web2py.gluon.dal.BaseAdapter.bulk_insert web2py.gluon.dal.BaseAdapter-class.html#bulk_insert +web2py.gluon.dal.BaseAdapter.AGGREGATE web2py.gluon.dal.BaseAdapter-class.html#AGGREGATE +web2py.gluon.dal.BaseAdapter.file_close web2py.gluon.dal.BaseAdapter-class.html#file_close +web2py.gluon.dal.PostgreSQLAdapter.RANDOM web2py.gluon.dal.PostgreSQLAdapter-class.html#RANDOM +web2py.gluon.dal.BaseAdapter.truncate web2py.gluon.dal.BaseAdapter-class.html#truncate +web2py.gluon.dal.PostgreSQLAdapter.driver psycopg2-module.html +web2py.gluon.dal.BaseAdapter.update web2py.gluon.dal.BaseAdapter-class.html#update +web2py.gluon.dal.BaseAdapter.AS web2py.gluon.dal.BaseAdapter-class.html#AS +web2py.gluon.dal.BaseAdapter.trigger_name web2py.gluon.dal.BaseAdapter-class.html#trigger_name +web2py.gluon.dal.BaseAdapter.expand web2py.gluon.dal.BaseAdapter-class.html#expand +web2py.gluon.dal.BaseAdapter._count web2py.gluon.dal.BaseAdapter-class.html#_count +web2py.gluon.dal.PostgreSQLAdapter.CONTAINS web2py.gluon.dal.PostgreSQLAdapter-class.html#CONTAINS +web2py.gluon.dal.BaseAdapter.INVERT web2py.gluon.dal.BaseAdapter-class.html#INVERT +web2py.gluon.dal.BaseAdapter.alias web2py.gluon.dal.BaseAdapter-class.html#alias +web2py.gluon.dal.PostgreSQLAdapter.lastrowid web2py.gluon.dal.PostgreSQLAdapter-class.html#lastrowid +web2py.gluon.dal.Query web2py.gluon.dal.Query-class.html +web2py.gluon.dal.Query.__invert__ web2py.gluon.dal.Query-class.html#__invert__ +web2py.gluon.dal.Query.__str__ web2py.gluon.dal.Query-class.html#__str__ +web2py.gluon.dal.Query.__or__ web2py.gluon.dal.Query-class.html#__or__ +web2py.gluon.dal.Query.__and__ web2py.gluon.dal.Query-class.html#__and__ +web2py.gluon.dal.Query.__init__ web2py.gluon.dal.Query-class.html#__init__ +web2py.gluon.dal.Reference web2py.gluon.dal.Reference-class.html +web2py.gluon.dal.Reference.__setattr__ web2py.gluon.dal.Reference-class.html#__setattr__ +web2py.gluon.dal.Reference.__getattr__ web2py.gluon.dal.Reference-class.html#__getattr__ +web2py.gluon.dal.Reference.__getitem__ web2py.gluon.dal.Reference-class.html#__getitem__ +web2py.gluon.dal.Reference.__setitem__ web2py.gluon.dal.Reference-class.html#__setitem__ +web2py.gluon.dal.Reference.__allocate web2py.gluon.dal.Reference-class.html#__allocate +web2py.gluon.dal.Row web2py.gluon.dal.Row-class.html +web2py.gluon.dal.Row.__int__ web2py.gluon.dal.Row-class.html#__int__ +web2py.gluon.dal.Row.__setattr__ web2py.gluon.dal.Row-class.html#__setattr__ +web2py.gluon.dal.Row.__getattr__ web2py.gluon.dal.Row-class.html#__getattr__ +web2py.gluon.dal.Row.__call__ web2py.gluon.dal.Row-class.html#__call__ +web2py.gluon.dal.Row.__ne__ web2py.gluon.dal.Row-class.html#__ne__ +web2py.gluon.dal.Row.__getitem__ web2py.gluon.dal.Row-class.html#__getitem__ +web2py.gluon.dal.Row.as_dict web2py.gluon.dal.Row-class.html#as_dict +web2py.gluon.dal.Row.__setitem__ web2py.gluon.dal.Row-class.html#__setitem__ +web2py.gluon.dal.Row.__eq__ web2py.gluon.dal.Row-class.html#__eq__ +web2py.gluon.dal.Row.__repr__ web2py.gluon.dal.Row-class.html#__repr__ +web2py.gluon.dal.Row.__copy__ web2py.gluon.dal.Row-class.html#__copy__ +web2py.gluon.dal.Rows web2py.gluon.dal.Rows-class.html +web2py.gluon.dal.Rows.__getslice__ web2py.gluon.dal.Rows-class.html#__getslice__ +web2py.gluon.dal.Rows.__str__ web2py.gluon.dal.Rows-class.html#__str__ +web2py.gluon.dal.Rows.__and__ web2py.gluon.dal.Rows-class.html#__and__ +web2py.gluon.dal.Rows.exclude web2py.gluon.dal.Rows-class.html#exclude +web2py.gluon.dal.Rows.find web2py.gluon.dal.Rows-class.html#find +web2py.gluon.dal.Rows.__init__ web2py.gluon.dal.Rows-class.html#__init__ +web2py.gluon.dal.Rows.xml web2py.gluon.dal.Rows-class.html#xml +web2py.gluon.dal.Rows.setvirtualfields web2py.gluon.dal.Rows-class.html#setvirtualfields +web2py.gluon.dal.Rows.export_to_csv_file web2py.gluon.dal.Rows-class.html#export_to_csv_file +web2py.gluon.dal.Rows.json web2py.gluon.dal.Rows-class.html#json +web2py.gluon.dal.Rows.__len__ web2py.gluon.dal.Rows-class.html#__len__ +web2py.gluon.dal.Rows.sort web2py.gluon.dal.Rows-class.html#sort +web2py.gluon.dal.Rows.__getitem__ web2py.gluon.dal.Rows-class.html#__getitem__ +web2py.gluon.dal.Rows.as_dict web2py.gluon.dal.Rows-class.html#as_dict +web2py.gluon.dal.Rows.__iter__ web2py.gluon.dal.Rows-class.html#__iter__ +web2py.gluon.dal.Rows.__or__ web2py.gluon.dal.Rows-class.html#__or__ +web2py.gluon.dal.Rows.last web2py.gluon.dal.Rows-class.html#last +web2py.gluon.dal.Rows.__nonzero__ web2py.gluon.dal.Rows-class.html#__nonzero__ +web2py.gluon.dal.Rows.as_list web2py.gluon.dal.Rows-class.html#as_list +web2py.gluon.dal.Rows.first web2py.gluon.dal.Rows-class.html#first +web2py.gluon.dal.SAPDBAdapter web2py.gluon.dal.SAPDBAdapter-class.html +web2py.gluon.dal.BaseAdapter.represent web2py.gluon.dal.BaseAdapter-class.html#represent +web2py.gluon.dal.BaseAdapter.log_execute web2py.gluon.dal.BaseAdapter-class.html#log_execute +web2py.gluon.dal.SAPDBAdapter.select_limitby web2py.gluon.dal.SAPDBAdapter-class.html#select_limitby +web2py.gluon.dal.BaseAdapter._truncate web2py.gluon.dal.BaseAdapter-class.html#_truncate +web2py.gluon.dal.BaseAdapter.tables web2py.gluon.dal.BaseAdapter-class.html#tables +web2py.gluon.dal.BaseAdapter.LEFT_JOIN web2py.gluon.dal.BaseAdapter-class.html#LEFT_JOIN +web2py.gluon.dal.BaseAdapter.distributed_transaction_begin web2py.gluon.dal.BaseAdapter-class.html#distributed_transaction_begin +web2py.gluon.dal.BaseAdapter.GT web2py.gluon.dal.BaseAdapter-class.html#GT +web2py.gluon.dal.BaseAdapter.rollback web2py.gluon.dal.BaseAdapter-class.html#rollback +web2py.gluon.dal.BaseAdapter.ON web2py.gluon.dal.BaseAdapter-class.html#ON +web2py.gluon.dal.BaseAdapter.ALLOW_NULL web2py.gluon.dal.BaseAdapter-class.html#ALLOW_NULL +web2py.gluon.dal.BaseAdapter.maxcharlength web2py.gluon.dal.BaseAdapter-class.html#maxcharlength +web2py.gluon.dal.BaseAdapter.GE web2py.gluon.dal.BaseAdapter-class.html#GE +web2py.gluon.dal.BaseAdapter.get_table web2py.gluon.dal.BaseAdapter-class.html#get_table +web2py.gluon.dal.BaseAdapter._drop web2py.gluon.dal.BaseAdapter-class.html#_drop +web2py.gluon.dal.BaseAdapter.integrity_error_class web2py.gluon.dal.BaseAdapter-class.html#integrity_error_class +web2py.gluon.dal.ConnectionPool.pool_connection web2py.gluon.dal.ConnectionPool-class.html#pool_connection +web2py.gluon.dal.BaseAdapter._select web2py.gluon.dal.BaseAdapter-class.html#_select +web2py.gluon.dal.BaseAdapter.insert web2py.gluon.dal.BaseAdapter-class.html#insert +web2py.gluon.dal.BaseAdapter.execute web2py.gluon.dal.BaseAdapter-class.html#execute +web2py.gluon.dal.BaseAdapter.drop web2py.gluon.dal.BaseAdapter-class.html#drop +web2py.gluon.dal.BaseAdapter.migrate_table web2py.gluon.dal.BaseAdapter-class.html#migrate_table +web2py.gluon.dal.BaseAdapter.concat_add web2py.gluon.dal.BaseAdapter-class.html#concat_add +web2py.gluon.dal.BaseAdapter._insert web2py.gluon.dal.BaseAdapter-class.html#_insert +web2py.gluon.dal.ConnectionPool.pools web2py.gluon.dal.ConnectionPool-class.html#pools +web2py.gluon.dal.SAPDBAdapter.support_distributed_transaction web2py.gluon.dal.SAPDBAdapter-class.html#support_distributed_transaction +web2py.gluon.dal.BaseAdapter.OR web2py.gluon.dal.BaseAdapter-class.html#OR +web2py.gluon.dal.BaseAdapter.file_delete web2py.gluon.dal.BaseAdapter-class.html#file_delete +web2py.gluon.dal.BaseAdapter.NOT_NULL web2py.gluon.dal.BaseAdapter-class.html#NOT_NULL +web2py.gluon.dal.ConnectionPool.find_or_make_work_folder web2py.gluon.dal.ConnectionPool-class.html#find_or_make_work_folder +web2py.gluon.dal.BaseAdapter.close web2py.gluon.dal.BaseAdapter-class.html#close +web2py.gluon.dal.BaseAdapter.integrity_error web2py.gluon.dal.BaseAdapter-class.html#integrity_error +web2py.gluon.dal.BaseAdapter.rowslice web2py.gluon.dal.BaseAdapter-class.html#rowslice +web2py.gluon.dal.SAPDBAdapter.create_sequence_and_triggers web2py.gluon.dal.SAPDBAdapter-class.html#create_sequence_and_triggers +web2py.gluon.dal.BaseAdapter.rollback_prepared web2py.gluon.dal.BaseAdapter-class.html#rollback_prepared +web2py.gluon.dal.BaseAdapter.commit_prepared web2py.gluon.dal.BaseAdapter-class.html#commit_prepared +web2py.gluon.dal.BaseAdapter.EQ web2py.gluon.dal.BaseAdapter-class.html#EQ +web2py.gluon.dal.BaseAdapter.AND web2py.gluon.dal.BaseAdapter-class.html#AND +web2py.gluon.dal.BaseAdapter.uploads_in_blob web2py.gluon.dal.BaseAdapter-class.html#uploads_in_blob +web2py.gluon.dal.BaseAdapter.NOT web2py.gluon.dal.BaseAdapter-class.html#NOT +web2py.gluon.dal.BaseAdapter.commit web2py.gluon.dal.BaseAdapter-class.html#commit +web2py.gluon.dal.BaseAdapter.MOD web2py.gluon.dal.BaseAdapter-class.html#MOD +web2py.gluon.dal.BaseAdapter.STARTSWITH web2py.gluon.dal.BaseAdapter-class.html#STARTSWITH +web2py.gluon.dal.BaseAdapter.SUB web2py.gluon.dal.BaseAdapter-class.html#SUB +web2py.gluon.dal.BaseAdapter.COALESCE_ZERO web2py.gluon.dal.BaseAdapter-class.html#COALESCE_ZERO +web2py.gluon.dal.BaseAdapter._delete web2py.gluon.dal.BaseAdapter-class.html#_delete +web2py.gluon.dal.BaseAdapter.BELONGS web2py.gluon.dal.BaseAdapter-class.html#BELONGS +web2py.gluon.dal.BaseAdapter.MUL web2py.gluon.dal.BaseAdapter-class.html#MUL +web2py.gluon.dal.BaseAdapter.select web2py.gluon.dal.BaseAdapter-class.html#select +web2py.gluon.dal.BaseAdapter.PRIMARY_KEY web2py.gluon.dal.BaseAdapter-class.html#PRIMARY_KEY +web2py.gluon.dal.BaseAdapter.prepare web2py.gluon.dal.BaseAdapter-class.html#prepare +web2py.gluon.dal.BaseAdapter.NE web2py.gluon.dal.BaseAdapter-class.html#NE +web2py.gluon.dal.BaseAdapter.SUBSTRING web2py.gluon.dal.BaseAdapter-class.html#SUBSTRING +web2py.gluon.dal.SAPDBAdapter.types web2py.gluon.dal.SAPDBAdapter-class.html#types +web2py.gluon.dal.BaseAdapter.EXTRACT web2py.gluon.dal.BaseAdapter-class.html#EXTRACT +web2py.gluon.dal.BaseAdapter.commit_on_alter_table web2py.gluon.dal.BaseAdapter-class.html#commit_on_alter_table +web2py.gluon.dal.BaseAdapter.represent_exceptions web2py.gluon.dal.BaseAdapter-class.html#represent_exceptions +web2py.gluon.dal.SAPDBAdapter.sequence_name web2py.gluon.dal.SAPDBAdapter-class.html#sequence_name +web2py.gluon.dal.BaseAdapter.ADD web2py.gluon.dal.BaseAdapter-class.html#ADD +web2py.gluon.dal.BaseAdapter.count web2py.gluon.dal.BaseAdapter-class.html#count +web2py.gluon.dal.BaseAdapter.UPPER web2py.gluon.dal.BaseAdapter-class.html#UPPER +web2py.gluon.dal.BaseAdapter.JOIN web2py.gluon.dal.BaseAdapter-class.html#JOIN +web2py.gluon.dal.BaseAdapter.LIKE web2py.gluon.dal.BaseAdapter-class.html#LIKE +web2py.gluon.dal.ConnectionPool.close_all_instances web2py.gluon.dal.ConnectionPool-class.html#close_all_instances +web2py.gluon.dal.BaseAdapter.file_exists web2py.gluon.dal.BaseAdapter-class.html#file_exists +web2py.gluon.dal.BaseAdapter.delete web2py.gluon.dal.BaseAdapter-class.html#delete +web2py.gluon.dal.BaseAdapter.LOWER web2py.gluon.dal.BaseAdapter-class.html#LOWER +web2py.gluon.dal.BaseAdapter._update web2py.gluon.dal.BaseAdapter-class.html#_update +web2py.gluon.dal.BaseAdapter.parse web2py.gluon.dal.BaseAdapter-class.html#parse +web2py.gluon.dal.BaseAdapter.ENDSWITH web2py.gluon.dal.BaseAdapter-class.html#ENDSWITH +web2py.gluon.dal.BaseAdapter.constraint_name web2py.gluon.dal.BaseAdapter-class.html#constraint_name +web2py.gluon.dal.BaseAdapter.DIV web2py.gluon.dal.BaseAdapter-class.html#DIV +web2py.gluon.dal.SAPDBAdapter.__init__ web2py.gluon.dal.SAPDBAdapter-class.html#__init__ +web2py.gluon.dal.BaseAdapter.filter_tenant web2py.gluon.dal.BaseAdapter-class.html#filter_tenant +web2py.gluon.dal.BaseAdapter.LE web2py.gluon.dal.BaseAdapter-class.html#LE +web2py.gluon.dal.BaseAdapter.file_open web2py.gluon.dal.BaseAdapter-class.html#file_open +web2py.gluon.dal.ConnectionPool.set_folder web2py.gluon.dal.ConnectionPool-class.html#set_folder +web2py.gluon.dal.BaseAdapter.LT web2py.gluon.dal.BaseAdapter-class.html#LT +web2py.gluon.dal.BaseAdapter.COMMA web2py.gluon.dal.BaseAdapter-class.html#COMMA +web2py.gluon.dal.BaseAdapter.create_table web2py.gluon.dal.BaseAdapter-class.html#create_table +web2py.gluon.dal.BaseAdapter.bulk_insert web2py.gluon.dal.BaseAdapter-class.html#bulk_insert +web2py.gluon.dal.BaseAdapter.AGGREGATE web2py.gluon.dal.BaseAdapter-class.html#AGGREGATE +web2py.gluon.dal.BaseAdapter.file_close web2py.gluon.dal.BaseAdapter-class.html#file_close +web2py.gluon.dal.BaseAdapter.RANDOM web2py.gluon.dal.BaseAdapter-class.html#RANDOM +web2py.gluon.dal.BaseAdapter.truncate web2py.gluon.dal.BaseAdapter-class.html#truncate +web2py.gluon.dal.SAPDBAdapter.driver web2py.gluon.dal.SAPDBAdapter-class.html#driver +web2py.gluon.dal.BaseAdapter.update web2py.gluon.dal.BaseAdapter-class.html#update +web2py.gluon.dal.BaseAdapter.AS web2py.gluon.dal.BaseAdapter-class.html#AS +web2py.gluon.dal.BaseAdapter.trigger_name web2py.gluon.dal.BaseAdapter-class.html#trigger_name +web2py.gluon.dal.BaseAdapter.expand web2py.gluon.dal.BaseAdapter-class.html#expand +web2py.gluon.dal.BaseAdapter._count web2py.gluon.dal.BaseAdapter-class.html#_count +web2py.gluon.dal.BaseAdapter.CONTAINS web2py.gluon.dal.BaseAdapter-class.html#CONTAINS +web2py.gluon.dal.BaseAdapter.INVERT web2py.gluon.dal.BaseAdapter-class.html#INVERT +web2py.gluon.dal.BaseAdapter.alias web2py.gluon.dal.BaseAdapter-class.html#alias +web2py.gluon.dal.SAPDBAdapter.lastrowid web2py.gluon.dal.SAPDBAdapter-class.html#lastrowid +web2py.gluon.dal.SQLALL web2py.gluon.dal.SQLALL-class.html +web2py.gluon.dal.SQLALL.__str__ web2py.gluon.dal.SQLALL-class.html#__str__ +web2py.gluon.dal.SQLALL.__init__ web2py.gluon.dal.SQLALL-class.html#__init__ +web2py.gluon.dal.SQLCallableList web2py.gluon.dal.SQLCallableList-class.html +web2py.gluon.dal.SQLCallableList.__call__ web2py.gluon.dal.SQLCallableList-class.html#__call__ +web2py.gluon.dal.SQLCustomType web2py.gluon.dal.SQLCustomType-class.html +web2py.gluon.dal.SQLCustomType.startswith web2py.gluon.dal.SQLCustomType-class.html#startswith +web2py.gluon.dal.SQLCustomType.__getslice__ web2py.gluon.dal.SQLCustomType-class.html#__getslice__ +web2py.gluon.dal.SQLCustomType.__getitem__ web2py.gluon.dal.SQLCustomType-class.html#__getitem__ +web2py.gluon.dal.SQLCustomType.__str__ web2py.gluon.dal.SQLCustomType-class.html#__str__ +web2py.gluon.dal.SQLCustomType.__init__ web2py.gluon.dal.SQLCustomType-class.html#__init__ +web2py.gluon.dal.SQLiteAdapter web2py.gluon.dal.SQLiteAdapter-class.html +web2py.gluon.dal.BaseAdapter.represent web2py.gluon.dal.BaseAdapter-class.html#represent +web2py.gluon.dal.BaseAdapter.log_execute web2py.gluon.dal.BaseAdapter-class.html#log_execute +web2py.gluon.dal.BaseAdapter.select_limitby web2py.gluon.dal.BaseAdapter-class.html#select_limitby +web2py.gluon.dal.SQLiteAdapter._truncate web2py.gluon.dal.SQLiteAdapter-class.html#_truncate +web2py.gluon.dal.BaseAdapter.tables web2py.gluon.dal.BaseAdapter-class.html#tables +web2py.gluon.dal.BaseAdapter.LEFT_JOIN web2py.gluon.dal.BaseAdapter-class.html#LEFT_JOIN +web2py.gluon.dal.BaseAdapter.distributed_transaction_begin web2py.gluon.dal.BaseAdapter-class.html#distributed_transaction_begin +web2py.gluon.dal.BaseAdapter.GT web2py.gluon.dal.BaseAdapter-class.html#GT +web2py.gluon.dal.BaseAdapter.rollback web2py.gluon.dal.BaseAdapter-class.html#rollback +web2py.gluon.dal.BaseAdapter.ON web2py.gluon.dal.BaseAdapter-class.html#ON +web2py.gluon.dal.BaseAdapter.ALLOW_NULL web2py.gluon.dal.BaseAdapter-class.html#ALLOW_NULL +web2py.gluon.dal.BaseAdapter.maxcharlength web2py.gluon.dal.BaseAdapter-class.html#maxcharlength +web2py.gluon.dal.BaseAdapter.GE web2py.gluon.dal.BaseAdapter-class.html#GE +web2py.gluon.dal.BaseAdapter.get_table web2py.gluon.dal.BaseAdapter-class.html#get_table +web2py.gluon.dal.BaseAdapter._drop web2py.gluon.dal.BaseAdapter-class.html#_drop +web2py.gluon.dal.BaseAdapter.integrity_error_class web2py.gluon.dal.BaseAdapter-class.html#integrity_error_class +web2py.gluon.dal.ConnectionPool.pool_connection web2py.gluon.dal.ConnectionPool-class.html#pool_connection +web2py.gluon.dal.BaseAdapter._select web2py.gluon.dal.BaseAdapter-class.html#_select +web2py.gluon.dal.BaseAdapter.insert web2py.gluon.dal.BaseAdapter-class.html#insert +web2py.gluon.dal.BaseAdapter.execute web2py.gluon.dal.BaseAdapter-class.html#execute +web2py.gluon.dal.BaseAdapter.drop web2py.gluon.dal.BaseAdapter-class.html#drop +web2py.gluon.dal.BaseAdapter.migrate_table web2py.gluon.dal.BaseAdapter-class.html#migrate_table +web2py.gluon.dal.BaseAdapter.concat_add web2py.gluon.dal.BaseAdapter-class.html#concat_add +web2py.gluon.dal.BaseAdapter._insert web2py.gluon.dal.BaseAdapter-class.html#_insert +web2py.gluon.dal.ConnectionPool.pools web2py.gluon.dal.ConnectionPool-class.html#pools +web2py.gluon.dal.BaseAdapter.support_distributed_transaction web2py.gluon.dal.BaseAdapter-class.html#support_distributed_transaction +web2py.gluon.dal.BaseAdapter.OR web2py.gluon.dal.BaseAdapter-class.html#OR +web2py.gluon.dal.BaseAdapter.file_delete web2py.gluon.dal.BaseAdapter-class.html#file_delete +web2py.gluon.dal.BaseAdapter.NOT_NULL web2py.gluon.dal.BaseAdapter-class.html#NOT_NULL +web2py.gluon.dal.ConnectionPool.find_or_make_work_folder web2py.gluon.dal.ConnectionPool-class.html#find_or_make_work_folder +web2py.gluon.dal.BaseAdapter.close web2py.gluon.dal.BaseAdapter-class.html#close +web2py.gluon.dal.BaseAdapter.integrity_error web2py.gluon.dal.BaseAdapter-class.html#integrity_error +web2py.gluon.dal.BaseAdapter.rowslice web2py.gluon.dal.BaseAdapter-class.html#rowslice +web2py.gluon.dal.BaseAdapter.create_sequence_and_triggers web2py.gluon.dal.BaseAdapter-class.html#create_sequence_and_triggers +web2py.gluon.dal.BaseAdapter.rollback_prepared web2py.gluon.dal.BaseAdapter-class.html#rollback_prepared +web2py.gluon.dal.BaseAdapter.commit_prepared web2py.gluon.dal.BaseAdapter-class.html#commit_prepared +web2py.gluon.dal.SQLiteAdapter.web2py_extract web2py.gluon.dal.SQLiteAdapter-class.html#web2py_extract +web2py.gluon.dal.BaseAdapter.EQ web2py.gluon.dal.BaseAdapter-class.html#EQ +web2py.gluon.dal.BaseAdapter.AND web2py.gluon.dal.BaseAdapter-class.html#AND +web2py.gluon.dal.BaseAdapter.uploads_in_blob web2py.gluon.dal.BaseAdapter-class.html#uploads_in_blob +web2py.gluon.dal.BaseAdapter.NOT web2py.gluon.dal.BaseAdapter-class.html#NOT +web2py.gluon.dal.BaseAdapter.commit web2py.gluon.dal.BaseAdapter-class.html#commit +web2py.gluon.dal.BaseAdapter.MOD web2py.gluon.dal.BaseAdapter-class.html#MOD +web2py.gluon.dal.BaseAdapter.STARTSWITH web2py.gluon.dal.BaseAdapter-class.html#STARTSWITH +web2py.gluon.dal.BaseAdapter.SUB web2py.gluon.dal.BaseAdapter-class.html#SUB +web2py.gluon.dal.BaseAdapter.COALESCE_ZERO web2py.gluon.dal.BaseAdapter-class.html#COALESCE_ZERO +web2py.gluon.dal.BaseAdapter._delete web2py.gluon.dal.BaseAdapter-class.html#_delete +web2py.gluon.dal.BaseAdapter.BELONGS web2py.gluon.dal.BaseAdapter-class.html#BELONGS +web2py.gluon.dal.BaseAdapter.MUL web2py.gluon.dal.BaseAdapter-class.html#MUL +web2py.gluon.dal.BaseAdapter.select web2py.gluon.dal.BaseAdapter-class.html#select +web2py.gluon.dal.BaseAdapter.PRIMARY_KEY web2py.gluon.dal.BaseAdapter-class.html#PRIMARY_KEY +web2py.gluon.dal.BaseAdapter.prepare web2py.gluon.dal.BaseAdapter-class.html#prepare +web2py.gluon.dal.BaseAdapter.NE web2py.gluon.dal.BaseAdapter-class.html#NE +web2py.gluon.dal.BaseAdapter.SUBSTRING web2py.gluon.dal.BaseAdapter-class.html#SUBSTRING +web2py.gluon.dal.BaseAdapter.types web2py.gluon.dal.BaseAdapter-class.html#types +web2py.gluon.dal.SQLiteAdapter.EXTRACT web2py.gluon.dal.SQLiteAdapter-class.html#EXTRACT +web2py.gluon.dal.BaseAdapter.commit_on_alter_table web2py.gluon.dal.BaseAdapter-class.html#commit_on_alter_table +web2py.gluon.dal.BaseAdapter.represent_exceptions web2py.gluon.dal.BaseAdapter-class.html#represent_exceptions +web2py.gluon.dal.BaseAdapter.sequence_name web2py.gluon.dal.BaseAdapter-class.html#sequence_name +web2py.gluon.dal.BaseAdapter.ADD web2py.gluon.dal.BaseAdapter-class.html#ADD +web2py.gluon.dal.BaseAdapter.count web2py.gluon.dal.BaseAdapter-class.html#count +web2py.gluon.dal.BaseAdapter.UPPER web2py.gluon.dal.BaseAdapter-class.html#UPPER +web2py.gluon.dal.BaseAdapter.JOIN web2py.gluon.dal.BaseAdapter-class.html#JOIN +web2py.gluon.dal.BaseAdapter.LIKE web2py.gluon.dal.BaseAdapter-class.html#LIKE +web2py.gluon.dal.ConnectionPool.close_all_instances web2py.gluon.dal.ConnectionPool-class.html#close_all_instances +web2py.gluon.dal.BaseAdapter.file_exists web2py.gluon.dal.BaseAdapter-class.html#file_exists +web2py.gluon.dal.BaseAdapter.delete web2py.gluon.dal.BaseAdapter-class.html#delete +web2py.gluon.dal.BaseAdapter.LOWER web2py.gluon.dal.BaseAdapter-class.html#LOWER +web2py.gluon.dal.BaseAdapter._update web2py.gluon.dal.BaseAdapter-class.html#_update +web2py.gluon.dal.BaseAdapter.parse web2py.gluon.dal.BaseAdapter-class.html#parse +web2py.gluon.dal.BaseAdapter.ENDSWITH web2py.gluon.dal.BaseAdapter-class.html#ENDSWITH +web2py.gluon.dal.BaseAdapter.constraint_name web2py.gluon.dal.BaseAdapter-class.html#constraint_name +web2py.gluon.dal.BaseAdapter.DIV web2py.gluon.dal.BaseAdapter-class.html#DIV +web2py.gluon.dal.SQLiteAdapter.__init__ web2py.gluon.dal.SQLiteAdapter-class.html#__init__ +web2py.gluon.dal.BaseAdapter.filter_tenant web2py.gluon.dal.BaseAdapter-class.html#filter_tenant +web2py.gluon.dal.BaseAdapter.LE web2py.gluon.dal.BaseAdapter-class.html#LE +web2py.gluon.dal.BaseAdapter.file_open web2py.gluon.dal.BaseAdapter-class.html#file_open +web2py.gluon.dal.ConnectionPool.set_folder web2py.gluon.dal.ConnectionPool-class.html#set_folder +web2py.gluon.dal.BaseAdapter.LT web2py.gluon.dal.BaseAdapter-class.html#LT +web2py.gluon.dal.BaseAdapter.COMMA web2py.gluon.dal.BaseAdapter-class.html#COMMA +web2py.gluon.dal.BaseAdapter.create_table web2py.gluon.dal.BaseAdapter-class.html#create_table +web2py.gluon.dal.BaseAdapter.bulk_insert web2py.gluon.dal.BaseAdapter-class.html#bulk_insert +web2py.gluon.dal.BaseAdapter.AGGREGATE web2py.gluon.dal.BaseAdapter-class.html#AGGREGATE +web2py.gluon.dal.BaseAdapter.file_close web2py.gluon.dal.BaseAdapter-class.html#file_close +web2py.gluon.dal.BaseAdapter.RANDOM web2py.gluon.dal.BaseAdapter-class.html#RANDOM +web2py.gluon.dal.BaseAdapter.truncate web2py.gluon.dal.BaseAdapter-class.html#truncate +web2py.gluon.dal.SQLiteAdapter.driver sqlite3.dbapi2-module.html +web2py.gluon.dal.BaseAdapter.update web2py.gluon.dal.BaseAdapter-class.html#update +web2py.gluon.dal.BaseAdapter.AS web2py.gluon.dal.BaseAdapter-class.html#AS +web2py.gluon.dal.BaseAdapter.trigger_name web2py.gluon.dal.BaseAdapter-class.html#trigger_name +web2py.gluon.dal.BaseAdapter.expand web2py.gluon.dal.BaseAdapter-class.html#expand +web2py.gluon.dal.BaseAdapter._count web2py.gluon.dal.BaseAdapter-class.html#_count +web2py.gluon.dal.BaseAdapter.CONTAINS web2py.gluon.dal.BaseAdapter-class.html#CONTAINS +web2py.gluon.dal.BaseAdapter.INVERT web2py.gluon.dal.BaseAdapter-class.html#INVERT +web2py.gluon.dal.BaseAdapter.alias web2py.gluon.dal.BaseAdapter-class.html#alias +web2py.gluon.dal.SQLiteAdapter.lastrowid web2py.gluon.dal.SQLiteAdapter-class.html#lastrowid +web2py.gluon.dal.Set web2py.gluon.dal.Set-class.html +web2py.gluon.dal.Set.delete_uploaded_files web2py.gluon.dal.Set-class.html#delete_uploaded_files +web2py.gluon.dal.Set._update web2py.gluon.dal.Set-class.html#_update +web2py.gluon.dal.Set._delete web2py.gluon.dal.Set-class.html#_delete +web2py.gluon.dal.Set.select web2py.gluon.dal.Set-class.html#select +web2py.gluon.dal.Set.__init__ web2py.gluon.dal.Set-class.html#__init__ +web2py.gluon.dal.Set.__call__ web2py.gluon.dal.Set-class.html#__call__ +web2py.gluon.dal.Set.count web2py.gluon.dal.Set-class.html#count +web2py.gluon.dal.Set.update web2py.gluon.dal.Set-class.html#update +web2py.gluon.dal.Set.isempty web2py.gluon.dal.Set-class.html#isempty +web2py.gluon.dal.Set.validate_and_update web2py.gluon.dal.Set-class.html#validate_and_update +web2py.gluon.dal.Set._select web2py.gluon.dal.Set-class.html#_select +web2py.gluon.dal.Set._count web2py.gluon.dal.Set-class.html#_count +web2py.gluon.dal.Set.delete web2py.gluon.dal.Set-class.html#delete +web2py.gluon.dal.Table web2py.gluon.dal.Table-class.html +web2py.gluon.dal.Table._listify web2py.gluon.dal.Table-class.html#_listify +web2py.gluon.dal.Table._validate web2py.gluon.dal.Table-class.html#_validate +web2py.gluon.dal.Table.__str__ web2py.gluon.dal.Table-class.html#__str__ +web2py.gluon.dal.Table._build_query web2py.gluon.dal.Table-class.html#_build_query +web2py.gluon.dal.Table.__delitem__ web2py.gluon.dal.Table-class.html#__delitem__ +web2py.gluon.dal.Table._truncate web2py.gluon.dal.Table-class.html#_truncate +web2py.gluon.dal.Table.__init__ web2py.gluon.dal.Table-class.html#__init__ +web2py.gluon.dal.Table.validate_and_insert web2py.gluon.dal.Table-class.html#validate_and_insert +web2py.gluon.dal.Table.import_from_csv_file web2py.gluon.dal.Table-class.html#import_from_csv_file +web2py.gluon.dal.Table.__setattr__ web2py.gluon.dal.Table-class.html#__setattr__ +web2py.gluon.dal.Table.truncate web2py.gluon.dal.Table-class.html#truncate +web2py.gluon.dal.Table.__getattr__ web2py.gluon.dal.Table-class.html#__getattr__ +web2py.gluon.dal.Table._filter_fields web2py.gluon.dal.Table-class.html#_filter_fields +web2py.gluon.dal.Table.with_alias web2py.gluon.dal.Table-class.html#with_alias +web2py.gluon.dal.Table.__call__ web2py.gluon.dal.Table-class.html#__call__ +web2py.gluon.dal.Table.__getitem__ web2py.gluon.dal.Table-class.html#__getitem__ +web2py.gluon.dal.Table.on web2py.gluon.dal.Table-class.html#on +web2py.gluon.dal.Table.bulk_insert web2py.gluon.dal.Table-class.html#bulk_insert +web2py.gluon.dal.Table.__setitem__ web2py.gluon.dal.Table-class.html#__setitem__ +web2py.gluon.dal.Table._drop web2py.gluon.dal.Table-class.html#_drop +web2py.gluon.dal.Table.insert web2py.gluon.dal.Table-class.html#insert +web2py.gluon.dal.Table.drop web2py.gluon.dal.Table-class.html#drop +web2py.gluon.dal.Table.__iter__ web2py.gluon.dal.Table-class.html#__iter__ +web2py.gluon.dal.Table.update_or_insert web2py.gluon.dal.Table-class.html#update_or_insert +web2py.gluon.dal.Table.__repr__ web2py.gluon.dal.Table-class.html#__repr__ +web2py.gluon.dal.Table._insert web2py.gluon.dal.Table-class.html#_insert +web2py.gluon.dal.Table._create_references web2py.gluon.dal.Table-class.html#_create_references +web2py.gluon.dal.TeradataAdapter web2py.gluon.dal.TeradataAdapter-class.html +web2py.gluon.dal.BaseAdapter.represent web2py.gluon.dal.BaseAdapter-class.html#represent +web2py.gluon.dal.BaseAdapter.log_execute web2py.gluon.dal.BaseAdapter-class.html#log_execute +web2py.gluon.dal.DB2Adapter.select_limitby web2py.gluon.dal.DB2Adapter-class.html#select_limitby +web2py.gluon.dal.BaseAdapter._truncate web2py.gluon.dal.BaseAdapter-class.html#_truncate +web2py.gluon.dal.BaseAdapter.tables web2py.gluon.dal.BaseAdapter-class.html#tables +web2py.gluon.dal.DB2Adapter.LEFT_JOIN web2py.gluon.dal.DB2Adapter-class.html#LEFT_JOIN +web2py.gluon.dal.BaseAdapter.distributed_transaction_begin web2py.gluon.dal.BaseAdapter-class.html#distributed_transaction_begin +web2py.gluon.dal.BaseAdapter.GT web2py.gluon.dal.BaseAdapter-class.html#GT +web2py.gluon.dal.BaseAdapter.rollback web2py.gluon.dal.BaseAdapter-class.html#rollback +web2py.gluon.dal.BaseAdapter.ON web2py.gluon.dal.BaseAdapter-class.html#ON +web2py.gluon.dal.BaseAdapter.ALLOW_NULL web2py.gluon.dal.BaseAdapter-class.html#ALLOW_NULL +web2py.gluon.dal.BaseAdapter.maxcharlength web2py.gluon.dal.BaseAdapter-class.html#maxcharlength +web2py.gluon.dal.BaseAdapter.GE web2py.gluon.dal.BaseAdapter-class.html#GE +web2py.gluon.dal.BaseAdapter.get_table web2py.gluon.dal.BaseAdapter-class.html#get_table +web2py.gluon.dal.BaseAdapter._drop web2py.gluon.dal.BaseAdapter-class.html#_drop +web2py.gluon.dal.BaseAdapter.integrity_error_class web2py.gluon.dal.BaseAdapter-class.html#integrity_error_class +web2py.gluon.dal.ConnectionPool.pool_connection web2py.gluon.dal.ConnectionPool-class.html#pool_connection +web2py.gluon.dal.BaseAdapter._select web2py.gluon.dal.BaseAdapter-class.html#_select +web2py.gluon.dal.BaseAdapter.insert web2py.gluon.dal.BaseAdapter-class.html#insert +web2py.gluon.dal.DB2Adapter.execute web2py.gluon.dal.DB2Adapter-class.html#execute +web2py.gluon.dal.BaseAdapter.drop web2py.gluon.dal.BaseAdapter-class.html#drop +web2py.gluon.dal.BaseAdapter.migrate_table web2py.gluon.dal.BaseAdapter-class.html#migrate_table +web2py.gluon.dal.BaseAdapter.concat_add web2py.gluon.dal.BaseAdapter-class.html#concat_add +web2py.gluon.dal.BaseAdapter._insert web2py.gluon.dal.BaseAdapter-class.html#_insert +web2py.gluon.dal.ConnectionPool.pools web2py.gluon.dal.ConnectionPool-class.html#pools +web2py.gluon.dal.BaseAdapter.support_distributed_transaction web2py.gluon.dal.BaseAdapter-class.html#support_distributed_transaction +web2py.gluon.dal.BaseAdapter.OR web2py.gluon.dal.BaseAdapter-class.html#OR +web2py.gluon.dal.BaseAdapter.file_delete web2py.gluon.dal.BaseAdapter-class.html#file_delete +web2py.gluon.dal.BaseAdapter.NOT_NULL web2py.gluon.dal.BaseAdapter-class.html#NOT_NULL +web2py.gluon.dal.ConnectionPool.find_or_make_work_folder web2py.gluon.dal.ConnectionPool-class.html#find_or_make_work_folder +web2py.gluon.dal.BaseAdapter.close web2py.gluon.dal.BaseAdapter-class.html#close +web2py.gluon.dal.BaseAdapter.integrity_error web2py.gluon.dal.BaseAdapter-class.html#integrity_error +web2py.gluon.dal.DB2Adapter.rowslice web2py.gluon.dal.DB2Adapter-class.html#rowslice +web2py.gluon.dal.BaseAdapter.create_sequence_and_triggers web2py.gluon.dal.BaseAdapter-class.html#create_sequence_and_triggers +web2py.gluon.dal.BaseAdapter.rollback_prepared web2py.gluon.dal.BaseAdapter-class.html#rollback_prepared +web2py.gluon.dal.BaseAdapter.commit_prepared web2py.gluon.dal.BaseAdapter-class.html#commit_prepared +web2py.gluon.dal.BaseAdapter.EQ web2py.gluon.dal.BaseAdapter-class.html#EQ +web2py.gluon.dal.BaseAdapter.AND web2py.gluon.dal.BaseAdapter-class.html#AND +web2py.gluon.dal.BaseAdapter.uploads_in_blob web2py.gluon.dal.BaseAdapter-class.html#uploads_in_blob +web2py.gluon.dal.BaseAdapter.NOT web2py.gluon.dal.BaseAdapter-class.html#NOT +web2py.gluon.dal.BaseAdapter.commit web2py.gluon.dal.BaseAdapter-class.html#commit +web2py.gluon.dal.BaseAdapter.MOD web2py.gluon.dal.BaseAdapter-class.html#MOD +web2py.gluon.dal.BaseAdapter.STARTSWITH web2py.gluon.dal.BaseAdapter-class.html#STARTSWITH +web2py.gluon.dal.BaseAdapter.SUB web2py.gluon.dal.BaseAdapter-class.html#SUB +web2py.gluon.dal.BaseAdapter.COALESCE_ZERO web2py.gluon.dal.BaseAdapter-class.html#COALESCE_ZERO +web2py.gluon.dal.BaseAdapter._delete web2py.gluon.dal.BaseAdapter-class.html#_delete +web2py.gluon.dal.BaseAdapter.BELONGS web2py.gluon.dal.BaseAdapter-class.html#BELONGS +web2py.gluon.dal.BaseAdapter.MUL web2py.gluon.dal.BaseAdapter-class.html#MUL +web2py.gluon.dal.BaseAdapter.select web2py.gluon.dal.BaseAdapter-class.html#select +web2py.gluon.dal.BaseAdapter.PRIMARY_KEY web2py.gluon.dal.BaseAdapter-class.html#PRIMARY_KEY +web2py.gluon.dal.BaseAdapter.prepare web2py.gluon.dal.BaseAdapter-class.html#prepare +web2py.gluon.dal.BaseAdapter.NE web2py.gluon.dal.BaseAdapter-class.html#NE +web2py.gluon.dal.BaseAdapter.SUBSTRING web2py.gluon.dal.BaseAdapter-class.html#SUBSTRING +web2py.gluon.dal.TeradataAdapter.types web2py.gluon.dal.TeradataAdapter-class.html#types +web2py.gluon.dal.BaseAdapter.EXTRACT web2py.gluon.dal.BaseAdapter-class.html#EXTRACT +web2py.gluon.dal.BaseAdapter.commit_on_alter_table web2py.gluon.dal.BaseAdapter-class.html#commit_on_alter_table +web2py.gluon.dal.DB2Adapter.represent_exceptions web2py.gluon.dal.DB2Adapter-class.html#represent_exceptions +web2py.gluon.dal.BaseAdapter.sequence_name web2py.gluon.dal.BaseAdapter-class.html#sequence_name +web2py.gluon.dal.BaseAdapter.ADD web2py.gluon.dal.BaseAdapter-class.html#ADD +web2py.gluon.dal.BaseAdapter.count web2py.gluon.dal.BaseAdapter-class.html#count +web2py.gluon.dal.BaseAdapter.UPPER web2py.gluon.dal.BaseAdapter-class.html#UPPER +web2py.gluon.dal.BaseAdapter.JOIN web2py.gluon.dal.BaseAdapter-class.html#JOIN +web2py.gluon.dal.BaseAdapter.LIKE web2py.gluon.dal.BaseAdapter-class.html#LIKE +web2py.gluon.dal.ConnectionPool.close_all_instances web2py.gluon.dal.ConnectionPool-class.html#close_all_instances +web2py.gluon.dal.BaseAdapter.file_exists web2py.gluon.dal.BaseAdapter-class.html#file_exists +web2py.gluon.dal.BaseAdapter.delete web2py.gluon.dal.BaseAdapter-class.html#delete +web2py.gluon.dal.BaseAdapter.LOWER web2py.gluon.dal.BaseAdapter-class.html#LOWER +web2py.gluon.dal.BaseAdapter._update web2py.gluon.dal.BaseAdapter-class.html#_update +web2py.gluon.dal.BaseAdapter.parse web2py.gluon.dal.BaseAdapter-class.html#parse +web2py.gluon.dal.BaseAdapter.ENDSWITH web2py.gluon.dal.BaseAdapter-class.html#ENDSWITH +web2py.gluon.dal.BaseAdapter.constraint_name web2py.gluon.dal.BaseAdapter-class.html#constraint_name +web2py.gluon.dal.BaseAdapter.DIV web2py.gluon.dal.BaseAdapter-class.html#DIV +web2py.gluon.dal.TeradataAdapter.__init__ web2py.gluon.dal.TeradataAdapter-class.html#__init__ +web2py.gluon.dal.BaseAdapter.filter_tenant web2py.gluon.dal.BaseAdapter-class.html#filter_tenant +web2py.gluon.dal.BaseAdapter.LE web2py.gluon.dal.BaseAdapter-class.html#LE +web2py.gluon.dal.BaseAdapter.file_open web2py.gluon.dal.BaseAdapter-class.html#file_open +web2py.gluon.dal.ConnectionPool.set_folder web2py.gluon.dal.ConnectionPool-class.html#set_folder +web2py.gluon.dal.BaseAdapter.LT web2py.gluon.dal.BaseAdapter-class.html#LT +web2py.gluon.dal.BaseAdapter.COMMA web2py.gluon.dal.BaseAdapter-class.html#COMMA +web2py.gluon.dal.BaseAdapter.create_table web2py.gluon.dal.BaseAdapter-class.html#create_table +web2py.gluon.dal.BaseAdapter.bulk_insert web2py.gluon.dal.BaseAdapter-class.html#bulk_insert +web2py.gluon.dal.BaseAdapter.AGGREGATE web2py.gluon.dal.BaseAdapter-class.html#AGGREGATE +web2py.gluon.dal.BaseAdapter.file_close web2py.gluon.dal.BaseAdapter-class.html#file_close +web2py.gluon.dal.DB2Adapter.RANDOM web2py.gluon.dal.DB2Adapter-class.html#RANDOM +web2py.gluon.dal.BaseAdapter.truncate web2py.gluon.dal.BaseAdapter-class.html#truncate +web2py.gluon.dal.TeradataAdapter.driver web2py.gluon.dal.TeradataAdapter-class.html#driver +web2py.gluon.dal.BaseAdapter.update web2py.gluon.dal.BaseAdapter-class.html#update +web2py.gluon.dal.BaseAdapter.AS web2py.gluon.dal.BaseAdapter-class.html#AS +web2py.gluon.dal.BaseAdapter.trigger_name web2py.gluon.dal.BaseAdapter-class.html#trigger_name +web2py.gluon.dal.BaseAdapter.expand web2py.gluon.dal.BaseAdapter-class.html#expand +web2py.gluon.dal.BaseAdapter._count web2py.gluon.dal.BaseAdapter-class.html#_count +web2py.gluon.dal.BaseAdapter.CONTAINS web2py.gluon.dal.BaseAdapter-class.html#CONTAINS +web2py.gluon.dal.BaseAdapter.INVERT web2py.gluon.dal.BaseAdapter-class.html#INVERT +web2py.gluon.dal.BaseAdapter.alias web2py.gluon.dal.BaseAdapter-class.html#alias +web2py.gluon.dal.DB2Adapter.lastrowid web2py.gluon.dal.DB2Adapter-class.html#lastrowid +web2py.gluon.dal.UseDatabaseStoredFile web2py.gluon.dal.UseDatabaseStoredFile-class.html +web2py.gluon.dal.UseDatabaseStoredFile.file_close web2py.gluon.dal.UseDatabaseStoredFile-class.html#file_close +web2py.gluon.dal.UseDatabaseStoredFile.file_exists web2py.gluon.dal.UseDatabaseStoredFile-class.html#file_exists +web2py.gluon.dal.UseDatabaseStoredFile.file_delete web2py.gluon.dal.UseDatabaseStoredFile-class.html#file_delete +web2py.gluon.dal.UseDatabaseStoredFile.file_open web2py.gluon.dal.UseDatabaseStoredFile-class.html#file_open +web2py.gluon.debug.Pipe web2py.gluon.debug.Pipe-class.html +web2py.gluon.debug.Pipe.read web2py.gluon.debug.Pipe-class.html#read +web2py.gluon.debug.Pipe.write web2py.gluon.debug.Pipe-class.html#write +web2py.gluon.debug.Pipe.flush web2py.gluon.debug.Pipe-class.html#flush +web2py.gluon.debug.Pipe.readline web2py.gluon.debug.Pipe-class.html#readline +web2py.gluon.debug.Pipe.__init__ web2py.gluon.debug.Pipe-class.html#__init__ +web2py.gluon.globals.Request web2py.gluon.globals.Request-class.html +web2py.gluon.storage.Storage.getlist web2py.gluon.storage.Storage-class.html#getlist +web2py.gluon.globals.Request.__init__ web2py.gluon.globals.Request-class.html#__init__ +web2py.gluon.storage.Storage.__setattr__ web2py.gluon.storage.Storage-class.html#__setattr__ +web2py.gluon.storage.Storage.__getattr__ web2py.gluon.storage.Storage-class.html#__getattr__ +web2py.gluon.storage.Storage.__getstate__ web2py.gluon.storage.Storage-class.html#__getstate__ +web2py.gluon.storage.Storage.__repr__ web2py.gluon.storage.Storage-class.html#__repr__ +web2py.gluon.globals.Request.compute_uuid web2py.gluon.globals.Request-class.html#compute_uuid +web2py.gluon.storage.Storage.__setstate__ web2py.gluon.storage.Storage-class.html#__setstate__ +web2py.gluon.storage.Storage.getlast web2py.gluon.storage.Storage-class.html#getlast +web2py.gluon.storage.Storage.__delattr__ web2py.gluon.storage.Storage-class.html#__delattr__ +web2py.gluon.globals.Request.user_agent web2py.gluon.globals.Request-class.html#user_agent +web2py.gluon.globals.Request.restful web2py.gluon.globals.Request-class.html#restful +web2py.gluon.storage.Storage.getfirst web2py.gluon.storage.Storage-class.html#getfirst +web2py.gluon.globals.Response web2py.gluon.globals.Response-class.html +web2py.gluon.globals.Response.render web2py.gluon.globals.Response-class.html#render +web2py.gluon.storage.Storage.getlist web2py.gluon.storage.Storage-class.html#getlist +web2py.gluon.globals.Response.download web2py.gluon.globals.Response-class.html#download +web2py.gluon.globals.Response.__init__ web2py.gluon.globals.Response-class.html#__init__ +web2py.gluon.storage.Storage.__setattr__ web2py.gluon.storage.Storage-class.html#__setattr__ +web2py.gluon.globals.Response.stream web2py.gluon.globals.Response-class.html#stream +web2py.gluon.storage.Storage.__getattr__ web2py.gluon.storage.Storage-class.html#__getattr__ +web2py.gluon.globals.Response.write web2py.gluon.globals.Response-class.html#write +web2py.gluon.globals.Response.json web2py.gluon.globals.Response-class.html#json +web2py.gluon.storage.Storage.__getstate__ web2py.gluon.storage.Storage-class.html#__getstate__ +web2py.gluon.globals.Response.xmlrpc web2py.gluon.globals.Response-class.html#xmlrpc +web2py.gluon.storage.Storage.__setstate__ web2py.gluon.storage.Storage-class.html#__setstate__ +web2py.gluon.globals.Response.toolbar web2py.gluon.globals.Response-class.html#toolbar +web2py.gluon.storage.Storage.getlast web2py.gluon.storage.Storage-class.html#getlast +web2py.gluon.storage.Storage.__delattr__ web2py.gluon.storage.Storage-class.html#__delattr__ +web2py.gluon.storage.Storage.__repr__ web2py.gluon.storage.Storage-class.html#__repr__ +web2py.gluon.storage.Storage.getfirst web2py.gluon.storage.Storage-class.html#getfirst +web2py.gluon.globals.Session web2py.gluon.globals.Session-class.html +web2py.gluon.globals.Session.forget web2py.gluon.globals.Session-class.html#forget +web2py.gluon.storage.Storage.getlist web2py.gluon.storage.Storage-class.html#getlist +web2py.gluon.globals.Session.is_new web2py.gluon.globals.Session-class.html#is_new +web2py.gluon.globals.Session.connect web2py.gluon.globals.Session-class.html#connect +web2py.gluon.globals.Session._try_store_in_db web2py.gluon.globals.Session-class.html#_try_store_in_db +web2py.gluon.globals.Session.secure web2py.gluon.globals.Session-class.html#secure +web2py.gluon.storage.Storage.__setattr__ web2py.gluon.storage.Storage-class.html#__setattr__ +web2py.gluon.storage.Storage.__getattr__ web2py.gluon.storage.Storage-class.html#__getattr__ +web2py.gluon.storage.Storage.__getstate__ web2py.gluon.storage.Storage-class.html#__getstate__ +web2py.gluon.globals.Session._close web2py.gluon.globals.Session-class.html#_close +web2py.gluon.storage.Storage.__setstate__ web2py.gluon.storage.Storage-class.html#__setstate__ +web2py.gluon.globals.Session._unlock web2py.gluon.globals.Session-class.html#_unlock +web2py.gluon.globals.Session._try_store_on_disk web2py.gluon.globals.Session-class.html#_try_store_on_disk +web2py.gluon.storage.Storage.getlast web2py.gluon.storage.Storage-class.html#getlast +web2py.gluon.storage.Storage.__delattr__ web2py.gluon.storage.Storage-class.html#__delattr__ +web2py.gluon.storage.Storage.__repr__ web2py.gluon.storage.Storage-class.html#__repr__ +web2py.gluon.globals.Session.is_expired web2py.gluon.globals.Session-class.html#is_expired +web2py.gluon.storage.Storage.getfirst web2py.gluon.storage.Storage-class.html#getfirst +web2py.gluon.highlight.Highlighter web2py.gluon.highlight.Highlighter-class.html +web2py.gluon.highlight.Highlighter.python_tokenizer web2py.gluon.highlight.Highlighter-class.html#python_tokenizer +web2py.gluon.highlight.Highlighter.c_tokenizer web2py.gluon.highlight.Highlighter-class.html#c_tokenizer +web2py.gluon.highlight.Highlighter.html_tokenizer web2py.gluon.highlight.Highlighter-class.html#html_tokenizer +web2py.gluon.highlight.Highlighter.all_styles web2py.gluon.highlight.Highlighter-class.html#all_styles +web2py.gluon.highlight.Highlighter.change_style web2py.gluon.highlight.Highlighter-class.html#change_style +web2py.gluon.highlight.Highlighter.highlight web2py.gluon.highlight.Highlighter-class.html#highlight +web2py.gluon.highlight.Highlighter.__init__ web2py.gluon.highlight.Highlighter-class.html#__init__ +web2py.gluon.html.A web2py.gluon.html.A-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.A.tag web2py.gluon.html.A-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.DIV._fixup web2py.gluon.html.DIV-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.A.xml web2py.gluon.html.A-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.B web2py.gluon.html.B-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.B.tag web2py.gluon.html.B-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.DIV._fixup web2py.gluon.html.DIV-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.DIV.xml web2py.gluon.html.DIV-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.BEAUTIFY web2py.gluon.html.BEAUTIFY-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.BEAUTIFY.tag web2py.gluon.html.BEAUTIFY-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.DIV._fixup web2py.gluon.html.DIV-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.DIV.xml web2py.gluon.html.DIV-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.BEAUTIFY.__init__ web2py.gluon.html.BEAUTIFY-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.BEAUTIFY.no_underscore web2py.gluon.html.BEAUTIFY-class.html#no_underscore +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.BODY web2py.gluon.html.BODY-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.BODY.tag web2py.gluon.html.BODY-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.DIV._fixup web2py.gluon.html.DIV-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.DIV.xml web2py.gluon.html.DIV-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.BR web2py.gluon.html.BR-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.BR.tag web2py.gluon.html.BR-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.DIV._fixup web2py.gluon.html.DIV-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.DIV.xml web2py.gluon.html.DIV-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.BUTTON web2py.gluon.html.BUTTON-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.BUTTON.tag web2py.gluon.html.BUTTON-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.DIV._fixup web2py.gluon.html.DIV-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.DIV.xml web2py.gluon.html.DIV-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.CAT web2py.gluon.html.CAT-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.CAT.tag web2py.gluon.html.CAT-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.DIV._fixup web2py.gluon.html.DIV-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.DIV.xml web2py.gluon.html.DIV-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.CENTER web2py.gluon.html.CENTER-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.CENTER.tag web2py.gluon.html.CENTER-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.DIV._fixup web2py.gluon.html.DIV-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.DIV.xml web2py.gluon.html.DIV-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.CODE web2py.gluon.html.CODE-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.DIV.tag web2py.gluon.html.DIV-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.DIV._fixup web2py.gluon.html.DIV-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.CODE.xml web2py.gluon.html.CODE-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.COL web2py.gluon.html.COL-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.COL.tag web2py.gluon.html.COL-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.DIV._fixup web2py.gluon.html.DIV-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.DIV.xml web2py.gluon.html.DIV-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.COLGROUP web2py.gluon.html.COLGROUP-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.COLGROUP.tag web2py.gluon.html.COLGROUP-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.DIV._fixup web2py.gluon.html.DIV-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.DIV.xml web2py.gluon.html.DIV-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.DIV web2py.gluon.html.DIV-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.DIV.tag web2py.gluon.html.DIV-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.DIV._fixup web2py.gluon.html.DIV-class.html#_fixup +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.xml web2py.gluon.html.DIV-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.EM web2py.gluon.html.EM-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.EM.tag web2py.gluon.html.EM-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.DIV._fixup web2py.gluon.html.DIV-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.DIV.xml web2py.gluon.html.DIV-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.EMBED web2py.gluon.html.EMBED-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.EMBED.tag web2py.gluon.html.EMBED-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.DIV._fixup web2py.gluon.html.DIV-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.DIV.xml web2py.gluon.html.DIV-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.FIELDSET web2py.gluon.html.FIELDSET-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.FIELDSET.tag web2py.gluon.html.FIELDSET-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.DIV._fixup web2py.gluon.html.DIV-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.DIV.xml web2py.gluon.html.DIV-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.FORM web2py.gluon.html.FORM-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.FORM.process web2py.gluon.html.FORM-class.html#process +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.FORM.tag web2py.gluon.html.FORM-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.DIV._fixup web2py.gluon.html.DIV-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.FORM.xml web2py.gluon.html.FORM-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.FORM.__init__ web2py.gluon.html.FORM-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.FORM._postprocessing web2py.gluon.html.FORM-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.FORM.validate web2py.gluon.html.FORM-class.html#validate +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.FORM.accepts web2py.gluon.html.FORM-class.html#accepts +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.FORM.hidden_fields web2py.gluon.html.FORM-class.html#hidden_fields +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.H1 web2py.gluon.html.H1-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.H1.tag web2py.gluon.html.H1-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.DIV._fixup web2py.gluon.html.DIV-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.DIV.xml web2py.gluon.html.DIV-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.H2 web2py.gluon.html.H2-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.H2.tag web2py.gluon.html.H2-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.DIV._fixup web2py.gluon.html.DIV-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.DIV.xml web2py.gluon.html.DIV-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.H3 web2py.gluon.html.H3-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.H3.tag web2py.gluon.html.H3-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.DIV._fixup web2py.gluon.html.DIV-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.DIV.xml web2py.gluon.html.DIV-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.H4 web2py.gluon.html.H4-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.H4.tag web2py.gluon.html.H4-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.DIV._fixup web2py.gluon.html.DIV-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.DIV.xml web2py.gluon.html.DIV-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.H5 web2py.gluon.html.H5-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.H5.tag web2py.gluon.html.H5-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.DIV._fixup web2py.gluon.html.DIV-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.DIV.xml web2py.gluon.html.DIV-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.H6 web2py.gluon.html.H6-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.H6.tag web2py.gluon.html.H6-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.DIV._fixup web2py.gluon.html.DIV-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.DIV.xml web2py.gluon.html.DIV-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.HEAD web2py.gluon.html.HEAD-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.HEAD.tag web2py.gluon.html.HEAD-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.DIV._fixup web2py.gluon.html.DIV-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.DIV.xml web2py.gluon.html.DIV-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.HR web2py.gluon.html.HR-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.HR.tag web2py.gluon.html.HR-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.DIV._fixup web2py.gluon.html.DIV-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.DIV.xml web2py.gluon.html.DIV-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.HTML web2py.gluon.html.HTML-class.html +web2py.gluon.html.HTML.frameset web2py.gluon.html.HTML-class.html#frameset +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.HTML.tag web2py.gluon.html.HTML-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.DIV._fixup web2py.gluon.html.DIV-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.HTML.xml web2py.gluon.html.HTML-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.HTML.strict web2py.gluon.html.HTML-class.html#strict +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.HTML.html5 web2py.gluon.html.HTML-class.html#html5 +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.HTML.transitional web2py.gluon.html.HTML-class.html#transitional +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.I web2py.gluon.html.I-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.I.tag web2py.gluon.html.I-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.DIV._fixup web2py.gluon.html.DIV-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.DIV.xml web2py.gluon.html.DIV-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.IFRAME web2py.gluon.html.IFRAME-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.IFRAME.tag web2py.gluon.html.IFRAME-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.DIV._fixup web2py.gluon.html.DIV-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.DIV.xml web2py.gluon.html.DIV-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.IMG web2py.gluon.html.IMG-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.IMG.tag web2py.gluon.html.IMG-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.DIV._fixup web2py.gluon.html.DIV-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.DIV.xml web2py.gluon.html.DIV-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.INPUT web2py.gluon.html.INPUT-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.INPUT._validate web2py.gluon.html.INPUT-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.INPUT.tag web2py.gluon.html.INPUT-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.DIV._fixup web2py.gluon.html.DIV-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.INPUT.xml web2py.gluon.html.INPUT-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.INPUT._postprocessing web2py.gluon.html.INPUT-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.LABEL web2py.gluon.html.LABEL-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.LABEL.tag web2py.gluon.html.LABEL-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.DIV._fixup web2py.gluon.html.DIV-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.DIV.xml web2py.gluon.html.DIV-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.LEGEND web2py.gluon.html.LEGEND-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.LEGEND.tag web2py.gluon.html.LEGEND-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.DIV._fixup web2py.gluon.html.DIV-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.DIV.xml web2py.gluon.html.DIV-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.LI web2py.gluon.html.LI-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.LI.tag web2py.gluon.html.LI-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.DIV._fixup web2py.gluon.html.DIV-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.DIV.xml web2py.gluon.html.DIV-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.LINK web2py.gluon.html.LINK-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.LINK.tag web2py.gluon.html.LINK-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.DIV._fixup web2py.gluon.html.DIV-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.DIV.xml web2py.gluon.html.DIV-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.MARKMIN web2py.gluon.html.MARKMIN-class.html +web2py.gluon.html.MARKMIN.xml web2py.gluon.html.MARKMIN-class.html#xml +web2py.gluon.html.MARKMIN.elements web2py.gluon.html.MARKMIN-class.html#elements +web2py.gluon.html.MARKMIN.__str__ web2py.gluon.html.MARKMIN-class.html#__str__ +web2py.gluon.html.MARKMIN.flatten web2py.gluon.html.MARKMIN-class.html#flatten +web2py.gluon.html.MARKMIN.__init__ web2py.gluon.html.MARKMIN-class.html#__init__ +web2py.gluon.html.MENU web2py.gluon.html.MENU-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.MENU.serialize web2py.gluon.html.MENU-class.html#serialize +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.MENU.tag web2py.gluon.html.MENU-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.DIV._fixup web2py.gluon.html.DIV-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.MENU.xml web2py.gluon.html.MENU-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.MENU.__init__ web2py.gluon.html.MENU-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.META web2py.gluon.html.META-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.META.tag web2py.gluon.html.META-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.DIV._fixup web2py.gluon.html.DIV-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.DIV.xml web2py.gluon.html.DIV-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.OBJECT web2py.gluon.html.OBJECT-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.OBJECT.tag web2py.gluon.html.OBJECT-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.DIV._fixup web2py.gluon.html.DIV-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.DIV.xml web2py.gluon.html.DIV-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.OL web2py.gluon.html.OL-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.OL.tag web2py.gluon.html.OL-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.UL._fixup web2py.gluon.html.UL-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.DIV.xml web2py.gluon.html.DIV-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.OPTGROUP web2py.gluon.html.OPTGROUP-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.OPTGROUP.tag web2py.gluon.html.OPTGROUP-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.OPTGROUP._fixup web2py.gluon.html.OPTGROUP-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.DIV.xml web2py.gluon.html.DIV-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.OPTION web2py.gluon.html.OPTION-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.OPTION.tag web2py.gluon.html.OPTION-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.OPTION._fixup web2py.gluon.html.OPTION-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.DIV.xml web2py.gluon.html.DIV-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.P web2py.gluon.html.P-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.P.tag web2py.gluon.html.P-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.DIV._fixup web2py.gluon.html.DIV-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.P.xml web2py.gluon.html.P-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.PRE web2py.gluon.html.PRE-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.PRE.tag web2py.gluon.html.PRE-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.DIV._fixup web2py.gluon.html.DIV-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.DIV.xml web2py.gluon.html.DIV-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.SCRIPT web2py.gluon.html.SCRIPT-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.SCRIPT.tag web2py.gluon.html.SCRIPT-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.DIV._fixup web2py.gluon.html.DIV-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.SCRIPT.xml web2py.gluon.html.SCRIPT-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.SELECT web2py.gluon.html.SELECT-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.INPUT._validate web2py.gluon.html.INPUT-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.SELECT.tag web2py.gluon.html.SELECT-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.SELECT._fixup web2py.gluon.html.SELECT-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.INPUT.xml web2py.gluon.html.INPUT-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.SELECT._postprocessing web2py.gluon.html.SELECT-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.SPAN web2py.gluon.html.SPAN-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.SPAN.tag web2py.gluon.html.SPAN-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.DIV._fixup web2py.gluon.html.DIV-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.DIV.xml web2py.gluon.html.DIV-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.STYLE web2py.gluon.html.STYLE-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.STYLE.tag web2py.gluon.html.STYLE-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.DIV._fixup web2py.gluon.html.DIV-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.STYLE.xml web2py.gluon.html.STYLE-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.TABLE web2py.gluon.html.TABLE-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.TABLE.tag web2py.gluon.html.TABLE-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.TABLE._fixup web2py.gluon.html.TABLE-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.DIV.xml web2py.gluon.html.DIV-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.TBODY web2py.gluon.html.TBODY-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.TBODY.tag web2py.gluon.html.TBODY-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.TBODY._fixup web2py.gluon.html.TBODY-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.DIV.xml web2py.gluon.html.DIV-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.TD web2py.gluon.html.TD-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.TD.tag web2py.gluon.html.TD-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.DIV._fixup web2py.gluon.html.DIV-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.DIV.xml web2py.gluon.html.DIV-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.TEXTAREA web2py.gluon.html.TEXTAREA-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.INPUT._validate web2py.gluon.html.INPUT-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.TEXTAREA.tag web2py.gluon.html.TEXTAREA-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.DIV._fixup web2py.gluon.html.DIV-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.INPUT.xml web2py.gluon.html.INPUT-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.TEXTAREA._postprocessing web2py.gluon.html.TEXTAREA-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.TFOOT web2py.gluon.html.TFOOT-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.TFOOT.tag web2py.gluon.html.TFOOT-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.TFOOT._fixup web2py.gluon.html.TFOOT-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.DIV.xml web2py.gluon.html.DIV-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.TH web2py.gluon.html.TH-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.TH.tag web2py.gluon.html.TH-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.DIV._fixup web2py.gluon.html.DIV-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.DIV.xml web2py.gluon.html.DIV-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.THEAD web2py.gluon.html.THEAD-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.THEAD.tag web2py.gluon.html.THEAD-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.THEAD._fixup web2py.gluon.html.THEAD-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.DIV.xml web2py.gluon.html.DIV-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.TITLE web2py.gluon.html.TITLE-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.TITLE.tag web2py.gluon.html.TITLE-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.DIV._fixup web2py.gluon.html.DIV-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.DIV.xml web2py.gluon.html.DIV-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.TR web2py.gluon.html.TR-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.TR.tag web2py.gluon.html.TR-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.TR._fixup web2py.gluon.html.TR-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.DIV.xml web2py.gluon.html.DIV-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.TT web2py.gluon.html.TT-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.TT.tag web2py.gluon.html.TT-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.DIV._fixup web2py.gluon.html.DIV-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.DIV.xml web2py.gluon.html.DIV-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.UL web2py.gluon.html.UL-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.UL.tag web2py.gluon.html.UL-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.UL._fixup web2py.gluon.html.UL-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.DIV.xml web2py.gluon.html.DIV-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.XHTML web2py.gluon.html.XHTML-class.html +web2py.gluon.html.XHTML.frameset web2py.gluon.html.XHTML-class.html#frameset +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.XHTML.tag web2py.gluon.html.XHTML-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.DIV._fixup web2py.gluon.html.DIV-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.XHTML.xml web2py.gluon.html.XHTML-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.XHTML.strict web2py.gluon.html.XHTML-class.html#strict +web2py.gluon.html.DIV.__init__ web2py.gluon.html.DIV-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.XHTML.xmlns web2py.gluon.html.XHTML-class.html#xmlns +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.XHTML.transitional web2py.gluon.html.XHTML-class.html#transitional +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.html.XML web2py.gluon.html.XML-class.html +web2py.gluon.html.XML.__getslice__ web2py.gluon.html.XML-class.html#__getslice__ +web2py.gluon.html.XML.__str__ web2py.gluon.html.XML-class.html#__str__ +web2py.gluon.html.XML.__radd__ web2py.gluon.html.XML-class.html#__radd__ +web2py.gluon.html.XML.flatten web2py.gluon.html.XML-class.html#flatten +web2py.gluon.html.XML.__init__ web2py.gluon.html.XML-class.html#__init__ +web2py.gluon.html.XML.xml web2py.gluon.html.XML-class.html#xml +web2py.gluon.html.XML.__getattr__ web2py.gluon.html.XML-class.html#__getattr__ +web2py.gluon.html.XML.__cmp__ web2py.gluon.html.XML-class.html#__cmp__ +web2py.gluon.html.XML.__len__ web2py.gluon.html.XML-class.html#__len__ +web2py.gluon.html.XML.elements web2py.gluon.html.XML-class.html#elements +web2py.gluon.html.XML.__getitem__ web2py.gluon.html.XML-class.html#__getitem__ +web2py.gluon.html.XML.__iter__ web2py.gluon.html.XML-class.html#__iter__ +web2py.gluon.html.XML.__add__ web2py.gluon.html.XML-class.html#__add__ +web2py.gluon.html.XML.__hash__ web2py.gluon.html.XML-class.html#__hash__ +web2py.gluon.html.XmlComponent web2py.gluon.html.XmlComponent-class.html +web2py.gluon.html.XmlComponent.xml web2py.gluon.html.XmlComponent-class.html#xml +web2py.gluon.html.__TAG__ web2py.gluon.html.__TAG__-class.html +web2py.gluon.html.XmlComponent.xml web2py.gluon.html.XmlComponent-class.html#xml +web2py.gluon.html.__TAG__.__getitem__ web2py.gluon.html.__TAG__-class.html#__getitem__ +web2py.gluon.html.__TAG__.__getattr__ web2py.gluon.html.__TAG__-class.html#__getattr__ +web2py.gluon.html.__TAG__.__call__ web2py.gluon.html.__TAG__-class.html#__call__ +web2py.gluon.html.web2pyHTMLParser web2py.gluon.html.web2pyHTMLParser-class.html +web2py.gluon.html.web2pyHTMLParser.handle_entityref web2py.gluon.html.web2pyHTMLParser-class.html#handle_entityref +web2py.gluon.html.web2pyHTMLParser.handle_starttag web2py.gluon.html.web2pyHTMLParser-class.html#handle_starttag +web2py.gluon.html.web2pyHTMLParser.__init__ web2py.gluon.html.web2pyHTMLParser-class.html#__init__ +web2py.gluon.html.web2pyHTMLParser.handle_endtag web2py.gluon.html.web2pyHTMLParser-class.html#handle_endtag +web2py.gluon.html.web2pyHTMLParser.handle_charref web2py.gluon.html.web2pyHTMLParser-class.html#handle_charref +web2py.gluon.html.web2pyHTMLParser.handle_data web2py.gluon.html.web2pyHTMLParser-class.html#handle_data +web2py.gluon.http.HTTP web2py.gluon.http.HTTP-class.html +web2py.gluon.http.HTTP.__str__ web2py.gluon.http.HTTP-class.html#__str__ +web2py.gluon.http.HTTP.to web2py.gluon.http.HTTP-class.html#to +web2py.gluon.http.HTTP.message web2py.gluon.http.HTTP-class.html#message +web2py.gluon.http.HTTP.__init__ web2py.gluon.http.HTTP-class.html#__init__ +web2py.gluon.languages.lazyT web2py.gluon.languages.lazyT-class.html +web2py.gluon.languages.lazyT.__getslice__ web2py.gluon.languages.lazyT-class.html#__getslice__ +web2py.gluon.languages.lazyT.__str__ web2py.gluon.languages.lazyT-class.html#__str__ +web2py.gluon.languages.lazyT.__radd__ web2py.gluon.languages.lazyT-class.html#__radd__ +web2py.gluon.languages.lazyT.__init__ web2py.gluon.languages.lazyT-class.html#__init__ +web2py.gluon.languages.lazyT.xml web2py.gluon.languages.lazyT-class.html#xml +web2py.gluon.languages.lazyT.__getattr__ web2py.gluon.languages.lazyT-class.html#__getattr__ +web2py.gluon.languages.lazyT.__cmp__ web2py.gluon.languages.lazyT-class.html#__cmp__ +web2py.gluon.languages.lazyT.decode web2py.gluon.languages.lazyT-class.html#decode +web2py.gluon.languages.lazyT.encode web2py.gluon.languages.lazyT-class.html#encode +web2py.gluon.languages.lazyT.__len__ web2py.gluon.languages.lazyT-class.html#__len__ +web2py.gluon.languages.lazyT.__ne__ web2py.gluon.languages.lazyT-class.html#__ne__ +web2py.gluon.languages.lazyT.__getitem__ web2py.gluon.languages.lazyT-class.html#__getitem__ +web2py.gluon.languages.lazyT.read web2py.gluon.languages.lazyT-class.html#read +web2py.gluon.languages.lazyT.__iter__ web2py.gluon.languages.lazyT-class.html#__iter__ +web2py.gluon.languages.lazyT.T web2py.gluon.languages.lazyT-class.html#T +web2py.gluon.languages.lazyT.__add__ web2py.gluon.languages.lazyT-class.html#__add__ +web2py.gluon.languages.lazyT.__eq__ web2py.gluon.languages.lazyT-class.html#__eq__ +web2py.gluon.languages.lazyT.__mod__ web2py.gluon.languages.lazyT-class.html#__mod__ +web2py.gluon.languages.lazyT.m web2py.gluon.languages.lazyT-class.html#m +web2py.gluon.languages.lazyT.s web2py.gluon.languages.lazyT-class.html#s +web2py.gluon.languages.lazyT.__repr__ web2py.gluon.languages.lazyT-class.html#__repr__ +web2py.gluon.languages.lazyT.__hash__ web2py.gluon.languages.lazyT-class.html#__hash__ +web2py.gluon.languages.translator web2py.gluon.languages.translator-class.html +web2py.gluon.languages.translator.force web2py.gluon.languages.translator-class.html#force +web2py.gluon.languages.translator.get_possible_languages web2py.gluon.languages.translator-class.html#get_possible_languages +web2py.gluon.languages.translator.__call__ web2py.gluon.languages.translator-class.html#__call__ +web2py.gluon.languages.translator.translate web2py.gluon.languages.translator-class.html#translate +web2py.gluon.languages.translator.__init__ web2py.gluon.languages.translator-class.html#__init__ +web2py.gluon.languages.translator.set_current_languages web2py.gluon.languages.translator-class.html#set_current_languages +web2py.gluon.main.HttpServer web2py.gluon.main.HttpServer-class.html +web2py.gluon.main.HttpServer.stop web2py.gluon.main.HttpServer-class.html#stop +web2py.gluon.main.HttpServer.start web2py.gluon.main.HttpServer-class.html#start +web2py.gluon.main.HttpServer.__init__ web2py.gluon.main.HttpServer-class.html#__init__ +web2py.gluon.newcron.Token web2py.gluon.newcron.Token-class.html +web2py.gluon.newcron.Token.acquire web2py.gluon.newcron.Token-class.html#acquire +web2py.gluon.newcron.Token.release web2py.gluon.newcron.Token-class.html#release +web2py.gluon.newcron.Token.__init__ web2py.gluon.newcron.Token-class.html#__init__ +web2py.gluon.newcron.cronlauncher web2py.gluon.newcron.cronlauncher-class.html +web2py.gluon.newcron.cronlauncher.run web2py.gluon.newcron.cronlauncher-class.html#run +web2py.gluon.newcron.cronlauncher.__init__ web2py.gluon.newcron.cronlauncher-class.html#__init__ +web2py.gluon.newcron.extcron web2py.gluon.newcron.extcron-class.html +web2py.gluon.newcron.extcron.run web2py.gluon.newcron.extcron-class.html#run +web2py.gluon.newcron.extcron.__init__ web2py.gluon.newcron.extcron-class.html#__init__ +web2py.gluon.newcron.hardcron web2py.gluon.newcron.hardcron-class.html +web2py.gluon.newcron.hardcron.run web2py.gluon.newcron.hardcron-class.html#run +web2py.gluon.newcron.hardcron.__init__ web2py.gluon.newcron.hardcron-class.html#__init__ +web2py.gluon.newcron.hardcron.launch web2py.gluon.newcron.hardcron-class.html#launch +web2py.gluon.newcron.softcron web2py.gluon.newcron.softcron-class.html +web2py.gluon.newcron.softcron.run web2py.gluon.newcron.softcron-class.html#run +web2py.gluon.newcron.softcron.__init__ web2py.gluon.newcron.softcron-class.html#__init__ +web2py.gluon.restricted.RestrictedError web2py.gluon.restricted.RestrictedError-class.html +web2py.gluon.restricted.RestrictedError.load web2py.gluon.restricted.RestrictedError-class.html#load +web2py.gluon.restricted.RestrictedError.log web2py.gluon.restricted.RestrictedError-class.html#log +web2py.gluon.restricted.RestrictedError.__init__ web2py.gluon.restricted.RestrictedError-class.html#__init__ +exceptions.Exception.__new__ exceptions.Exception-class.html#__new__ +web2py.gluon.restricted.TicketStorage web2py.gluon.restricted.TicketStorage-class.html +web2py.gluon.restricted.TicketStorage.load web2py.gluon.restricted.TicketStorage-class.html#load +web2py.gluon.storage.Storage.getlist web2py.gluon.storage.Storage-class.html#getlist +web2py.gluon.restricted.TicketStorage.__init__ web2py.gluon.restricted.TicketStorage-class.html#__init__ +web2py.gluon.storage.Storage.__setattr__ web2py.gluon.storage.Storage-class.html#__setattr__ +web2py.gluon.storage.Storage.__getattr__ web2py.gluon.storage.Storage-class.html#__getattr__ +web2py.gluon.storage.Storage.__getstate__ web2py.gluon.storage.Storage-class.html#__getstate__ +web2py.gluon.restricted.TicketStorage.store web2py.gluon.restricted.TicketStorage-class.html#store +web2py.gluon.storage.Storage.__setstate__ web2py.gluon.storage.Storage-class.html#__setstate__ +web2py.gluon.storage.Storage.getlast web2py.gluon.storage.Storage-class.html#getlast +web2py.gluon.restricted.TicketStorage._error_file web2py.gluon.restricted.TicketStorage-class.html#_error_file +web2py.gluon.storage.Storage.__delattr__ web2py.gluon.storage.Storage-class.html#__delattr__ +web2py.gluon.storage.Storage.__repr__ web2py.gluon.storage.Storage-class.html#__repr__ +web2py.gluon.restricted.TicketStorage._get_table web2py.gluon.restricted.TicketStorage-class.html#_get_table +web2py.gluon.restricted.TicketStorage._store_on_disk web2py.gluon.restricted.TicketStorage-class.html#_store_on_disk +web2py.gluon.restricted.TicketStorage._store_in_db web2py.gluon.restricted.TicketStorage-class.html#_store_in_db +web2py.gluon.storage.Storage.getfirst web2py.gluon.storage.Storage-class.html#getfirst +web2py.gluon.rewrite.MapUrlIn web2py.gluon.rewrite.MapUrlIn-class.html +web2py.gluon.rewrite.MapUrlIn.map_controller web2py.gluon.rewrite.MapUrlIn-class.html#map_controller +web2py.gluon.rewrite.MapUrlIn.validate_args web2py.gluon.rewrite.MapUrlIn-class.html#validate_args +web2py.gluon.rewrite.MapUrlIn.arg0 web2py.gluon.rewrite.MapUrlIn-class.html#arg0 +web2py.gluon.rewrite.MapUrlIn.map_static web2py.gluon.rewrite.MapUrlIn-class.html#map_static +web2py.gluon.rewrite.MapUrlIn.map_language web2py.gluon.rewrite.MapUrlIn-class.html#map_language +web2py.gluon.rewrite.MapUrlIn.map_app web2py.gluon.rewrite.MapUrlIn-class.html#map_app +web2py.gluon.rewrite.MapUrlIn.__init__ web2py.gluon.rewrite.MapUrlIn-class.html#__init__ +web2py.gluon.rewrite.MapUrlIn.map_prefix web2py.gluon.rewrite.MapUrlIn-class.html#map_prefix +web2py.gluon.rewrite.MapUrlIn.update_request web2py.gluon.rewrite.MapUrlIn-class.html#update_request +web2py.gluon.rewrite.MapUrlIn.map_function web2py.gluon.rewrite.MapUrlIn-class.html#map_function +web2py.gluon.rewrite.MapUrlIn.map_root_static web2py.gluon.rewrite.MapUrlIn-class.html#map_root_static +web2py.gluon.rewrite.MapUrlIn.harg0 web2py.gluon.rewrite.MapUrlIn-class.html#harg0 +web2py.gluon.rewrite.MapUrlIn.pop_arg_if web2py.gluon.rewrite.MapUrlIn-class.html#pop_arg_if +web2py.gluon.rewrite.MapUrlOut web2py.gluon.rewrite.MapUrlOut-class.html +web2py.gluon.rewrite.MapUrlOut.acf web2py.gluon.rewrite.MapUrlOut-class.html#acf +web2py.gluon.rewrite.MapUrlOut.omit_lang web2py.gluon.rewrite.MapUrlOut-class.html#omit_lang +web2py.gluon.rewrite.MapUrlOut.build_acf web2py.gluon.rewrite.MapUrlOut-class.html#build_acf +web2py.gluon.rewrite.MapUrlOut.omit_acf web2py.gluon.rewrite.MapUrlOut-class.html#omit_acf +web2py.gluon.rewrite.MapUrlOut.__init__ web2py.gluon.rewrite.MapUrlOut-class.html#__init__ +web2py.gluon.rocket.BadRequest web2py.gluon.rocket.BadRequest-class.html +exceptions.Exception.__init__ exceptions.Exception-class.html#__init__ +exceptions.Exception.__new__ exceptions.Exception-class.html#__new__ +web2py.gluon.rocket.ChunkedReader web2py.gluon.rocket.ChunkedReader-class.html +web2py.gluon.rocket.ChunkedReader._read_header web2py.gluon.rocket.ChunkedReader-class.html#_read_header +web2py.gluon.rocket.ChunkedReader.read web2py.gluon.rocket.ChunkedReader-class.html#read +web2py.gluon.rocket.ChunkedReader.readlines web2py.gluon.rocket.ChunkedReader-class.html#readlines +web2py.gluon.rocket.ChunkedReader.readline web2py.gluon.rocket.ChunkedReader-class.html#readline +web2py.gluon.rocket.ChunkedReader.__init__ web2py.gluon.rocket.ChunkedReader-class.html#__init__ +web2py.gluon.rocket.Connection web2py.gluon.rocket.Connection-class.html +web2py.gluon.rocket.Connection.secure web2py.gluon.rocket.Connection-class.html#secure +web2py.gluon.rocket.Connection.shutdown web2py.gluon.rocket.Connection-class.html#shutdown +web2py.gluon.rocket.Connection.close web2py.gluon.rocket.Connection-class.html#close +web2py.gluon.rocket.Connection.__init__ web2py.gluon.rocket.Connection-class.html#__init__ +web2py.gluon.rocket.Connection.makefile web2py.gluon.rocket.Connection-class.html#makefile +web2py.gluon.rocket.Connection.server_port web2py.gluon.rocket.Connection-class.html#server_port +web2py.gluon.rocket.Connection.start_time web2py.gluon.rocket.Connection-class.html#start_time +web2py.gluon.rocket.Connection.ssl web2py.gluon.rocket.Connection-class.html#ssl +web2py.gluon.rocket.Connection.client_port web2py.gluon.rocket.Connection-class.html#client_port +web2py.gluon.rocket.Connection.setblocking web2py.gluon.rocket.Connection-class.html#setblocking +web2py.gluon.rocket.Connection.fileno web2py.gluon.rocket.Connection-class.html#fileno +web2py.gluon.rocket.Connection.socket web2py.gluon.rocket.Connection-class.html#socket +web2py.gluon.rocket.Connection.client_addr web2py.gluon.rocket.Connection-class.html#client_addr +web2py.gluon.rocket.Connection.sendall web2py.gluon.rocket.Connection-class.html#sendall +web2py.gluon.rocket.FileWrapper web2py.gluon.rocket.FileWrapper-class.html +web2py.gluon.rocket.FileWrapper.__iter__ web2py.gluon.rocket.FileWrapper-class.html#__iter__ +web2py.gluon.rocket.FileWrapper.__init__ web2py.gluon.rocket.FileWrapper-class.html#__init__ +web2py.gluon.rocket.FileWrapper.__getitem__ web2py.gluon.rocket.FileWrapper-class.html#__getitem__ +web2py.gluon.rocket.FileWrapper.next web2py.gluon.rocket.FileWrapper-class.html#next +web2py.gluon.rocket.Headers web2py.gluon.rocket.Headers-class.html +web2py.gluon.rocket.Headers.__delitem__ web2py.gluon.rocket.Headers-class.html#__delitem__ +web2py.gluon.rocket.Headers.setdefault web2py.gluon.rocket.Headers-class.html#setdefault +web2py.gluon.rocket.Headers.__getitem__ web2py.gluon.rocket.Headers-class.html#__getitem__ +web2py.gluon.rocket.Headers.__contains__ web2py.gluon.rocket.Headers-class.html#__contains__ +web2py.gluon.rocket.Headers.keys web2py.gluon.rocket.Headers-class.html#keys +web2py.gluon.rocket.Headers.items web2py.gluon.rocket.Headers-class.html#items +web2py.gluon.rocket.Headers.__str__ web2py.gluon.rocket.Headers-class.html#__str__ +web2py.gluon.rocket.Headers.get_all web2py.gluon.rocket.Headers-class.html#get_all +web2py.gluon.rocket.Headers.add_header web2py.gluon.rocket.Headers-class.html#add_header +web2py.gluon.rocket.Headers.__setitem__ web2py.gluon.rocket.Headers-class.html#__setitem__ +web2py.gluon.rocket.Headers.has_key web2py.gluon.rocket.Headers-class.html#has_key +web2py.gluon.rocket.Headers.values web2py.gluon.rocket.Headers-class.html#values +web2py.gluon.rocket.Headers.__len__ web2py.gluon.rocket.Headers-class.html#__len__ +web2py.gluon.rocket.Headers.get web2py.gluon.rocket.Headers-class.html#get +web2py.gluon.rocket.Headers.__init__ web2py.gluon.rocket.Headers-class.html#__init__ +web2py.gluon.rocket.Headers.__repr__ web2py.gluon.rocket.Headers-class.html#__repr__ +web2py.gluon.rocket.Listener web2py.gluon.rocket.Listener-class.html +web2py.gluon.rocket.Listener.run web2py.gluon.rocket.Listener-class.html#run +web2py.gluon.rocket.Listener.__init__ web2py.gluon.rocket.Listener-class.html#__init__ +web2py.gluon.rocket.Listener.wrap_socket web2py.gluon.rocket.Listener-class.html#wrap_socket +web2py.gluon.rocket.Monitor web2py.gluon.rocket.Monitor-class.html +web2py.gluon.rocket.Monitor.run web2py.gluon.rocket.Monitor-class.html#run +web2py.gluon.rocket.Monitor.__init__ web2py.gluon.rocket.Monitor-class.html#__init__ +web2py.gluon.rocket.Monitor.stop web2py.gluon.rocket.Monitor-class.html#stop +web2py.gluon.rocket.NullHandler web2py.gluon.rocket.NullHandler-class.html +web2py.gluon.rocket.NullHandler.emit web2py.gluon.rocket.NullHandler-class.html#emit +web2py.gluon.rocket.Rocket web2py.gluon.rocket.Rocket-class.html +web2py.gluon.rocket.Rocket._sigterm web2py.gluon.rocket.Rocket-class.html#_sigterm +web2py.gluon.rocket.Rocket.stop web2py.gluon.rocket.Rocket-class.html#stop +web2py.gluon.rocket.Rocket._sighup web2py.gluon.rocket.Rocket-class.html#_sighup +web2py.gluon.rocket.Rocket.start web2py.gluon.rocket.Rocket-class.html#start +web2py.gluon.rocket.Rocket.__init__ web2py.gluon.rocket.Rocket-class.html#__init__ +web2py.gluon.rocket.Rocket.restart web2py.gluon.rocket.Rocket-class.html#restart +web2py.gluon.rocket.SSLError web2py.gluon.rocket.SSLError-class.html +exceptions.Exception.__init__ exceptions.Exception-class.html#__init__ +exceptions.Exception.__new__ exceptions.Exception-class.html#__new__ +web2py.gluon.rocket.SocketClosed web2py.gluon.rocket.SocketClosed-class.html +exceptions.Exception.__init__ exceptions.Exception-class.html#__init__ +exceptions.Exception.__new__ exceptions.Exception-class.html#__new__ +web2py.gluon.rocket.SocketTimeout web2py.gluon.rocket.SocketTimeout-class.html +exceptions.Exception.__init__ exceptions.Exception-class.html#__init__ +exceptions.Exception.__new__ exceptions.Exception-class.html#__new__ +web2py.gluon.rocket.ThreadPool web2py.gluon.rocket.ThreadPool-class.html +web2py.gluon.rocket.ThreadPool.stop web2py.gluon.rocket.ThreadPool-class.html#stop +web2py.gluon.rocket.ThreadPool.grow web2py.gluon.rocket.ThreadPool-class.html#grow +web2py.gluon.rocket.ThreadPool.start web2py.gluon.rocket.ThreadPool-class.html#start +web2py.gluon.rocket.ThreadPool.dynamic_resize web2py.gluon.rocket.ThreadPool-class.html#dynamic_resize +web2py.gluon.rocket.ThreadPool.bring_out_your_dead web2py.gluon.rocket.ThreadPool-class.html#bring_out_your_dead +web2py.gluon.rocket.ThreadPool.shrink web2py.gluon.rocket.ThreadPool-class.html#shrink +web2py.gluon.rocket.ThreadPool.__init__ web2py.gluon.rocket.ThreadPool-class.html#__init__ +web2py.gluon.rocket.WSGIWorker web2py.gluon.rocket.WSGIWorker-class.html +web2py.gluon.rocket.WSGIWorker.start_response web2py.gluon.rocket.WSGIWorker-class.html#start_response +web2py.gluon.rocket.Worker.read_request_line web2py.gluon.rocket.Worker-class.html#read_request_line +web2py.gluon.rocket.WSGIWorker.run_app web2py.gluon.rocket.WSGIWorker-class.html#run_app +web2py.gluon.rocket.WSGIWorker.write_warning web2py.gluon.rocket.WSGIWorker-class.html#write_warning +web2py.gluon.rocket.WSGIWorker.__init__ web2py.gluon.rocket.WSGIWorker-class.html#__init__ +web2py.gluon.rocket.WSGIWorker.write web2py.gluon.rocket.WSGIWorker-class.html#write +web2py.gluon.rocket.Worker._read_request_line_jython web2py.gluon.rocket.Worker-class.html#_read_request_line_jython +web2py.gluon.rocket.Worker.send_response web2py.gluon.rocket.Worker-class.html#send_response +web2py.gluon.rocket.Worker.run web2py.gluon.rocket.Worker-class.html#run +web2py.gluon.rocket.WSGIWorker.build_environ web2py.gluon.rocket.WSGIWorker-class.html#build_environ +web2py.gluon.rocket.Worker._handleError web2py.gluon.rocket.Worker-class.html#_handleError +web2py.gluon.rocket.WSGIWorker.send_headers web2py.gluon.rocket.WSGIWorker-class.html#send_headers +web2py.gluon.rocket.Worker.kill web2py.gluon.rocket.Worker-class.html#kill +web2py.gluon.rocket.Worker.read_headers web2py.gluon.rocket.Worker-class.html#read_headers +web2py.gluon.rocket.Worker web2py.gluon.rocket.Worker-class.html +web2py.gluon.rocket.Worker.read_request_line web2py.gluon.rocket.Worker-class.html#read_request_line +web2py.gluon.rocket.Worker.run_app web2py.gluon.rocket.Worker-class.html#run_app +web2py.gluon.rocket.Worker.kill web2py.gluon.rocket.Worker-class.html#kill +web2py.gluon.rocket.Worker.__init__ web2py.gluon.rocket.Worker-class.html#__init__ +web2py.gluon.rocket.Worker._read_request_line_jython web2py.gluon.rocket.Worker-class.html#_read_request_line_jython +web2py.gluon.rocket.Worker.send_response web2py.gluon.rocket.Worker-class.html#send_response +web2py.gluon.rocket.Worker.run web2py.gluon.rocket.Worker-class.html#run +web2py.gluon.rocket.Worker._handleError web2py.gluon.rocket.Worker-class.html#_handleError +web2py.gluon.rocket.Worker.read_headers web2py.gluon.rocket.Worker-class.html#read_headers +web2py.gluon.sanitizer.XssCleaner web2py.gluon.sanitizer.XssCleaner-class.html +web2py.gluon.sanitizer.XssCleaner.xtags web2py.gluon.sanitizer.XssCleaner-class.html#xtags +web2py.gluon.sanitizer.XssCleaner.handle_data web2py.gluon.sanitizer.XssCleaner-class.html#handle_data +web2py.gluon.sanitizer.XssCleaner.handle_charref web2py.gluon.sanitizer.XssCleaner-class.html#handle_charref +web2py.gluon.sanitizer.XssCleaner.strip web2py.gluon.sanitizer.XssCleaner-class.html#strip +web2py.gluon.sanitizer.XssCleaner.unknown_starttag web2py.gluon.sanitizer.XssCleaner-class.html#unknown_starttag +web2py.gluon.sanitizer.XssCleaner.handle_entityref web2py.gluon.sanitizer.XssCleaner-class.html#handle_entityref +web2py.gluon.sanitizer.XssCleaner.handle_comment web2py.gluon.sanitizer.XssCleaner-class.html#handle_comment +web2py.gluon.sanitizer.XssCleaner.handle_starttag web2py.gluon.sanitizer.XssCleaner-class.html#handle_starttag +web2py.gluon.sanitizer.XssCleaner.__init__ web2py.gluon.sanitizer.XssCleaner-class.html#__init__ +web2py.gluon.sanitizer.XssCleaner.url_is_acceptable web2py.gluon.sanitizer.XssCleaner-class.html#url_is_acceptable +web2py.gluon.sanitizer.XssCleaner.handle_endtag web2py.gluon.sanitizer.XssCleaner-class.html#handle_endtag +web2py.gluon.sanitizer.XssCleaner.unknown_endtag web2py.gluon.sanitizer.XssCleaner-class.html#unknown_endtag +web2py.gluon.sqlhtml.AutocompleteWidget web2py.gluon.sqlhtml.AutocompleteWidget-class.html +web2py.gluon.sqlhtml.AutocompleteWidget.callback web2py.gluon.sqlhtml.AutocompleteWidget-class.html#callback +web2py.gluon.sqlhtml.AutocompleteWidget.__call__ web2py.gluon.sqlhtml.AutocompleteWidget-class.html#__call__ +web2py.gluon.sqlhtml.AutocompleteWidget.__init__ web2py.gluon.sqlhtml.AutocompleteWidget-class.html#__init__ +web2py.gluon.sqlhtml.BooleanWidget web2py.gluon.sqlhtml.BooleanWidget-class.html +web2py.gluon.sqlhtml.BooleanWidget.widget web2py.gluon.sqlhtml.BooleanWidget-class.html#widget +web2py.gluon.sqlhtml.FormWidget._attributes web2py.gluon.sqlhtml.FormWidget-class.html#_attributes +web2py.gluon.sqlhtml.CheckboxesWidget web2py.gluon.sqlhtml.CheckboxesWidget-class.html +web2py.gluon.sqlhtml.CheckboxesWidget.widget web2py.gluon.sqlhtml.CheckboxesWidget-class.html#widget +web2py.gluon.sqlhtml.FormWidget._attributes web2py.gluon.sqlhtml.FormWidget-class.html#_attributes +web2py.gluon.sqlhtml.OptionsWidget.has_options web2py.gluon.sqlhtml.OptionsWidget-class.html#has_options +web2py.gluon.sqlhtml.DateWidget web2py.gluon.sqlhtml.DateWidget-class.html +web2py.gluon.sqlhtml.StringWidget.widget web2py.gluon.sqlhtml.StringWidget-class.html#widget +web2py.gluon.sqlhtml.FormWidget._attributes web2py.gluon.sqlhtml.FormWidget-class.html#_attributes +web2py.gluon.sqlhtml.DatetimeWidget web2py.gluon.sqlhtml.DatetimeWidget-class.html +web2py.gluon.sqlhtml.StringWidget.widget web2py.gluon.sqlhtml.StringWidget-class.html#widget +web2py.gluon.sqlhtml.FormWidget._attributes web2py.gluon.sqlhtml.FormWidget-class.html#_attributes +web2py.gluon.sqlhtml.DecimalWidget web2py.gluon.sqlhtml.DecimalWidget-class.html +web2py.gluon.sqlhtml.StringWidget.widget web2py.gluon.sqlhtml.StringWidget-class.html#widget +web2py.gluon.sqlhtml.FormWidget._attributes web2py.gluon.sqlhtml.FormWidget-class.html#_attributes +web2py.gluon.sqlhtml.DoubleWidget web2py.gluon.sqlhtml.DoubleWidget-class.html +web2py.gluon.sqlhtml.StringWidget.widget web2py.gluon.sqlhtml.StringWidget-class.html#widget +web2py.gluon.sqlhtml.FormWidget._attributes web2py.gluon.sqlhtml.FormWidget-class.html#_attributes +web2py.gluon.sqlhtml.FormWidget web2py.gluon.sqlhtml.FormWidget-class.html +web2py.gluon.sqlhtml.FormWidget.widget web2py.gluon.sqlhtml.FormWidget-class.html#widget +web2py.gluon.sqlhtml.FormWidget._attributes web2py.gluon.sqlhtml.FormWidget-class.html#_attributes +web2py.gluon.sqlhtml.IntegerWidget web2py.gluon.sqlhtml.IntegerWidget-class.html +web2py.gluon.sqlhtml.StringWidget.widget web2py.gluon.sqlhtml.StringWidget-class.html#widget +web2py.gluon.sqlhtml.FormWidget._attributes web2py.gluon.sqlhtml.FormWidget-class.html#_attributes +web2py.gluon.sqlhtml.ListWidget web2py.gluon.sqlhtml.ListWidget-class.html +web2py.gluon.sqlhtml.ListWidget.widget web2py.gluon.sqlhtml.ListWidget-class.html#widget +web2py.gluon.sqlhtml.FormWidget._attributes web2py.gluon.sqlhtml.FormWidget-class.html#_attributes +web2py.gluon.sqlhtml.MultipleOptionsWidget web2py.gluon.sqlhtml.MultipleOptionsWidget-class.html +web2py.gluon.sqlhtml.MultipleOptionsWidget.widget web2py.gluon.sqlhtml.MultipleOptionsWidget-class.html#widget +web2py.gluon.sqlhtml.FormWidget._attributes web2py.gluon.sqlhtml.FormWidget-class.html#_attributes +web2py.gluon.sqlhtml.OptionsWidget.has_options web2py.gluon.sqlhtml.OptionsWidget-class.html#has_options +web2py.gluon.sqlhtml.OptionsWidget web2py.gluon.sqlhtml.OptionsWidget-class.html +web2py.gluon.sqlhtml.OptionsWidget.widget web2py.gluon.sqlhtml.OptionsWidget-class.html#widget +web2py.gluon.sqlhtml.FormWidget._attributes web2py.gluon.sqlhtml.FormWidget-class.html#_attributes +web2py.gluon.sqlhtml.OptionsWidget.has_options web2py.gluon.sqlhtml.OptionsWidget-class.html#has_options +web2py.gluon.sqlhtml.PasswordWidget web2py.gluon.sqlhtml.PasswordWidget-class.html +web2py.gluon.sqlhtml.PasswordWidget.widget web2py.gluon.sqlhtml.PasswordWidget-class.html#widget +web2py.gluon.sqlhtml.FormWidget._attributes web2py.gluon.sqlhtml.FormWidget-class.html#_attributes +web2py.gluon.sqlhtml.PasswordWidget.DEFAULT_PASSWORD_DISPLAY web2py.gluon.sqlhtml.PasswordWidget-class.html#DEFAULT_PASSWORD_DISPLAY +web2py.gluon.sqlhtml.RadioWidget web2py.gluon.sqlhtml.RadioWidget-class.html +web2py.gluon.sqlhtml.RadioWidget.widget web2py.gluon.sqlhtml.RadioWidget-class.html#widget +web2py.gluon.sqlhtml.FormWidget._attributes web2py.gluon.sqlhtml.FormWidget-class.html#_attributes +web2py.gluon.sqlhtml.OptionsWidget.has_options web2py.gluon.sqlhtml.OptionsWidget-class.html#has_options +web2py.gluon.sqlhtml.SQLFORM web2py.gluon.sqlhtml.SQLFORM-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.FORM.process web2py.gluon.html.FORM-class.html#process +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.FORM.tag web2py.gluon.html.FORM-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.sqlhtml.SQLFORM.FIELDNAME_REQUEST_DELETE web2py.gluon.sqlhtml.SQLFORM-class.html#FIELDNAME_REQUEST_DELETE +web2py.gluon.html.DIV._fixup web2py.gluon.html.DIV-class.html#_fixup +web2py.gluon.sqlhtml.SQLFORM.createform web2py.gluon.sqlhtml.SQLFORM-class.html#createform +web2py.gluon.sqlhtml.SQLFORM.__init__ web2py.gluon.sqlhtml.SQLFORM-class.html#__init__ +web2py.gluon.html.FORM.xml web2py.gluon.html.FORM-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.sqlhtml.SQLFORM.ID_LABEL_SUFFIX web2py.gluon.sqlhtml.SQLFORM-class.html#ID_LABEL_SUFFIX +web2py.gluon.sqlhtml.SQLFORM.factory web2py.gluon.sqlhtml.SQLFORM-class.html#factory +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.sqlhtml.SQLFORM.ID_ROW_SUFFIX web2py.gluon.sqlhtml.SQLFORM-class.html#ID_ROW_SUFFIX +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.sqlhtml.SQLFORM.widgets web2py.gluon.sqlhtml.SQLFORM-class.html#widgets +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.sqlhtml.SQLFORM.FIELDKEY_DELETE_RECORD web2py.gluon.sqlhtml.SQLFORM-class.html#FIELDKEY_DELETE_RECORD +web2py.gluon.html.FORM._postprocessing web2py.gluon.html.FORM-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.FORM.validate web2py.gluon.html.FORM-class.html#validate +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.sqlhtml.SQLFORM.accepts web2py.gluon.sqlhtml.SQLFORM-class.html#accepts +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.FORM.hidden_fields web2py.gluon.html.FORM-class.html#hidden_fields +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.sqlhtml.SQLTABLE web2py.gluon.sqlhtml.SQLTABLE-class.html +web2py.gluon.html.DIV.regex_id web2py.gluon.html.DIV-class.html#regex_id +web2py.gluon.html.DIV._validate web2py.gluon.html.DIV-class.html#_validate +web2py.gluon.html.DIV.regex_tag web2py.gluon.html.DIV-class.html#regex_tag +web2py.gluon.html.DIV.__str__ web2py.gluon.html.DIV-class.html#__str__ +web2py.gluon.html.DIV.sibling web2py.gluon.html.DIV-class.html#sibling +web2py.gluon.html.TABLE.tag web2py.gluon.html.TABLE-class.html#tag +web2py.gluon.html.DIV.flatten web2py.gluon.html.DIV-class.html#flatten +web2py.gluon.html.TABLE._fixup web2py.gluon.html.TABLE-class.html#_fixup +web2py.gluon.html.DIV.append web2py.gluon.html.DIV-class.html#append +web2py.gluon.html.DIV.xml web2py.gluon.html.DIV-class.html#xml +web2py.gluon.html.DIV._setnode web2py.gluon.html.DIV-class.html#_setnode +web2py.gluon.sqlhtml.SQLTABLE.style web2py.gluon.sqlhtml.SQLTABLE-class.html#style +web2py.gluon.html.DIV._xml web2py.gluon.html.DIV-class.html#_xml +web2py.gluon.sqlhtml.SQLTABLE.__init__ web2py.gluon.sqlhtml.SQLTABLE-class.html#__init__ +web2py.gluon.html.DIV.siblings web2py.gluon.html.DIV-class.html#siblings +web2py.gluon.html.DIV.__len__ web2py.gluon.html.DIV-class.html#__len__ +web2py.gluon.html.DIV.elements web2py.gluon.html.DIV-class.html#elements +web2py.gluon.html.DIV.__getitem__ web2py.gluon.html.DIV-class.html#__getitem__ +web2py.gluon.html.DIV.insert web2py.gluon.html.DIV-class.html#insert +web2py.gluon.html.DIV._postprocessing web2py.gluon.html.DIV-class.html#_postprocessing +web2py.gluon.html.DIV.update web2py.gluon.html.DIV-class.html#update +web2py.gluon.html.DIV.__setitem__ web2py.gluon.html.DIV-class.html#__setitem__ +web2py.gluon.html.DIV.__delitem__ web2py.gluon.html.DIV-class.html#__delitem__ +web2py.gluon.html.DIV.regex_class web2py.gluon.html.DIV-class.html#regex_class +web2py.gluon.html.DIV.__nonzero__ web2py.gluon.html.DIV-class.html#__nonzero__ +web2py.gluon.html.DIV._traverse web2py.gluon.html.DIV-class.html#_traverse +web2py.gluon.html.DIV.element web2py.gluon.html.DIV-class.html#element +web2py.gluon.html.DIV._wrap_components web2py.gluon.html.DIV-class.html#_wrap_components +web2py.gluon.html.DIV.regex_attr web2py.gluon.html.DIV-class.html#regex_attr +web2py.gluon.sqlhtml.StringWidget web2py.gluon.sqlhtml.StringWidget-class.html +web2py.gluon.sqlhtml.StringWidget.widget web2py.gluon.sqlhtml.StringWidget-class.html#widget +web2py.gluon.sqlhtml.FormWidget._attributes web2py.gluon.sqlhtml.FormWidget-class.html#_attributes +web2py.gluon.sqlhtml.TextWidget web2py.gluon.sqlhtml.TextWidget-class.html +web2py.gluon.sqlhtml.TextWidget.widget web2py.gluon.sqlhtml.TextWidget-class.html#widget +web2py.gluon.sqlhtml.FormWidget._attributes web2py.gluon.sqlhtml.FormWidget-class.html#_attributes +web2py.gluon.sqlhtml.TimeWidget web2py.gluon.sqlhtml.TimeWidget-class.html +web2py.gluon.sqlhtml.StringWidget.widget web2py.gluon.sqlhtml.StringWidget-class.html#widget +web2py.gluon.sqlhtml.FormWidget._attributes web2py.gluon.sqlhtml.FormWidget-class.html#_attributes +web2py.gluon.sqlhtml.UploadWidget web2py.gluon.sqlhtml.UploadWidget-class.html +web2py.gluon.sqlhtml.UploadWidget.represent web2py.gluon.sqlhtml.UploadWidget-class.html#represent +web2py.gluon.sqlhtml.UploadWidget.widget web2py.gluon.sqlhtml.UploadWidget-class.html#widget +web2py.gluon.sqlhtml.UploadWidget.GENERIC_DESCRIPTION web2py.gluon.sqlhtml.UploadWidget-class.html#GENERIC_DESCRIPTION +web2py.gluon.sqlhtml.UploadWidget.DELETE_FILE web2py.gluon.sqlhtml.UploadWidget-class.html#DELETE_FILE +web2py.gluon.sqlhtml.UploadWidget.is_image web2py.gluon.sqlhtml.UploadWidget-class.html#is_image +web2py.gluon.sqlhtml.UploadWidget.DEFAULT_WIDTH web2py.gluon.sqlhtml.UploadWidget-class.html#DEFAULT_WIDTH +web2py.gluon.sqlhtml.FormWidget._attributes web2py.gluon.sqlhtml.FormWidget-class.html#_attributes +web2py.gluon.sqlhtml.UploadWidget.ID_DELETE_SUFFIX web2py.gluon.sqlhtml.UploadWidget-class.html#ID_DELETE_SUFFIX +web2py.gluon.storage.List web2py.gluon.storage.List-class.html +web2py.gluon.storage.List.__call__ web2py.gluon.storage.List-class.html#__call__ +web2py.gluon.storage.Messages web2py.gluon.storage.Messages-class.html +web2py.gluon.storage.Storage.getlist web2py.gluon.storage.Storage-class.html#getlist +web2py.gluon.storage.Messages.__init__ web2py.gluon.storage.Messages-class.html#__init__ +web2py.gluon.storage.Messages.__setattr__ web2py.gluon.storage.Messages-class.html#__setattr__ +web2py.gluon.storage.Messages.__getattr__ web2py.gluon.storage.Messages-class.html#__getattr__ +web2py.gluon.storage.Storage.__getstate__ web2py.gluon.storage.Storage-class.html#__getstate__ +web2py.gluon.storage.Storage.__setstate__ web2py.gluon.storage.Storage-class.html#__setstate__ +web2py.gluon.storage.Storage.getlast web2py.gluon.storage.Storage-class.html#getlast +web2py.gluon.storage.Storage.__delattr__ web2py.gluon.storage.Storage-class.html#__delattr__ +web2py.gluon.storage.Storage.__repr__ web2py.gluon.storage.Storage-class.html#__repr__ +web2py.gluon.storage.Storage.getfirst web2py.gluon.storage.Storage-class.html#getfirst +web2py.gluon.storage.Settings web2py.gluon.storage.Settings-class.html +web2py.gluon.storage.Storage.getlist web2py.gluon.storage.Storage-class.html#getlist +web2py.gluon.storage.Settings.__setattr__ web2py.gluon.storage.Settings-class.html#__setattr__ +web2py.gluon.storage.Storage.__getattr__ web2py.gluon.storage.Storage-class.html#__getattr__ +web2py.gluon.storage.Storage.__getstate__ web2py.gluon.storage.Storage-class.html#__getstate__ +web2py.gluon.storage.Storage.__setstate__ web2py.gluon.storage.Storage-class.html#__setstate__ +web2py.gluon.storage.Storage.getlast web2py.gluon.storage.Storage-class.html#getlast +web2py.gluon.storage.Storage.__delattr__ web2py.gluon.storage.Storage-class.html#__delattr__ +web2py.gluon.storage.Storage.__repr__ web2py.gluon.storage.Storage-class.html#__repr__ +web2py.gluon.storage.Storage.getfirst web2py.gluon.storage.Storage-class.html#getfirst +web2py.gluon.storage.Storage web2py.gluon.storage.Storage-class.html +web2py.gluon.storage.Storage.getlist web2py.gluon.storage.Storage-class.html#getlist +web2py.gluon.storage.Storage.__setattr__ web2py.gluon.storage.Storage-class.html#__setattr__ +web2py.gluon.storage.Storage.__getattr__ web2py.gluon.storage.Storage-class.html#__getattr__ +web2py.gluon.storage.Storage.__getstate__ web2py.gluon.storage.Storage-class.html#__getstate__ +web2py.gluon.storage.Storage.__setstate__ web2py.gluon.storage.Storage-class.html#__setstate__ +web2py.gluon.storage.Storage.getlast web2py.gluon.storage.Storage-class.html#getlast +web2py.gluon.storage.Storage.__delattr__ web2py.gluon.storage.Storage-class.html#__delattr__ +web2py.gluon.storage.Storage.__repr__ web2py.gluon.storage.Storage-class.html#__repr__ +web2py.gluon.storage.Storage.getfirst web2py.gluon.storage.Storage-class.html#getfirst +web2py.gluon.storage.StorageList web2py.gluon.storage.StorageList-class.html +web2py.gluon.storage.Storage.getlist web2py.gluon.storage.Storage-class.html#getlist +web2py.gluon.storage.Storage.__setattr__ web2py.gluon.storage.Storage-class.html#__setattr__ +web2py.gluon.storage.StorageList.__getattr__ web2py.gluon.storage.StorageList-class.html#__getattr__ +web2py.gluon.storage.Storage.__getstate__ web2py.gluon.storage.Storage-class.html#__getstate__ +web2py.gluon.storage.Storage.__setstate__ web2py.gluon.storage.Storage-class.html#__setstate__ +web2py.gluon.storage.Storage.getlast web2py.gluon.storage.Storage-class.html#getlast +web2py.gluon.storage.Storage.__delattr__ web2py.gluon.storage.Storage-class.html#__delattr__ +web2py.gluon.storage.Storage.__repr__ web2py.gluon.storage.Storage-class.html#__repr__ +web2py.gluon.storage.Storage.getfirst web2py.gluon.storage.Storage-class.html#getfirst +web2py.gluon.template.BlockNode web2py.gluon.template.BlockNode-class.html +web2py.gluon.template.BlockNode.extend web2py.gluon.template.BlockNode-class.html#extend +web2py.gluon.template.BlockNode.__str__ web2py.gluon.template.BlockNode-class.html#__str__ +web2py.gluon.template.BlockNode.append web2py.gluon.template.BlockNode-class.html#append +web2py.gluon.template.BlockNode.output web2py.gluon.template.BlockNode-class.html#output +web2py.gluon.template.BlockNode.__init__ web2py.gluon.template.BlockNode-class.html#__init__ +web2py.gluon.template.BlockNode.__repr__ web2py.gluon.template.BlockNode-class.html#__repr__ +web2py.gluon.template.Content web2py.gluon.template.Content-class.html +web2py.gluon.template.Content.insert web2py.gluon.template.Content-class.html#insert +web2py.gluon.template.Content.extend web2py.gluon.template.Content-class.html#extend +web2py.gluon.template.Content.__str__ web2py.gluon.template.Content-class.html#__str__ +web2py.gluon.template.Content.clear_content web2py.gluon.template.Content-class.html#clear_content +web2py.gluon.template.Content.append web2py.gluon.template.Content-class.html#append +web2py.gluon.template.Content._insert web2py.gluon.template.Content-class.html#_insert +web2py.gluon.template.BlockNode.output web2py.gluon.template.BlockNode-class.html#output +web2py.gluon.template.Content.__init__ web2py.gluon.template.Content-class.html#__init__ +web2py.gluon.template.BlockNode.__repr__ web2py.gluon.template.BlockNode-class.html#__repr__ +web2py.gluon.template.Node web2py.gluon.template.Node-class.html +web2py.gluon.template.Node.__str__ web2py.gluon.template.Node-class.html#__str__ +web2py.gluon.template.Node.__init__ web2py.gluon.template.Node-class.html#__init__ +web2py.gluon.template.SuperNode web2py.gluon.template.SuperNode-class.html +web2py.gluon.template.SuperNode.__str__ web2py.gluon.template.SuperNode-class.html#__str__ +web2py.gluon.template.SuperNode.__repr__ web2py.gluon.template.SuperNode-class.html#__repr__ +web2py.gluon.template.SuperNode.__init__ web2py.gluon.template.SuperNode-class.html#__init__ +web2py.gluon.template.TemplateParser web2py.gluon.template.TemplateParser-class.html +web2py.gluon.template.TemplateParser.reindent web2py.gluon.template.TemplateParser-class.html#reindent +web2py.gluon.template.TemplateParser.__str__ web2py.gluon.template.TemplateParser-class.html#__str__ +web2py.gluon.template.TemplateParser.parse web2py.gluon.template.TemplateParser-class.html#parse +web2py.gluon.template.TemplateParser.__init__ web2py.gluon.template.TemplateParser-class.html#__init__ +web2py.gluon.template.TemplateParser.extend web2py.gluon.template.TemplateParser-class.html#extend +web2py.gluon.template.TemplateParser.r_multiline web2py.gluon.template.TemplateParser-class.html#r_multiline +web2py.gluon.template.TemplateParser.re_unblock web2py.gluon.template.TemplateParser-class.html#re_unblock +web2py.gluon.template.TemplateParser.include web2py.gluon.template.TemplateParser-class.html#include +web2py.gluon.template.TemplateParser.re_pass web2py.gluon.template.TemplateParser-class.html#re_pass +web2py.gluon.template.TemplateParser.__unicode__ web2py.gluon.template.TemplateParser-class.html#__unicode__ +web2py.gluon.template.TemplateParser.r_tag web2py.gluon.template.TemplateParser-class.html#r_tag +web2py.gluon.template.TemplateParser.to_string web2py.gluon.template.TemplateParser-class.html#to_string +web2py.gluon.template.TemplateParser._raise_error web2py.gluon.template.TemplateParser-class.html#_raise_error +web2py.gluon.template.TemplateParser.re_block web2py.gluon.template.TemplateParser-class.html#re_block +web2py.gluon.template.TemplateParser._get_file_text web2py.gluon.template.TemplateParser-class.html#_get_file_text +web2py.gluon.tools.Auth web2py.gluon.tools.Auth-class.html +web2py.gluon.tools.Auth.profile web2py.gluon.tools.Auth-class.html#profile +web2py.gluon.tools.Auth.retrieve_username web2py.gluon.tools.Auth-class.html#retrieve_username +web2py.gluon.tools.Auth.add_permission web2py.gluon.tools.Auth-class.html#add_permission +web2py.gluon.tools.Auth.requires_membership web2py.gluon.tools.Auth-class.html#requires_membership +web2py.gluon.tools.Auth.add_group web2py.gluon.tools.Auth-class.html#add_group +web2py.gluon.tools.Auth.requires_permission web2py.gluon.tools.Auth-class.html#requires_permission +web2py.gluon.tools.Auth.random_password web2py.gluon.tools.Auth-class.html#random_password +web2py.gluon.tools.Auth.verify_email web2py.gluon.tools.Auth-class.html#verify_email +web2py.gluon.tools.Auth.id_group web2py.gluon.tools.Auth-class.html#id_group +web2py.gluon.tools.Auth.cas_login web2py.gluon.tools.Auth-class.html#cas_login +web2py.gluon.tools.Auth.define_tables web2py.gluon.tools.Auth-class.html#define_tables +web2py.gluon.tools.Auth.cas_validate web2py.gluon.tools.Auth-class.html#cas_validate +web2py.gluon.tools.Auth.del_group web2py.gluon.tools.Auth-class.html#del_group +web2py.gluon.tools.Auth.user_id web2py.gluon.tools.Auth-class.html#user_id +web2py.gluon.tools.Auth.del_permission web2py.gluon.tools.Auth-class.html#del_permission +web2py.gluon.tools.Auth._HTTP web2py.gluon.tools.Auth-class.html#_HTTP +web2py.gluon.tools.Auth.accessible_query web2py.gluon.tools.Auth-class.html#accessible_query +web2py.gluon.tools.Auth.login_bare web2py.gluon.tools.Auth-class.html#login_bare +web2py.gluon.tools.Auth.is_logged_in web2py.gluon.tools.Auth-class.html#is_logged_in +web2py.gluon.tools.Auth.basic web2py.gluon.tools.Auth-class.html#basic +web2py.gluon.tools.Auth.__call__ web2py.gluon.tools.Auth-class.html#__call__ +web2py.gluon.tools.Auth.requires_signature web2py.gluon.tools.Auth-class.html#requires_signature +web2py.gluon.tools.Auth.not_authorized web2py.gluon.tools.Auth-class.html#not_authorized +web2py.gluon.tools.Auth.reset_password_deprecated web2py.gluon.tools.Auth-class.html#reset_password_deprecated +web2py.gluon.tools.Auth.__get_migrate web2py.gluon.tools.Auth-class.html#__get_migrate +web2py.gluon.tools.Auth.is_impersonating web2py.gluon.tools.Auth-class.html#is_impersonating +web2py.gluon.tools.Auth.has_membership web2py.gluon.tools.Auth-class.html#has_membership +web2py.gluon.tools.Auth.log_event web2py.gluon.tools.Auth-class.html#log_event +web2py.gluon.tools.Auth._get_user_id web2py.gluon.tools.Auth-class.html#_get_user_id +web2py.gluon.tools.Auth.has_permission web2py.gluon.tools.Auth-class.html#has_permission +web2py.gluon.tools.Auth.logout web2py.gluon.tools.Auth-class.html#logout +web2py.gluon.tools.Auth.groups web2py.gluon.tools.Auth-class.html#groups +web2py.gluon.tools.Auth.user_group web2py.gluon.tools.Auth-class.html#user_group +web2py.gluon.tools.Auth.add_membership web2py.gluon.tools.Auth-class.html#add_membership +web2py.gluon.tools.Auth.retrieve_password web2py.gluon.tools.Auth-class.html#retrieve_password +web2py.gluon.tools.Auth.url web2py.gluon.tools.Auth-class.html#url +web2py.gluon.tools.Auth.get_or_create_user web2py.gluon.tools.Auth-class.html#get_or_create_user +web2py.gluon.tools.Auth.register web2py.gluon.tools.Auth-class.html#register +web2py.gluon.tools.Auth.reset_password web2py.gluon.tools.Auth-class.html#reset_password +web2py.gluon.tools.Auth.navbar web2py.gluon.tools.Auth-class.html#navbar +web2py.gluon.tools.Auth.requires_login web2py.gluon.tools.Auth-class.html#requires_login +web2py.gluon.tools.Auth.__init__ web2py.gluon.tools.Auth-class.html#__init__ +web2py.gluon.tools.Auth.change_password web2py.gluon.tools.Auth-class.html#change_password +web2py.gluon.tools.Auth.del_membership web2py.gluon.tools.Auth-class.html#del_membership +web2py.gluon.tools.Auth.impersonate web2py.gluon.tools.Auth-class.html#impersonate +web2py.gluon.tools.Auth.login web2py.gluon.tools.Auth-class.html#login +web2py.gluon.tools.Auth.request_reset_password web2py.gluon.tools.Auth-class.html#request_reset_password +web2py.gluon.tools.Auth.requires web2py.gluon.tools.Auth-class.html#requires +web2py.gluon.tools.Crud web2py.gluon.tools.Crud-class.html +web2py.gluon.tools.Crud.get_format web2py.gluon.tools.Crud-class.html#get_format +web2py.gluon.tools.Crud.archive web2py.gluon.tools.Crud-class.html#archive +web2py.gluon.tools.Crud.__init__ web2py.gluon.tools.Crud-class.html#__init__ +web2py.gluon.tools.Crud.tables web2py.gluon.tools.Crud-class.html#tables +web2py.gluon.tools.Crud.rows web2py.gluon.tools.Crud-class.html#rows +web2py.gluon.tools.Crud.create web2py.gluon.tools.Crud-class.html#create +web2py.gluon.tools.Crud.select web2py.gluon.tools.Crud-class.html#select +web2py.gluon.tools.Crud.__call__ web2py.gluon.tools.Crud-class.html#__call__ +web2py.gluon.tools.Crud.get_query web2py.gluon.tools.Crud-class.html#get_query +web2py.gluon.tools.Crud.read web2py.gluon.tools.Crud-class.html#read +web2py.gluon.tools.Crud.update web2py.gluon.tools.Crud-class.html#update +web2py.gluon.tools.Crud.log_event web2py.gluon.tools.Crud-class.html#log_event +web2py.gluon.tools.Crud.search web2py.gluon.tools.Crud-class.html#search +web2py.gluon.tools.Crud.url web2py.gluon.tools.Crud-class.html#url +web2py.gluon.tools.Crud.has_permission web2py.gluon.tools.Crud-class.html#has_permission +web2py.gluon.tools.Crud.delete web2py.gluon.tools.Crud-class.html#delete +web2py.gluon.tools.Mail web2py.gluon.tools.Mail-class.html +web2py.gluon.tools.Mail.send web2py.gluon.tools.Mail-class.html#send +web2py.gluon.tools.Mail.Attachment web2py.gluon.tools.Mail.Attachment-class.html +web2py.gluon.tools.Mail.__init__ web2py.gluon.tools.Mail-class.html#__init__ +web2py.gluon.tools.Mail.Attachment web2py.gluon.tools.Mail.Attachment-class.html +web2py.gluon.tools.Mail.Attachment.__init__ web2py.gluon.tools.Mail.Attachment-class.html#__init__ +web2py.gluon.tools.PluginManager web2py.gluon.tools.PluginManager-class.html +web2py.gluon.tools.PluginManager.__new__ web2py.gluon.tools.PluginManager-class.html#__new__ +web2py.gluon.tools.PluginManager.__contains__ web2py.gluon.tools.PluginManager-class.html#__contains__ +web2py.gluon.tools.PluginManager.keys web2py.gluon.tools.PluginManager-class.html#keys +web2py.gluon.tools.PluginManager.__getattr__ web2py.gluon.tools.PluginManager-class.html#__getattr__ +web2py.gluon.tools.PluginManager.instances web2py.gluon.tools.PluginManager-class.html#instances +web2py.gluon.tools.PluginManager.__init__ web2py.gluon.tools.PluginManager-class.html#__init__ +web2py.gluon.tools.Recaptcha web2py.gluon.tools.Recaptcha-class.html +web2py.gluon.tools.Recaptcha.VERIFY_SERVER web2py.gluon.tools.Recaptcha-class.html#VERIFY_SERVER +web2py.gluon.tools.Recaptcha._validate web2py.gluon.tools.Recaptcha-class.html#_validate +web2py.gluon.tools.Recaptcha.xml web2py.gluon.tools.Recaptcha-class.html#xml +web2py.gluon.tools.Recaptcha.__init__ web2py.gluon.tools.Recaptcha-class.html#__init__ +web2py.gluon.tools.Recaptcha.API_SERVER web2py.gluon.tools.Recaptcha-class.html#API_SERVER +web2py.gluon.tools.Recaptcha.API_SSL_SERVER web2py.gluon.tools.Recaptcha-class.html#API_SSL_SERVER +web2py.gluon.tools.Service web2py.gluon.tools.Service-class.html +web2py.gluon.tools.Service.__init__ web2py.gluon.tools.Service-class.html#__init__ +web2py.gluon.tools.Service.xml web2py.gluon.tools.Service-class.html#xml +web2py.gluon.tools.Service.amfrpc3 web2py.gluon.tools.Service-class.html#amfrpc3 +web2py.gluon.tools.Service.json web2py.gluon.tools.Service-class.html#json +web2py.gluon.tools.Service.serve_xmlrpc web2py.gluon.tools.Service-class.html#serve_xmlrpc +web2py.gluon.tools.Service.csv web2py.gluon.tools.Service-class.html#csv +web2py.gluon.tools.Service.soap web2py.gluon.tools.Service-class.html#soap +web2py.gluon.tools.Service.JsonRpcException web2py.gluon.tools.Service.JsonRpcException-class.html +web2py.gluon.tools.Service.serve_soap web2py.gluon.tools.Service-class.html#serve_soap +web2py.gluon.tools.Service.run web2py.gluon.tools.Service-class.html#run +web2py.gluon.tools.Service.serve_csv web2py.gluon.tools.Service-class.html#serve_csv +web2py.gluon.tools.Service.xmlrpc web2py.gluon.tools.Service-class.html#xmlrpc +web2py.gluon.tools.Service.serve_run web2py.gluon.tools.Service-class.html#serve_run +web2py.gluon.tools.Service.__call__ web2py.gluon.tools.Service-class.html#__call__ +web2py.gluon.tools.Service.serve_jsonrpc web2py.gluon.tools.Service-class.html#serve_jsonrpc +web2py.gluon.tools.Service.rss web2py.gluon.tools.Service-class.html#rss +web2py.gluon.tools.Service.serve_json web2py.gluon.tools.Service-class.html#serve_json +web2py.gluon.tools.Service.serve_rss web2py.gluon.tools.Service-class.html#serve_rss +web2py.gluon.tools.Service.jsonrpc web2py.gluon.tools.Service-class.html#jsonrpc +web2py.gluon.tools.Service.amfrpc web2py.gluon.tools.Service-class.html#amfrpc +web2py.gluon.tools.Service.serve_amfrpc web2py.gluon.tools.Service-class.html#serve_amfrpc +web2py.gluon.tools.Service.serve_xml web2py.gluon.tools.Service-class.html#serve_xml +web2py.gluon.tools.Service.error web2py.gluon.tools.Service-class.html#error +web2py.gluon.tools.Service.JsonRpcException web2py.gluon.tools.Service.JsonRpcException-class.html +web2py.gluon.tools.Service.JsonRpcException.__init__ web2py.gluon.tools.Service.JsonRpcException-class.html#__init__ +exceptions.Exception.__new__ exceptions.Exception-class.html#__new__ +web2py.gluon.validators.CLEANUP web2py.gluon.validators.CLEANUP-class.html +web2py.gluon.validators.CLEANUP.__call__ web2py.gluon.validators.CLEANUP-class.html#__call__ +web2py.gluon.validators.Validator.formatter web2py.gluon.validators.Validator-class.html#formatter +web2py.gluon.validators.CLEANUP.__init__ web2py.gluon.validators.CLEANUP-class.html#__init__ +web2py.gluon.validators.CRYPT web2py.gluon.validators.CRYPT-class.html +web2py.gluon.validators.CRYPT.__call__ web2py.gluon.validators.CRYPT-class.html#__call__ +web2py.gluon.validators.CRYPT.__init__ web2py.gluon.validators.CRYPT-class.html#__init__ +web2py.gluon.validators.IS_ALPHANUMERIC web2py.gluon.validators.IS_ALPHANUMERIC-class.html +web2py.gluon.validators.IS_MATCH.__call__ web2py.gluon.validators.IS_MATCH-class.html#__call__ +web2py.gluon.validators.Validator.formatter web2py.gluon.validators.Validator-class.html#formatter +web2py.gluon.validators.IS_ALPHANUMERIC.__init__ web2py.gluon.validators.IS_ALPHANUMERIC-class.html#__init__ +web2py.gluon.validators.IS_DATE web2py.gluon.validators.IS_DATE-class.html +web2py.gluon.validators.IS_DATE.__call__ web2py.gluon.validators.IS_DATE-class.html#__call__ +web2py.gluon.validators.IS_DATE.formatter web2py.gluon.validators.IS_DATE-class.html#formatter +web2py.gluon.validators.IS_DATE.__init__ web2py.gluon.validators.IS_DATE-class.html#__init__ +web2py.gluon.validators.IS_DATETIME web2py.gluon.validators.IS_DATETIME-class.html +web2py.gluon.validators.IS_DATETIME.formatter web2py.gluon.validators.IS_DATETIME-class.html#formatter +web2py.gluon.validators.IS_DATETIME.__call__ web2py.gluon.validators.IS_DATETIME-class.html#__call__ +web2py.gluon.validators.IS_DATETIME.isodatetime web2py.gluon.validators.IS_DATETIME-class.html#isodatetime +web2py.gluon.validators.IS_DATETIME.__init__ web2py.gluon.validators.IS_DATETIME-class.html#__init__ +web2py.gluon.validators.IS_DATETIME.nice web2py.gluon.validators.IS_DATETIME-class.html#nice +web2py.gluon.validators.IS_DATETIME_IN_RANGE web2py.gluon.validators.IS_DATETIME_IN_RANGE-class.html +web2py.gluon.validators.IS_DATETIME.isodatetime web2py.gluon.validators.IS_DATETIME-class.html#isodatetime +web2py.gluon.validators.IS_DATETIME_IN_RANGE.__call__ web2py.gluon.validators.IS_DATETIME_IN_RANGE-class.html#__call__ +web2py.gluon.validators.IS_DATETIME.formatter web2py.gluon.validators.IS_DATETIME-class.html#formatter +web2py.gluon.validators.IS_DATETIME_IN_RANGE.__init__ web2py.gluon.validators.IS_DATETIME_IN_RANGE-class.html#__init__ +web2py.gluon.validators.IS_DATETIME.nice web2py.gluon.validators.IS_DATETIME-class.html#nice +web2py.gluon.validators.IS_DATE_IN_RANGE web2py.gluon.validators.IS_DATE_IN_RANGE-class.html +web2py.gluon.validators.IS_DATE_IN_RANGE.__call__ web2py.gluon.validators.IS_DATE_IN_RANGE-class.html#__call__ +web2py.gluon.validators.IS_DATE.formatter web2py.gluon.validators.IS_DATE-class.html#formatter +web2py.gluon.validators.IS_DATE_IN_RANGE.__init__ web2py.gluon.validators.IS_DATE_IN_RANGE-class.html#__init__ +web2py.gluon.validators.IS_DECIMAL_IN_RANGE web2py.gluon.validators.IS_DECIMAL_IN_RANGE-class.html +web2py.gluon.validators.IS_DECIMAL_IN_RANGE.__call__ web2py.gluon.validators.IS_DECIMAL_IN_RANGE-class.html#__call__ +web2py.gluon.validators.IS_DECIMAL_IN_RANGE.formatter web2py.gluon.validators.IS_DECIMAL_IN_RANGE-class.html#formatter +web2py.gluon.validators.IS_DECIMAL_IN_RANGE.__init__ web2py.gluon.validators.IS_DECIMAL_IN_RANGE-class.html#__init__ +web2py.gluon.validators.IS_EMAIL web2py.gluon.validators.IS_EMAIL-class.html +web2py.gluon.validators.IS_EMAIL.regex web2py.gluon.validators.IS_EMAIL-class.html#regex +web2py.gluon.validators.IS_EMAIL.regex_proposed_but_failed web2py.gluon.validators.IS_EMAIL-class.html#regex_proposed_but_failed +web2py.gluon.validators.IS_EMAIL.__call__ web2py.gluon.validators.IS_EMAIL-class.html#__call__ +web2py.gluon.validators.Validator.formatter web2py.gluon.validators.Validator-class.html#formatter +web2py.gluon.validators.IS_EMAIL.__init__ web2py.gluon.validators.IS_EMAIL-class.html#__init__ +web2py.gluon.validators.IS_EMPTY_OR web2py.gluon.validators.IS_EMPTY_OR-class.html +web2py.gluon.validators.IS_EMPTY_OR.set_self_id web2py.gluon.validators.IS_EMPTY_OR-class.html#set_self_id +web2py.gluon.validators.IS_EMPTY_OR._options web2py.gluon.validators.IS_EMPTY_OR-class.html#_options +web2py.gluon.validators.IS_EMPTY_OR.__call__ web2py.gluon.validators.IS_EMPTY_OR-class.html#__call__ +web2py.gluon.validators.IS_EMPTY_OR.formatter web2py.gluon.validators.IS_EMPTY_OR-class.html#formatter +web2py.gluon.validators.IS_EMPTY_OR.__init__ web2py.gluon.validators.IS_EMPTY_OR-class.html#__init__ +web2py.gluon.validators.IS_EQUAL_TO web2py.gluon.validators.IS_EQUAL_TO-class.html +web2py.gluon.validators.IS_EQUAL_TO.__call__ web2py.gluon.validators.IS_EQUAL_TO-class.html#__call__ +web2py.gluon.validators.Validator.formatter web2py.gluon.validators.Validator-class.html#formatter +web2py.gluon.validators.IS_EQUAL_TO.__init__ web2py.gluon.validators.IS_EQUAL_TO-class.html#__init__ +web2py.gluon.validators.IS_EXPR web2py.gluon.validators.IS_EXPR-class.html +web2py.gluon.validators.IS_EXPR.__call__ web2py.gluon.validators.IS_EXPR-class.html#__call__ +web2py.gluon.validators.Validator.formatter web2py.gluon.validators.Validator-class.html#formatter +web2py.gluon.validators.IS_EXPR.__init__ web2py.gluon.validators.IS_EXPR-class.html#__init__ +web2py.gluon.validators.IS_FLOAT_IN_RANGE web2py.gluon.validators.IS_FLOAT_IN_RANGE-class.html +web2py.gluon.validators.IS_FLOAT_IN_RANGE.__call__ web2py.gluon.validators.IS_FLOAT_IN_RANGE-class.html#__call__ +web2py.gluon.validators.IS_FLOAT_IN_RANGE.formatter web2py.gluon.validators.IS_FLOAT_IN_RANGE-class.html#formatter +web2py.gluon.validators.IS_FLOAT_IN_RANGE.__init__ web2py.gluon.validators.IS_FLOAT_IN_RANGE-class.html#__init__ +web2py.gluon.validators.IS_GENERIC_URL web2py.gluon.validators.IS_GENERIC_URL-class.html +web2py.gluon.validators.IS_GENERIC_URL.__call__ web2py.gluon.validators.IS_GENERIC_URL-class.html#__call__ +web2py.gluon.validators.Validator.formatter web2py.gluon.validators.Validator-class.html#formatter +web2py.gluon.validators.IS_GENERIC_URL.__init__ web2py.gluon.validators.IS_GENERIC_URL-class.html#__init__ +web2py.gluon.validators.IS_HTTP_URL web2py.gluon.validators.IS_HTTP_URL-class.html +web2py.gluon.validators.IS_HTTP_URL.__call__ web2py.gluon.validators.IS_HTTP_URL-class.html#__call__ +web2py.gluon.validators.Validator.formatter web2py.gluon.validators.Validator-class.html#formatter +web2py.gluon.validators.IS_HTTP_URL.__init__ web2py.gluon.validators.IS_HTTP_URL-class.html#__init__ +web2py.gluon.validators.IS_IMAGE web2py.gluon.validators.IS_IMAGE-class.html +web2py.gluon.validators.IS_IMAGE.__png web2py.gluon.validators.IS_IMAGE-class.html#__png +web2py.gluon.validators.IS_IMAGE.__jpeg web2py.gluon.validators.IS_IMAGE-class.html#__jpeg +web2py.gluon.validators.IS_IMAGE.__bmp web2py.gluon.validators.IS_IMAGE-class.html#__bmp +web2py.gluon.validators.IS_IMAGE.__call__ web2py.gluon.validators.IS_IMAGE-class.html#__call__ +web2py.gluon.validators.Validator.formatter web2py.gluon.validators.Validator-class.html#formatter +web2py.gluon.validators.IS_IMAGE.__gif web2py.gluon.validators.IS_IMAGE-class.html#__gif +web2py.gluon.validators.IS_IMAGE.__init__ web2py.gluon.validators.IS_IMAGE-class.html#__init__ +web2py.gluon.validators.IS_INT_IN_RANGE web2py.gluon.validators.IS_INT_IN_RANGE-class.html +web2py.gluon.validators.IS_INT_IN_RANGE.__call__ web2py.gluon.validators.IS_INT_IN_RANGE-class.html#__call__ +web2py.gluon.validators.Validator.formatter web2py.gluon.validators.Validator-class.html#formatter +web2py.gluon.validators.IS_INT_IN_RANGE.__init__ web2py.gluon.validators.IS_INT_IN_RANGE-class.html#__init__ +web2py.gluon.validators.IS_IN_DB web2py.gluon.validators.IS_IN_DB-class.html +web2py.gluon.validators.IS_IN_DB.build_set web2py.gluon.validators.IS_IN_DB-class.html#build_set +web2py.gluon.validators.IS_IN_DB.set_self_id web2py.gluon.validators.IS_IN_DB-class.html#set_self_id +web2py.gluon.validators.IS_IN_DB.__call__ web2py.gluon.validators.IS_IN_DB-class.html#__call__ +web2py.gluon.validators.Validator.formatter web2py.gluon.validators.Validator-class.html#formatter +web2py.gluon.validators.IS_IN_DB.options web2py.gluon.validators.IS_IN_DB-class.html#options +web2py.gluon.validators.IS_IN_DB.__init__ web2py.gluon.validators.IS_IN_DB-class.html#__init__ +web2py.gluon.validators.IS_IN_SET web2py.gluon.validators.IS_IN_SET-class.html +web2py.gluon.validators.IS_IN_SET.__call__ web2py.gluon.validators.IS_IN_SET-class.html#__call__ +web2py.gluon.validators.Validator.formatter web2py.gluon.validators.Validator-class.html#formatter +web2py.gluon.validators.IS_IN_SET.options web2py.gluon.validators.IS_IN_SET-class.html#options +web2py.gluon.validators.IS_IN_SET.__init__ web2py.gluon.validators.IS_IN_SET-class.html#__init__ +web2py.gluon.validators.IS_IN_SUBSET web2py.gluon.validators.IS_IN_SUBSET-class.html +web2py.gluon.validators.IS_IN_SUBSET.__call__ web2py.gluon.validators.IS_IN_SUBSET-class.html#__call__ +web2py.gluon.validators.Validator.formatter web2py.gluon.validators.Validator-class.html#formatter +web2py.gluon.validators.IS_IN_SET.options web2py.gluon.validators.IS_IN_SET-class.html#options +web2py.gluon.validators.IS_IN_SUBSET.__init__ web2py.gluon.validators.IS_IN_SUBSET-class.html#__init__ +web2py.gluon.validators.IS_IPV4 web2py.gluon.validators.IS_IPV4-class.html +web2py.gluon.validators.IS_IPV4.regex web2py.gluon.validators.IS_IPV4-class.html#regex +web2py.gluon.validators.IS_IPV4.private web2py.gluon.validators.IS_IPV4-class.html#private +web2py.gluon.validators.IS_IPV4.numbers web2py.gluon.validators.IS_IPV4-class.html#numbers +web2py.gluon.validators.IS_IPV4.__call__ web2py.gluon.validators.IS_IPV4-class.html#__call__ +web2py.gluon.validators.Validator.formatter web2py.gluon.validators.Validator-class.html#formatter +web2py.gluon.validators.IS_IPV4.automatic web2py.gluon.validators.IS_IPV4-class.html#automatic +web2py.gluon.validators.IS_IPV4.__init__ web2py.gluon.validators.IS_IPV4-class.html#__init__ +web2py.gluon.validators.IS_IPV4.localhost web2py.gluon.validators.IS_IPV4-class.html#localhost +web2py.gluon.validators.IS_LENGTH web2py.gluon.validators.IS_LENGTH-class.html +web2py.gluon.validators.IS_LENGTH.__call__ web2py.gluon.validators.IS_LENGTH-class.html#__call__ +web2py.gluon.validators.Validator.formatter web2py.gluon.validators.Validator-class.html#formatter +web2py.gluon.validators.IS_LENGTH.__init__ web2py.gluon.validators.IS_LENGTH-class.html#__init__ +web2py.gluon.validators.IS_LIST_OF web2py.gluon.validators.IS_LIST_OF-class.html +web2py.gluon.validators.IS_LIST_OF.__call__ web2py.gluon.validators.IS_LIST_OF-class.html#__call__ +web2py.gluon.validators.Validator.formatter web2py.gluon.validators.Validator-class.html#formatter +web2py.gluon.validators.IS_LIST_OF.__init__ web2py.gluon.validators.IS_LIST_OF-class.html#__init__ +web2py.gluon.validators.IS_LOWER web2py.gluon.validators.IS_LOWER-class.html +web2py.gluon.validators.IS_LOWER.__call__ web2py.gluon.validators.IS_LOWER-class.html#__call__ +web2py.gluon.validators.Validator.formatter web2py.gluon.validators.Validator-class.html#formatter +web2py.gluon.validators.IS_MATCH web2py.gluon.validators.IS_MATCH-class.html +web2py.gluon.validators.IS_MATCH.__call__ web2py.gluon.validators.IS_MATCH-class.html#__call__ +web2py.gluon.validators.Validator.formatter web2py.gluon.validators.Validator-class.html#formatter +web2py.gluon.validators.IS_MATCH.__init__ web2py.gluon.validators.IS_MATCH-class.html#__init__ +web2py.gluon.validators.IS_NOT_EMPTY web2py.gluon.validators.IS_NOT_EMPTY-class.html +web2py.gluon.validators.IS_NOT_EMPTY.__call__ web2py.gluon.validators.IS_NOT_EMPTY-class.html#__call__ +web2py.gluon.validators.Validator.formatter web2py.gluon.validators.Validator-class.html#formatter +web2py.gluon.validators.IS_NOT_EMPTY.__init__ web2py.gluon.validators.IS_NOT_EMPTY-class.html#__init__ +web2py.gluon.validators.IS_NOT_IN_DB web2py.gluon.validators.IS_NOT_IN_DB-class.html +web2py.gluon.validators.IS_NOT_IN_DB.set_self_id web2py.gluon.validators.IS_NOT_IN_DB-class.html#set_self_id +web2py.gluon.validators.IS_NOT_IN_DB.__call__ web2py.gluon.validators.IS_NOT_IN_DB-class.html#__call__ +web2py.gluon.validators.Validator.formatter web2py.gluon.validators.Validator-class.html#formatter +web2py.gluon.validators.IS_NOT_IN_DB.__init__ web2py.gluon.validators.IS_NOT_IN_DB-class.html#__init__ +web2py.gluon.validators.IS_SLUG web2py.gluon.validators.IS_SLUG-class.html +web2py.gluon.validators.IS_SLUG.urlify web2py.gluon.validators.IS_SLUG-class.html#urlify +web2py.gluon.validators.IS_SLUG.__call__ web2py.gluon.validators.IS_SLUG-class.html#__call__ +web2py.gluon.validators.Validator.formatter web2py.gluon.validators.Validator-class.html#formatter +web2py.gluon.validators.IS_SLUG.__init__ web2py.gluon.validators.IS_SLUG-class.html#__init__ +web2py.gluon.validators.IS_STRONG web2py.gluon.validators.IS_STRONG-class.html +web2py.gluon.validators.IS_STRONG.__call__ web2py.gluon.validators.IS_STRONG-class.html#__call__ +web2py.gluon.validators.IS_STRONG.__init__ web2py.gluon.validators.IS_STRONG-class.html#__init__ +web2py.gluon.validators.IS_TIME web2py.gluon.validators.IS_TIME-class.html +web2py.gluon.validators.IS_TIME.__call__ web2py.gluon.validators.IS_TIME-class.html#__call__ +web2py.gluon.validators.Validator.formatter web2py.gluon.validators.Validator-class.html#formatter +web2py.gluon.validators.IS_TIME.__init__ web2py.gluon.validators.IS_TIME-class.html#__init__ +web2py.gluon.validators.IS_UPLOAD_FILENAME web2py.gluon.validators.IS_UPLOAD_FILENAME-class.html +web2py.gluon.validators.IS_UPLOAD_FILENAME.__call__ web2py.gluon.validators.IS_UPLOAD_FILENAME-class.html#__call__ +web2py.gluon.validators.Validator.formatter web2py.gluon.validators.Validator-class.html#formatter +web2py.gluon.validators.IS_UPLOAD_FILENAME.__init__ web2py.gluon.validators.IS_UPLOAD_FILENAME-class.html#__init__ +web2py.gluon.validators.IS_UPPER web2py.gluon.validators.IS_UPPER-class.html +web2py.gluon.validators.IS_UPPER.__call__ web2py.gluon.validators.IS_UPPER-class.html#__call__ +web2py.gluon.validators.Validator.formatter web2py.gluon.validators.Validator-class.html#formatter +web2py.gluon.validators.IS_URL web2py.gluon.validators.IS_URL-class.html +web2py.gluon.validators.IS_URL.__call__ web2py.gluon.validators.IS_URL-class.html#__call__ +web2py.gluon.validators.Validator.formatter web2py.gluon.validators.Validator-class.html#formatter +web2py.gluon.validators.IS_URL.__init__ web2py.gluon.validators.IS_URL-class.html#__init__ +web2py.gluon.validators.Validator web2py.gluon.validators.Validator-class.html +web2py.gluon.validators.Validator.formatter web2py.gluon.validators.Validator-class.html#formatter +web2py.gluon.widget.IO web2py.gluon.widget.IO-class.html +web2py.gluon.widget.IO.write web2py.gluon.widget.IO-class.html#write +web2py.gluon.widget.IO.__init__ web2py.gluon.widget.IO-class.html#__init__ +web2py.gluon.widget.web2pyDialog web2py.gluon.widget.web2pyDialog-class.html +web2py.gluon.widget.web2pyDialog.quit web2py.gluon.widget.web2pyDialog-class.html#quit +web2py.gluon.widget.web2pyDialog.update_canvas web2py.gluon.widget.web2pyDialog-class.html#update_canvas +web2py.gluon.widget.web2pyDialog.stop web2py.gluon.widget.web2pyDialog-class.html#stop +web2py.gluon.widget.web2pyDialog.update web2py.gluon.widget.web2pyDialog-class.html#update +web2py.gluon.widget.web2pyDialog.connect_pages web2py.gluon.widget.web2pyDialog-class.html#connect_pages +web2py.gluon.widget.web2pyDialog.start web2py.gluon.widget.web2pyDialog-class.html#start +web2py.gluon.widget.web2pyDialog.error web2py.gluon.widget.web2pyDialog-class.html#error +web2py.gluon.widget.web2pyDialog.checkTaskBar web2py.gluon.widget.web2pyDialog-class.html#checkTaskBar +web2py.gluon.widget.web2pyDialog.__init__ web2py.gluon.widget.web2pyDialog-class.html#__init__ +web2py.gluon.winservice.Service web2py.gluon.winservice.Service-class.html +web2py.gluon.winservice.Service.SvcDoRun web2py.gluon.winservice.Service-class.html#SvcDoRun +web2py.gluon.winservice.Service.SvcStop web2py.gluon.winservice.Service-class.html#SvcStop +web2py.gluon.winservice.Service.log web2py.gluon.winservice.Service-class.html#log +web2py.gluon.winservice.Service.stop web2py.gluon.winservice.Service-class.html#stop +web2py.gluon.winservice.Service._svc_display_name_ web2py.gluon.winservice.Service-class.html#_svc_display_name_ +web2py.gluon.winservice.Service.start web2py.gluon.winservice.Service-class.html#start +web2py.gluon.winservice.Service._svc_name_ web2py.gluon.winservice.Service-class.html#_svc_name_ +web2py.gluon.winservice.Service.__init__ web2py.gluon.winservice.Service-class.html#__init__ +web2py.gluon.winservice.Web2pyService web2py.gluon.winservice.Web2pyService-class.html +web2py.gluon.winservice.Service.SvcDoRun web2py.gluon.winservice.Service-class.html#SvcDoRun +web2py.gluon.winservice.Service.SvcStop web2py.gluon.winservice.Service-class.html#SvcStop +web2py.gluon.winservice.Web2pyService.chdir web2py.gluon.winservice.Web2pyService-class.html#chdir +web2py.gluon.winservice.Service.log web2py.gluon.winservice.Service-class.html#log +web2py.gluon.winservice.Web2pyService.start web2py.gluon.winservice.Web2pyService-class.html#start +web2py.gluon.winservice.Web2pyService.stop web2py.gluon.winservice.Web2pyService-class.html#stop +web2py.gluon.winservice.Web2pyService.server web2py.gluon.winservice.Web2pyService-class.html#server +web2py.gluon.winservice.Web2pyService._exe_args_ web2py.gluon.winservice.Web2pyService-class.html#_exe_args_ +web2py.gluon.winservice.Web2pyService._svc_name_ web2py.gluon.winservice.Web2pyService-class.html#_svc_name_ +web2py.gluon.winservice.Web2pyService._svc_display_name_ web2py.gluon.winservice.Web2pyService-class.html#_svc_display_name_ +web2py.gluon.winservice.Service.__init__ web2py.gluon.winservice.Service-class.html#__init__ ADDED applications/examples/static/epydoc/class-tree.html Index: applications/examples/static/epydoc/class-tree.html ================================================================== --- applications/examples/static/epydoc/class-tree.html +++ applications/examples/static/epydoc/class-tree.html @@ -0,0 +1,1080 @@ + + + + + Class Hierarchy + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + +
      + + + + +
    [hide private]
    [frames] | no frames]
    +
    +
    + [ Module Hierarchy + | Class Hierarchy ] +

    +

    Class Hierarchy

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/crarr.png Index: applications/examples/static/epydoc/crarr.png ================================================================== --- applications/examples/static/epydoc/crarr.png +++ applications/examples/static/epydoc/crarr.png cannot compute difference between binary files ADDED applications/examples/static/epydoc/datetime.date-class.html Index: applications/examples/static/epydoc/datetime.date-class.html ================================================================== --- applications/examples/static/epydoc/datetime.date-class.html +++ applications/examples/static/epydoc/datetime.date-class.html @@ -0,0 +1,960 @@ + + + + + datetime.date + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + datetime :: + date :: + Class date + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class date



    +
    +object --+
    +         |
    +        date
    +
    + +
    Known Subclasses:
    +
    + datetime +
    + +
    +date(year, month, day) --> date object

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __add__(x, + y)
    + x+y
    + + +
    + +
    +   + + + + + + +
    __eq__(x, + y)
    + x==y
    + + +
    + +
    +   + + + + + + +
    __ge__(x, + y)
    + x>=y
    + + +
    + +
    +   + + + + + + +
    __getattribute__(...)
    + x.__getattribute__('name') <==> x.name
    + + +
    + +
    +   + + + + + + +
    __gt__(x, + y)
    + x>y
    + + +
    + +
    +   + + + + + + +
    __hash__(x)
    + hash(x)
    + + +
    + +
    +   + + + + + + +
    __le__(x, + y)
    + x<=y
    + + +
    + +
    +   + + + + + + +
    __lt__(x, + y)
    + x<y
    + + +
    + +
    +   + + + + + + +
    __ne__(x, + y)
    + x!=y
    + + +
    + +
    +   + + + + + + +
    __new__(T, + S, + ...)
    + Returns: +a new object with type S, a subtype of T
    + + +
    + +
    +   + + + + + + +
    __radd__(x, + y)
    + y+x
    + + +
    + +
    +   + + + + + + +
    __reduce__()
    + Returns: +(cls, state)
    + + +
    + +
    +   + + + + + + +
    __repr__(x)
    + repr(x)
    + + +
    + +
    +   + + + + + + +
    __rsub__(x, + y)
    + y-x
    + + +
    + +
    +   + + + + + + +
    __str__(x)
    + str(x)
    + + +
    + +
    +   + + + + + + +
    __sub__(x, + y)
    + x-y
    + + +
    + +
    +   + + + + + + +
    ctime(...)
    + Return ctime() style string.
    + + +
    + +
    +   + + + + + + +
    fromordinal(...)
    + int -> date corresponding to a proleptic Gregorian ordinal.
    + + +
    + +
    +   + + + + + + +
    fromtimestamp(...)
    + timestamp -> local date from a POSIX timestamp (like + time.time()).
    + + +
    + +
    +   + + + + + + +
    isocalendar(...)
    + Return a 3-tuple containing ISO year, week number, and + weekday.
    + + +
    + +
    +   + + + + + + +
    isoformat(...)
    + Return string in ISO 8601 format, YYYY-MM-DD.
    + + +
    + +
    +   + + + + + + +
    isoweekday(...)
    + Return the day of the week represented by the date.
    + + +
    + +
    +   + + + + + + +
    replace(...)
    + Return date with new specified fields.
    + + +
    + +
    +   + + + + + + +
    strftime(...)
    + format -> strftime() style string.
    + + +
    + +
    +   + + + + + + +
    timetuple(...)
    + Return time tuple, compatible with time.localtime().
    + + +
    + +
    +   + + + + + + +
    today(...)
    + Current date or datetime: same as + self.__class__.fromtimestamp(time.time()).
    + + +
    + +
    +   + + + + + + +
    toordinal(...)
    + Return proleptic Gregorian ordinal.
    + + +
    + +
    +   + + + + + + +
    weekday(...)
    + Return the day of the week represented by the date.
    + + +
    + +
    +

    Inherited from object: + __delattr__, + __init__, + __reduce_ex__, + __setattr__ +

    +
    + + + + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + max = datetime.date(9999, 12, 31) +
    +   + + min = datetime.date(1, 1, 1) +
    +   + + resolution = datetime.timedelta(1) +
    + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +   + + day +
    +   + + month +
    +   + + year +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __getattribute__(...) +

    +
      +
    + + x.__getattribute__('name') <==> x.name +
    +
    Overrides: + object.__getattribute__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    __hash__(x) +
    (Hashing function) +

    +
      +
    + + hash(x) +
    +
    Overrides: + object.__hash__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    __new__(T, + S, + ...) +

    +
      +
    + + +
    +
    Returns:
    +
    +a new object with type S, a subtype of T
    +
    +
    +
    Overrides: + object.__new__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    __reduce__() +

    +
      +
    + + helper for pickle +
    +
    Returns:
    +
    +(cls, state)
    +
    +
    +
    Overrides: + object.__reduce__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    __repr__(x) +
    (Representation operator) +

    +
      +
    + + repr(x) +
    +
    Overrides: + object.__repr__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    __str__(x) +
    (Informal representation operator) +

    +
      +
    + + str(x) +
    +
    Overrides: + object.__str__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    isoweekday(...) +

    +
      +
    + + Return the day of the week represented by the date. Monday == 1 ... + Sunday == 7 +
    +
    +
    +
    + +
    + +
    + + +
    +

    toordinal(...) +

    +
      +
    + + Return proleptic Gregorian ordinal. January 1 of year 1 is day 1. +
    +
    +
    +
    + +
    + +
    + + +
    +

    weekday(...) +

    +
      +
    + + Return the day of the week represented by the date. Monday == 0 ... + Sunday == 6 +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/datetime.datetime-class.html Index: applications/examples/static/epydoc/datetime.datetime-class.html ================================================================== --- applications/examples/static/epydoc/datetime.datetime-class.html +++ applications/examples/static/epydoc/datetime.datetime-class.html @@ -0,0 +1,1383 @@ + + + + + datetime.datetime + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + datetime :: + datetime :: + Class datetime + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class datetime



    +
    +object --+    
    +         |    
    +      date --+
    +             |
    +            datetime
    +
    + +
    +

    datetime(year, month, day[, hour[, minute[, second[, + microsecond[,tzinfo]]]]])

    + The year, month and day arguments are required. tzinfo may be None, or + an instance of a tzinfo subclass. The remaining arguments may be ints or + longs.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __add__(x, + y)
    + x+y
    + + +
    + +
    +   + + + + + + +
    __eq__(x, + y)
    + x==y
    + + +
    + +
    +   + + + + + + +
    __ge__(x, + y)
    + x>=y
    + + +
    + +
    +   + + + + + + +
    __getattribute__(...)
    + x.__getattribute__('name') <==> x.name
    + + +
    + +
    +   + + + + + + +
    __gt__(x, + y)
    + x>y
    + + +
    + +
    +   + + + + + + +
    __hash__(x)
    + hash(x)
    + + +
    + +
    +   + + + + + + +
    __le__(x, + y)
    + x<=y
    + + +
    + +
    +   + + + + + + +
    __lt__(x, + y)
    + x<y
    + + +
    + +
    +   + + + + + + +
    __ne__(x, + y)
    + x!=y
    + + +
    + +
    +   + + + + + + +
    __new__(T, + S, + ...)
    + Returns: +a new object with type S, a subtype of T
    + + +
    + +
    +   + + + + + + +
    __radd__(x, + y)
    + y+x
    + + +
    + +
    +   + + + + + + +
    __reduce__()
    + Returns: +(cls, state)
    + + +
    + +
    +   + + + + + + +
    __repr__(x)
    + repr(x)
    + + +
    + +
    +   + + + + + + +
    __rsub__(x, + y)
    + y-x
    + + +
    + +
    +   + + + + + + +
    __str__(x)
    + str(x)
    + + +
    + +
    +   + + + + + + +
    __sub__(x, + y)
    + x-y
    + + +
    + +
    +   + + + + + + +
    astimezone(...)
    + tz -> convert to local time in new timezone tz
    + + +
    + +
    +   + + + + + + +
    combine(...)
    + date, time -> datetime with same date and time fields
    + + +
    + +
    +   + + + + + + +
    ctime(...)
    + Return ctime() style string.
    + + +
    + +
    +   + + + + + + +
    date(...)
    + Return date object with same year, month and day.
    + + +
    + +
    +   + + + + + + +
    dst(...)
    + Return self.tzinfo.dst(self).
    + + +
    + +
    +   + + + + + + +
    fromtimestamp(...)
    + timestamp[, tz] -> tz's local time from POSIX timestamp.
    + + +
    + +
    +   + + + + + + +
    isoformat(...)
    + [sep] -> string in ISO 8601 format, + YYYY-MM-DDTHH:MM:SS[.mmmmmm][+HH:MM].
    + + +
    + +
    +   + + + + + + +
    now(...)
    + [tz] -> new datetime with tz's local day and time.
    + + +
    + +
    +   + + + + + + +
    replace(...)
    + Return datetime with new specified fields.
    + + +
    + +
    +   + + + + + + +
    strptime(...)
    + string, format -> new datetime parsed from a string (like + time.strptime()).
    + + +
    + +
    +   + + + + + + +
    time(...)
    + Return time object with same time but with tzinfo=None.
    + + +
    + +
    +   + + + + + + +
    timetuple(...)
    + Return time tuple, compatible with time.localtime().
    + + +
    + +
    +   + + + + + + +
    timetz(...)
    + Return time object with same time and tzinfo.
    + + +
    + +
    +   + + + + + + +
    tzname(...)
    + Return self.tzinfo.tzname(self).
    + + +
    + +
    +   + + + + + + +
    utcfromtimestamp(...)
    + timestamp -> UTC datetime from a POSIX timestamp (like + time.time()).
    + + +
    + +
    +   + + + + + + +
    utcnow(...)
    + Return a new datetime representing UTC day and time.
    + + +
    + +
    +   + + + + + + +
    utcoffset(...)
    + Return self.tzinfo.utcoffset(self).
    + + +
    + +
    +   + + + + + + +
    utctimetuple(...)
    + Return UTC time tuple, compatible with time.localtime().
    + + +
    + +
    +

    Inherited from date: + fromordinal, + isocalendar, + isoweekday, + strftime, + today, + toordinal, + weekday +

    +

    Inherited from object: + __delattr__, + __init__, + __reduce_ex__, + __setattr__ +

    +
    + + + + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + max = datetime.datetime(9999, 12, 31, 23, 59, 59, 999999) +
    +   + + min = datetime.datetime(1, 1, 1, 0, 0) +
    +   + + resolution = datetime.timedelta(0, 0, 1) +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +   + + hour +
    +   + + microsecond +
    +   + + minute +
    +   + + second +
    +   + + tzinfo +
    +

    Inherited from date: + day, + month, + year +

    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __add__(x, + y) +
    (Addition operator) +

    +
      +
    + + x+y +
    +
    Overrides: + date.__add__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    __eq__(x, + y) +
    (Equality operator) +

    +
      +
    + + x==y +
    +
    Overrides: + date.__eq__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    __ge__(x, + y) +
    (Greater-than-or-equals operator) +

    +
      +
    + + x>=y +
    +
    Overrides: + date.__ge__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    __getattribute__(...) +

    +
      +
    + + x.__getattribute__('name') <==> x.name +
    +
    Overrides: + date.__getattribute__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    __gt__(x, + y) +
    (Greater-than operator) +

    +
      +
    + + x>y +
    +
    Overrides: + date.__gt__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    __hash__(x) +
    (Hashing function) +

    +
      +
    + + hash(x) +
    +
    Overrides: + date.__hash__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    __le__(x, + y) +
    (Less-than-or-equals operator) +

    +
      +
    + + x<=y +
    +
    Overrides: + date.__le__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    __lt__(x, + y) +
    (Less-than operator) +

    +
      +
    + + x<y +
    +
    Overrides: + date.__lt__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    __ne__(x, + y) +

    +
      +
    + + x!=y +
    +
    Overrides: + date.__ne__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    __new__(T, + S, + ...) +

    +
      +
    + + +
    +
    Returns:
    +
    +a new object with type S, a subtype of T
    +
    +
    +
    Overrides: + date.__new__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    __radd__(x, + y) +
    (Right-side addition operator) +

    +
      +
    + + y+x +
    +
    Overrides: + date.__radd__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    __reduce__() +

    +
      +
    + + helper for pickle +
    +
    Returns:
    +
    +(cls, state)
    +
    +
    +
    Overrides: + date.__reduce__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    __repr__(x) +
    (Representation operator) +

    +
      +
    + + repr(x) +
    +
    Overrides: + date.__repr__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    __rsub__(x, + y) +

    +
      +
    + + y-x +
    +
    Overrides: + date.__rsub__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    __str__(x) +
    (Informal representation operator) +

    +
      +
    + + str(x) +
    +
    Overrides: + date.__str__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    __sub__(x, + y) +
    (Subtraction operator) +

    +
      +
    + + x-y +
    +
    Overrides: + date.__sub__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    ctime(...) +

    +
      +
    + + Return ctime() style string. +
    +
    Overrides: + date.ctime +
    +
    +
    +
    + +
    + +
    + + +
    +

    fromtimestamp(...) +

    +
      +
    + + timestamp[, tz] -> tz's local time from POSIX timestamp. +
    +
    Overrides: + date.fromtimestamp +
    +
    +
    +
    + +
    + +
    + + +
    +

    isoformat(...) +

    +
      +
    + +

    [sep] -> string in ISO 8601 format, + YYYY-MM-DDTHH:MM:SS[.mmmmmm][+HH:MM].

    + sep is used to separate the year from the time, and defaults to + 'T'. +
    +
    Overrides: + date.isoformat +
    +
    +
    +
    + +
    + +
    + + +
    +

    replace(...) +

    +
      +
    + + Return datetime with new specified fields. +
    +
    Overrides: + date.replace +
    +
    +
    +
    + +
    + +
    + + +
    +

    timetuple(...) +

    +
      +
    + + Return time tuple, compatible with time.localtime(). +
    +
    Overrides: + date.timetuple +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/datetime.time-class.html Index: applications/examples/static/epydoc/datetime.time-class.html ================================================================== --- applications/examples/static/epydoc/datetime.time-class.html +++ applications/examples/static/epydoc/datetime.time-class.html @@ -0,0 +1,753 @@ + + + + + datetime.time + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + datetime :: + time :: + Class time + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class time



    +
    +object --+
    +         |
    +        time
    +
    + +
    +

    time([hour[, minute[, second[, microsecond[, tzinfo]]]]]) --> a + time object

    + All arguments are optional. tzinfo may be None, or an instance of a + tzinfo subclass. The remaining arguments may be ints or longs.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __eq__(x, + y)
    + x==y
    + + +
    + +
    +   + + + + + + +
    __ge__(x, + y)
    + x>=y
    + + +
    + +
    +   + + + + + + +
    __getattribute__(...)
    + x.__getattribute__('name') <==> x.name
    + + +
    + +
    +   + + + + + + +
    __gt__(x, + y)
    + x>y
    + + +
    + +
    +   + + + + + + +
    __hash__(x)
    + hash(x)
    + + +
    + +
    +   + + + + + + +
    __le__(x, + y)
    + x<=y
    + + +
    + +
    +   + + + + + + +
    __lt__(x, + y)
    + x<y
    + + +
    + +
    +   + + + + + + +
    __ne__(x, + y)
    + x!=y
    + + +
    + +
    +   + + + + + + +
    __new__(T, + S, + ...)
    + Returns: +a new object with type S, a subtype of T
    + + +
    + +
    +   + + + + + + +
    __nonzero__(x)
    + x != 0
    + + +
    + +
    +   + + + + + + +
    __reduce__()
    + Returns: +(cls, state)
    + + +
    + +
    +   + + + + + + +
    __repr__(x)
    + repr(x)
    + + +
    + +
    +   + + + + + + +
    __str__(x)
    + str(x)
    + + +
    + +
    +   + + + + + + +
    dst(...)
    + Return self.tzinfo.dst(self).
    + + +
    + +
    +   + + + + + + +
    isoformat(...)
    + Return string in ISO 8601 format, HH:MM:SS[.mmmmmm][+HH:MM].
    + + +
    + +
    +   + + + + + + +
    replace(...)
    + Return time with new specified fields.
    + + +
    + +
    +   + + + + + + +
    strftime(...)
    + format -> strftime() style string.
    + + +
    + +
    +   + + + + + + +
    tzname(...)
    + Return self.tzinfo.tzname(self).
    + + +
    + +
    +   + + + + + + +
    utcoffset(...)
    + Return self.tzinfo.utcoffset(self).
    + + +
    + +
    +

    Inherited from object: + __delattr__, + __init__, + __reduce_ex__, + __setattr__ +

    +
    + + + + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + max = datetime.time(23, 59, 59, 999999) +
    +   + + min = datetime.time(0, 0) +
    +   + + resolution = datetime.timedelta(0, 0, 1) +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +   + + hour +
    +   + + microsecond +
    +   + + minute +
    +   + + second +
    +   + + tzinfo +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __getattribute__(...) +

    +
      +
    + + x.__getattribute__('name') <==> x.name +
    +
    Overrides: + object.__getattribute__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    __hash__(x) +
    (Hashing function) +

    +
      +
    + + hash(x) +
    +
    Overrides: + object.__hash__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    __new__(T, + S, + ...) +

    +
      +
    + + +
    +
    Returns:
    +
    +a new object with type S, a subtype of T
    +
    +
    +
    Overrides: + object.__new__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    __reduce__() +

    +
      +
    + + helper for pickle +
    +
    Returns:
    +
    +(cls, state)
    +
    +
    +
    Overrides: + object.__reduce__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    __repr__(x) +
    (Representation operator) +

    +
      +
    + + repr(x) +
    +
    Overrides: + object.__repr__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    __str__(x) +
    (Informal representation operator) +

    +
      +
    + + str(x) +
    +
    Overrides: + object.__str__ +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/epydoc.css Index: applications/examples/static/epydoc/epydoc.css ================================================================== --- applications/examples/static/epydoc/epydoc.css +++ applications/examples/static/epydoc/epydoc.css @@ -0,0 +1,410 @@ + + +/* Epydoc CSS Stylesheet + * + * This stylesheet can be used to customize the appearance of epydoc's + * HTML output. + * + */ + +/* Default Colors & Styles + * - Set the default foreground & background color with 'body'; and + * link colors with 'a:link' and 'a:visited'. + * - Use bold for decision list terms. + * - The heading styles defined here are used for headings *within* + * docstring descriptions. All headings used by epydoc itself use + * either class='epydoc' or class='toc' (CSS styles for both + * defined below). +body { background: #ffffff; color: #000000; } +a:link { color: #0000ff; } +a:visited { color: #204080; } +dt { font-weight: bold; } +h1 { font-size: +140%; font-style: italic; + font-weight: bold; } +h2 { font-size: +125%; font-style: italic; + font-weight: bold; } +h3 { font-size: +110%; font-style: italic; + font-weight: normal; } +code { font-size: 100%; } + */ + +body { background-color: #fff; color: #585858; font-size: 10pt; font-family: georgia, serif; } +a {color: #FF5C1F; } +a:hover { text-decoration: underline; } +a:visited { color: #FF5C1F;} +dt { font-weight: bold; } +h1 { font-size: +140%; font-style: italic; + font-weight: bold; } +h2 { color: #185360; font-size: +125%; font-style: italic; + font-weight: bold; } +h3 { color: #185360; font-size: +110%; font-style: italic; + font-weight: normal; } +code { font-size: 100%; } + +/* Page Header & Footer + * - The standard page header consists of a navigation bar (with + * pointers to standard pages such as 'home' and 'trees'); a + * breadcrumbs list, which can be used to navigate to containing + * classes or modules; options links, to show/hide private + * variables and to show/hide frames; and a page title (using + *

    ). The page title may be followed by a link to the + * corresponding source code (using 'span.codelink'). + * - The footer consists of a navigation bar, a timestamp, and a + * pointer to epydoc's homepage. + +h1.epydoc { margin: 0; font-size: +140%; font-weight: bold; } +h2.epydoc { font-size: +130%; font-weight: bold; } +h3.epydoc { font-size: +115%; font-weight: bold; } +td h3.epydoc { font-size: +115%; font-weight: bold; + margin-bottom: 0; } +table.navbar { background: #a0c0ff; color: #000000; + border: 2px groove #c0d0d0; } +table.navbar table { color: #000000; } +th.navbar-select { background: #70b0ff; + color: #000000; } +table.navbar a { text-decoration: none; } +table.navbar a:link { color: #0000ff; } +table.navbar a:visited { color: #204080; } +span.breadcrumbs { font-size: 85%; font-weight: bold; } +span.options { font-size: 70%; } +span.codelink { font-size: 85%; } +td.footer { font-size: 85%; } +*/ + +h1.epydoc { margin: 0; font-size: +140%; font-weight: bold; } +h2.epydoc { font-size: +130%; font-weight: bold; } +h3.epydoc { font-size: +115%; font-weight: bold; } +td h3.epydoc { font-size: +115%; font-weight: bold; + margin-bottom: 0; } +table.navbar { background: url('title.png') repeat-x; + #background: #a0c0ff; + color: #FF5C1F; + #border: 2px groove #c0d0d0; } + +table.navbar table { color: #FF5C1F; } +th.navbar-select { background: #fff; + color: #195866; } + +table.navbar a { text-decoration: none; + color: #FF5C1F;} +table.navbar a:link { color: #FF5C1F; } +table.navbar a:visited { color: #FF5C1F; } + +span.breadcrumbs { font-size: 85%; font-weight: bold; } +span.options { font-size: 70%; } +span.codelink { font-size: 85%; } +td.footer { font-size: 85%; } + + +/* Table Headers + * - Each summary table and details section begins with a 'header' + * row. This row contains a section title (marked by + * 'span.table-header') as well as a show/hide private link + * (marked by 'span.options', defined above). + * - Summary tables that contain user-defined groups mark those + * groups using 'group header' rows. + +td.table-header { background: #70b0ff; color: #000000; + border: 1px solid #608090; } +td.table-header table { color: #000000; } +td.table-header table a:link { color: #0000ff; } +td.table-header table a:visited { color: #204080; } +span.table-header { font-size: 120%; font-weight: bold; } +th.group-header { background: #c0e0f8; color: #000000; + text-align: left; font-style: italic; + font-size: 115%; + border: 1px solid #608090; } +*/ +td.table-header { background: #258396; color: #000000; + border: 1px solid #608090; } + +td.table-header table { color: #fff; } +td.table-header table a:link { color: #FF5C1F; } +td.table-header table a:visited { color: #FF5C1F; } +span.table-header { font-size: 120%; font-weight: bold; } +th.group-header { background: #185360; color: #fff; + text-align: left; font-style: italic; + font-size: 115%; + border: 1px solid #608090; } + +/* Summary Tables (functions, variables, etc) + * - Each object is described by a single row of the table with + * two cells. The left cell gives the object's type, and is + * marked with 'code.summary-type'. The right cell gives the + * object's name and a summary description. + * - CSS styles for the table's header and group headers are + * defined above, under 'Table Headers' + */ +table.summary { border-collapse: collapse; + background: #e8f0f8; color: #000000; + border: 1px solid #608090; + margin-bottom: 0.5em; } +td.summary { border: 1px solid #608090; } +code.summary-type { font-size: 85%; } +table.summary a:link { color: #FF5C1F; } +table.summary a:visited { color: #FF5C1F; } + + +/* Details Tables (functions, variables, etc) + * - Each object is described in its own div. + * - A single-row summary table w/ table-header is used as + * a header for each details section (CSS style for table-header + * is defined above, under 'Table Headers'). + */ +table.details { border-collapse: collapse; + background: #e8f0f8; color: #585858; + border: 1px solid #608090; + margin: .2em 0 0 0; } +table.details table { color: #fff; } +table.details a:link { color: #FF5C1F; } +table.details a:visited { color: #FF5C1F; } + +/* Fields */ +dl.fields { margin-left: 2em; margin-top: 1em; + margin-bottom: 1em; } +dl.fields dd ul { margin-left: 0em; padding-left: 0em; } +div.fields { margin-left: 2em; } +div.fields p { margin-bottom: 0.5em; } + +/* Index tables (identifier index, term index, etc) + * - link-index is used for indices containing lists of links + * (namely, the identifier index & term index). + * - index-where is used in link indices for the text indicating + * the container/source for each link. + * - metadata-index is used for indices containing metadata + * extracted from fields (namely, the bug index & todo index). + */ +table.link-index { border-collapse: collapse; + background: #e8f0f8; color: #000000; + border: 1px solid #608090; } +td.link-index { border-width: 0px; } +table.link-index a:link { color: #FF5C1F; } +table.link-index a:visited { color: #FF5C1F; } +span.index-where { font-size: 70%; } +table.metadata-index { border-collapse: collapse; + background: #e8f0f8; color: #000000; + border: 1px solid #608090; + margin: .2em 0 0 0; } +td.metadata-index { border-width: 1px; border-style: solid; } +table.metadata-index a:link { color: #FF5C1F; } +table.metadata-index a:visited { color: #FF5C1F; } + +/* Function signatures + * - sig* is used for the signature in the details section. + * - .summary-sig* is used for the signature in the summary + * table, and when listing property accessor functions. +.sig-name { color: #006080; } +.sig-arg { color: #008060; } +.sig-default { color: #602000; } +.summary-sig { font-family: monospace; } +.summary-sig-name { color: #006080; font-weight: bold; } +table.summary a.summary-sig-name:link + { color: #006080; font-weight: bold; } +table.summary a.summary-sig-name:visited + { color: #006080; font-weight: bold; } +.summary-sig-arg { color: #006040; } +.summary-sig-default { color: #501800; } + * */ +.sig-name { color: #FF5C1F; } +.sig-arg { color: #008060; } +.sig-default { color: #602000; } +.summary-sig { font-family: monospace; } +.summary-sig-name { color: #FF5C1F; font-weight: bold; } +table.summary a.summary-sig-name:link + { color: #FF5C1F; font-weight: bold; } +table.summary a.summary-sig-name:visited + { color: #FF5C1F; font-weight: bold; } +.summary-sig-arg { color: #006040; } +.summary-sig-default { color: #FF5C1F; } + + +/* To render variables, classes etc. like functions */ +table.summary .summary-name { color: #FF5C1F; font-weight: bold; + font-family: monospace; } +table.summary + a.summary-name:link { color: #FF5C1F; font-weight: bold; + font-family: monospace; } +table.summary + a.summary-name:visited { color: #FF5C1F; font-weight: bold; + font-family: monospace; } + +/* Variable values + * - In the 'variable details' sections, each variable's value is + * listed in a 'pre.variable' box. The width of this box is + * restricted to 80 chars; if the value's repr is longer than + * this it will be wrapped, using a backslash marked with + * class 'variable-linewrap'. If the value's repr is longer + * than 3 lines, the rest will be elided; and an ellipsis + * marker ('...' marked with 'variable-ellipsis') will be used. + * - If the value is a string, its quote marks will be marked + * with 'variable-quote'. + * - If the variable is a regexp, it is syntax-highlighted using + * the re* CSS classes. + */ +pre.variable { padding: .5em; margin: 0; + background: #dce4ec; color: #000000; + border: 1px solid #708890; } +.variable-linewrap { color: #604000; font-weight: bold; } +.variable-ellipsis { color: #604000; font-weight: bold; } +.variable-quote { color: #604000; font-weight: bold; } +.variable-group { color: #008000; font-weight: bold; } +.variable-op { color: #604000; font-weight: bold; } +.variable-string { color: #006030; } +.variable-unknown { color: #a00000; font-weight: bold; } +.re { color: #000000; } +.re-char { color: #006030; } +.re-op { color: #600000; } +.re-group { color: #003060; } +.re-ref { color: #404040; } + +/* Base tree + * - Used by class pages to display the base class hierarchy. + */ +pre.base-tree { font-size: 80%; margin: 0; } + +/* Frames-based table of contents headers + * - Consists of two frames: one for selecting modules; and + * the other listing the contents of the selected module. + * - h1.toc is used for each frame's heading + * - h2.toc is used for subheadings within each frame. + */ +h1.toc { text-align: center; font-size: 105%; + margin: 0; font-weight: bold; + padding: 0; } +h2.toc { font-size: 100%; font-weight: bold; + margin: 0.5em 0 0 -0.3em; } + +/* Syntax Highlighting for Source Code + * - doctest examples are displayed in a 'pre.py-doctest' block. + * If the example is in a details table entry, then it will use + * the colors specified by the 'table pre.py-doctest' line. + * - Source code listings are displayed in a 'pre.py-src' block. + * Each line is marked with 'span.py-line' (used to draw a line + * down the left margin, separating the code from the line + * numbers). Line numbers are displayed with 'span.py-lineno'. + * The expand/collapse block toggle button is displayed with + * 'a.py-toggle' (Note: the CSS style for 'a.py-toggle' should not + * modify the font size of the text.) + * - If a source code page is opened with an anchor, then the + * corresponding code block will be highlighted. The code + * block's header is highlighted with 'py-highlight-hdr'; and + * the code block's body is highlighted with 'py-highlight'. + * - The remaining py-* classes are used to perform syntax + * highlighting (py-string for string literals, py-name for names, + * etc.) + +pre.py-doctest { padding: .5em; margin: 1em; + background: #e8f0f8; color: #000000; + border: 1px solid #708890; } +table pre.py-doctest { background: #dce4ec; + color: #000000; } +pre.py-src { border: 2px solid #000000; + background: #f0f0f0; color: #000000; } +.py-line { border-left: 2px solid #000000; + margin-left: .2em; padding-left: .4em; } +.py-lineno { font-style: italic; font-size: 90%; + padding-left: .5em; } +a.py-toggle { text-decoration: none; } +div.py-highlight-hdr { border-top: 2px solid #000000; + border-bottom: 2px solid #000000; + background: #d8e8e8; } +div.py-highlight { border-bottom: 2px solid #000000; + background: #d0e0e0; } +.py-prompt { color: #005050; font-weight: bold;} +.py-more { color: #005050; font-weight: bold;} +.py-string { color: #006030; } +.py-comment { color: #003060; } +.py-keyword { color: #600000; } +.py-output { color: #404040; } +.py-name { color: #000050; } +.py-name:link { color: #000050 !important; } +.py-name:visited { color: #000050 !important; } +.py-number { color: #005000; } +.py-defname { color: #000060; font-weight: bold; } +.py-def-name { color: #000060; font-weight: bold; } +.py-base-class { color: #000060; } +.py-param { color: #000060; } +.py-docstring { color: #006030; } +.py-decorator { color: #804020; } + */ +/* Use this if you don't want links to names underlined: */ +/*a.py-name { text-decoration: none; }*/ + +pre.py-doctest { padding: .5em; margin: 1em; + background: #e8f0f8; color: #000000; + border: 1px solid #708890; } +table pre.py-doctest { background: #dce4ec; + color: #000000; } +pre.py-src { border: 2px solid #000000; + background: #f0f0f0; color: #000000; } +.py-line { border-left: 2px solid #000000; + margin-left: .2em; padding-left: .4em; } +.py-lineno { font-style: italic; font-size: 90%; + padding-left: .5em; } +a.py-toggle { text-decoration: none; } +div.py-highlight-hdr { border-top: 2px solid #000000; + border-bottom: 2px solid #000000; + background: #d8e8e8; } +div.py-highlight { border-bottom: 2px solid #000000; + background: #d0e0e0; } +.py-prompt { color: #005050; font-weight: bold;} +.py-more { color: #005050; font-weight: bold;} +.py-string { color: green; } +.py-comment { color: green; } +.py-keyword { color: blue; } +.py-output { color: #404040; } +.py-name { color: #585858;} +.py-name:link { color: #FF5C1F !important; } +.py-name:visited { color: #FF5C1F !important; } +.py-number { color: #005000; } +.py-defname { color: #FF5C1F; font-weight: bold; } +.py-def-name { color: #FF5C1F; font-weight: bold; } +.py-base-class { color: #FF5C1F; } +.py-param { color: #000060; } +.py-docstring { color: green; } +.py-decorator { color: #804020; } + +/* Graphs & Diagrams + * - These CSS styles are used for graphs & diagrams generated using + * Graphviz dot. 'img.graph-without-title' is used for bare + * diagrams (to remove the border created by making the image + * clickable). + */ +img.graph-without-title { border: none; } +img.graph-with-title { border: 1px solid #000000; } +span.graph-title { font-weight: bold; } +span.graph-caption { } + +/* General-purpose classes + * - 'p.indent-wrapped-lines' defines a paragraph whose first line + * is not indented, but whose subsequent lines are. + * - The 'nomargin-top' class is used to remove the top margin (e.g. + * from lists). The 'nomargin' class is used to remove both the + * top and bottom margin (but not the left or right margin -- + * for lists, that would cause the bullets to disappear.) + */ +p.indent-wrapped-lines { padding: 0 0 0 7em; text-indent: -7em; + margin: 0; } +.nomargin-top { margin-top: 0; } +.nomargin { margin-top: 0; margin-bottom: 0; } + +/* HTML Log */ +div.log-block { padding: 0; margin: .5em 0 .5em 0; + background: #e8f0f8; color: #000000; + border: 1px solid #000000; } +div.log-error { padding: .1em .3em .1em .3em; margin: 4px; + background: #ffb0b0; color: #000000; + border: 1px solid #000000; } +div.log-warning { padding: .1em .3em .1em .3em; margin: 4px; + background: #ffffb0; color: #000000; + border: 1px solid #000000; } +div.log-info { padding: .1em .3em .1em .3em; margin: 4px; + background: #b0ffb0; color: #000000; + border: 1px solid #000000; } +h2.log-hdr { background: #70b0ff; color: #000000; + margin: 0; padding: 0em 0.5em 0em 0.5em; + border-bottom: 1px solid #000000; font-size: 110%; } +p.log { font-weight: bold; margin: .5em 0 .5em 0; } +tr.opt-changed { color: #000000; font-weight: bold; } +tr.opt-default { color: #606060; } +pre.log { margin: 0; padding: 0; padding-left: 1em; } ADDED applications/examples/static/epydoc/epydoc.js Index: applications/examples/static/epydoc/epydoc.js ================================================================== --- applications/examples/static/epydoc/epydoc.js +++ applications/examples/static/epydoc/epydoc.js @@ -0,0 +1,280 @@ +function toggle_private() { + // Search for any private/public links on this page. Store + // their old text in "cmd," so we will know what action to + // take; and change their text to the opposite action. + var cmd = "?"; + var elts = document.getElementsByTagName("a"); + for(var i=0; i...
    "; + elt.innerHTML = s; + } +} + +function toggle(id) { + elt = document.getElementById(id+"-toggle"); + if (elt.innerHTML == "-") + collapse(id); + else + expand(id); + return false; +} + +function highlight(id) { + var elt = document.getElementById(id+"-def"); + if (elt) elt.className = "py-highlight-hdr"; + var elt = document.getElementById(id+"-expanded"); + if (elt) elt.className = "py-highlight"; + var elt = document.getElementById(id+"-collapsed"); + if (elt) elt.className = "py-highlight"; +} + +function num_lines(s) { + var n = 1; + var pos = s.indexOf("\n"); + while ( pos > 0) { + n += 1; + pos = s.indexOf("\n", pos+1); + } + return n; +} + +// Collapse all blocks that mave more than `min_lines` lines. +function collapse_all(min_lines) { + var elts = document.getElementsByTagName("div"); + for (var i=0; i 0) + if (elt.id.substring(split, elt.id.length) == "-expanded") + if (num_lines(elt.innerHTML) > min_lines) + collapse(elt.id.substring(0, split)); + } +} + +function expandto(href) { + var start = href.indexOf("#")+1; + if (start != 0 && start != href.length) { + if (href.substring(start, href.length) != "-") { + collapse_all(4); + pos = href.indexOf(".", start); + while (pos != -1) { + var id = href.substring(start, pos); + expand(id); + pos = href.indexOf(".", pos+1); + } + var id = href.substring(start, href.length); + expand(id); + highlight(id); + } + } +} + +function kill_doclink(id) { + var parent = document.getElementById(id); + parent.removeChild(parent.childNodes.item(0)); +} +function auto_kill_doclink(ev) { + if (!ev) var ev = window.event; + if (!this.contains(ev.toElement)) { + var parent = document.getElementById(this.parentID); + parent.removeChild(parent.childNodes.item(0)); + } +} + +function doclink(id, name, targets_id) { + var elt = document.getElementById(id); + + // If we already opened the box, then destroy it. + // (This case should never occur, but leave it in just in case.) + if (elt.childNodes.length > 1) { + elt.removeChild(elt.childNodes.item(0)); + } + else { + // The outer box: relative + inline positioning. + var box1 = document.createElement("div"); + box1.style.position = "relative"; + box1.style.display = "inline"; + box1.style.top = 0; + box1.style.left = 0; + + // A shadow for fun + var shadow = document.createElement("div"); + shadow.style.position = "absolute"; + shadow.style.left = "-1.3em"; + shadow.style.top = "-1.3em"; + shadow.style.background = "#404040"; + + // The inner box: absolute positioning. + var box2 = document.createElement("div"); + box2.style.position = "relative"; + box2.style.border = "1px solid #a0a0a0"; + box2.style.left = "-.2em"; + box2.style.top = "-.2em"; + box2.style.background = "white"; + box2.style.padding = ".3em .4em .3em .4em"; + box2.style.fontStyle = "normal"; + box2.onmouseout=auto_kill_doclink; + box2.parentID = id; + + // Get the targets + var targets_elt = document.getElementById(targets_id); + var targets = targets_elt.getAttribute("targets"); + var links = ""; + target_list = targets.split(","); + for (var i=0; i" + + target[0] + ""; + } + + // Put it all together. + elt.insertBefore(box1, elt.childNodes.item(0)); + //box1.appendChild(box2); + box1.appendChild(shadow); + shadow.appendChild(box2); + box2.innerHTML = + "Which "+name+" do you want to see documentation for?" + + ""; + } + return false; +} + +function get_anchor() { + var href = location.href; + var start = href.indexOf("#")+1; + if ((start != 0) && (start != href.length)) + return href.substring(start, href.length); + } +function redirect_url(dottedName) { + // Scan through each element of the "pages" list, and check + // if "name" matches with any of them. + for (var i=0; i-m" or "-c"; + // extract the portion & compare it to dottedName. + var pagename = pages[i].substring(0, pages[i].length-2); + if (pagename == dottedName.substring(0,pagename.length)) { + + // We've found a page that matches `dottedName`; + // construct its URL, using leftover `dottedName` + // content to form an anchor. + var pagetype = pages[i].charAt(pages[i].length-1); + var url = pagename + ((pagetype=="m")?"-module.html": + "-class.html"); + if (dottedName.length > pagename.length) + url += "#" + dottedName.substring(pagename.length+1, + dottedName.length); + return url; + } + } + } ADDED applications/examples/static/epydoc/exceptions.Exception-class.html Index: applications/examples/static/epydoc/exceptions.Exception-class.html ================================================================== --- applications/examples/static/epydoc/exceptions.Exception-class.html +++ applications/examples/static/epydoc/exceptions.Exception-class.html @@ -0,0 +1,324 @@ + + + + + exceptions.Exception + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + exceptions :: + Exception :: + Class Exception + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class Exception



    +
    +   object --+    
    +            |    
    +BaseException --+
    +                |
    +               Exception
    +
    + +
    Known Subclasses:
    +
    + StandardError, + Warning, + GeneratorExit, + StopIteration, + web2py.gluon.http.HTTP, + Queue.Empty, + Queue.Full, + HTMLParser.HTMLParseError, + web2py.gluon.rocket.BadRequest, + socket.error, + web2py.gluon.rocket.SocketClosed, + web2py.gluon.rocket.SocketTimeout, + web2py.gluon.rocket.BadRequest, + web2py.gluon.rocket.SocketClosed, + web2py.gluon.contrib.pymysql.err.MySQLError, + web2py.gluon.tools.Service.JsonRpcException, + web2py.gluon.tools.Service.JsonRpcException, + web2py.gluon.restricted.RestrictedError +
    + +
    +Common base class for all non-exit exceptions.

    + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(...)
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + + +
    + +
    +   + + + + + + +
    __new__(T, + S, + ...)
    + Returns: +a new object with type S, a subtype of T
    + + +
    + +
    +

    Inherited from BaseException: + __delattr__, + __getattribute__, + __getitem__, + __getslice__, + __reduce__, + __repr__, + __setattr__, + __setstate__, + __str__ +

    +

    Inherited from object: + __hash__, + __reduce_ex__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from BaseException: + args, + message +

    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(...) +
    (Constructor) +

    +
      +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + BaseException.__init__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    __new__(T, + S, + ...) +

    +
      +
    + + +
    +
    Returns:
    +
    +a new object with type S, a subtype of T
    +
    +
    +
    Overrides: + BaseException.__new__ +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/frames.html Index: applications/examples/static/epydoc/frames.html ================================================================== --- applications/examples/static/epydoc/frames.html +++ applications/examples/static/epydoc/frames.html @@ -0,0 +1,17 @@ + + + + + web2py Web Framework + + + + + + + + + ADDED applications/examples/static/epydoc/help.html Index: applications/examples/static/epydoc/help.html ================================================================== --- applications/examples/static/epydoc/help.html +++ applications/examples/static/epydoc/help.html @@ -0,0 +1,278 @@ + + + + + Help + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    API Documentation

    + +

    This document contains the API (Application Programming Interface) +documentation for web2py Web Framework. Documentation for the Python +objects defined by the project is divided into separate pages for each +package, module, and class. The API documentation also includes two +pages containing information about the project as a whole: a trees +page, and an index page.

    + +

    Object Documentation

    + +

    Each Package Documentation page contains:

    +
      +
    • A description of the package.
    • +
    • A list of the modules and sub-packages contained by the + package.
    • +
    • A summary of the classes defined by the package.
    • +
    • A summary of the functions defined by the package.
    • +
    • A summary of the variables defined by the package.
    • +
    • A detailed description of each function defined by the + package.
    • +
    • A detailed description of each variable defined by the + package.
    • +
    + +

    Each Module Documentation page contains:

    +
      +
    • A description of the module.
    • +
    • A summary of the classes defined by the module.
    • +
    • A summary of the functions defined by the module.
    • +
    • A summary of the variables defined by the module.
    • +
    • A detailed description of each function defined by the + module.
    • +
    • A detailed description of each variable defined by the + module.
    • +
    + +

    Each Class Documentation page contains:

    +
      +
    • A class inheritance diagram.
    • +
    • A list of known subclasses.
    • +
    • A description of the class.
    • +
    • A summary of the methods defined by the class.
    • +
    • A summary of the instance variables defined by the class.
    • +
    • A summary of the class (static) variables defined by the + class.
    • +
    • A detailed description of each method defined by the + class.
    • +
    • A detailed description of each instance variable defined by the + class.
    • +
    • A detailed description of each class (static) variable defined + by the class.
    • +
    + +

    Project Documentation

    + +

    The Trees page contains the module and class hierarchies:

    +
      +
    • The module hierarchy lists every package and module, with + modules grouped into packages. At the top level, and within each + package, modules and sub-packages are listed alphabetically.
    • +
    • The class hierarchy lists every class, grouped by base + class. If a class has more than one base class, then it will be + listed under each base class. At the top level, and under each base + class, classes are listed alphabetically.
    • +
    + +

    The Index page contains indices of terms and + identifiers:

    +
      +
    • The term index lists every term indexed by any object's + documentation. For each term, the index provides links to each + place where the term is indexed.
    • +
    • The identifier index lists the (short) name of every package, + module, class, method, function, variable, and parameter. For each + identifier, the index provides a short description, and a link to + its documentation.
    • +
    + +

    The Table of Contents

    + +

    The table of contents occupies the two frames on the left side of +the window. The upper-left frame displays the project +contents, and the lower-left frame displays the module +contents:

    + + + + + + + + + +
    + Project
    Contents
    ...
    + API
    Documentation
    Frame


    +
    + Module
    Contents
     
    ...
      +

    + +

    The project contents frame contains a list of all packages +and modules that are defined by the project. Clicking on an entry +will display its contents in the module contents frame. Clicking on a +special entry, labeled "Everything," will display the contents of +the entire project.

    + +

    The module contents frame contains a list of every +submodule, class, type, exception, function, and variable defined by a +module or package. Clicking on an entry will display its +documentation in the API documentation frame. Clicking on the name of +the module, at the top of the frame, will display the documentation +for the module itself.

    + +

    The "frames" and "no frames" buttons below the top +navigation bar can be used to control whether the table of contents is +displayed or not.

    + +

    The Navigation Bar

    + +

    A navigation bar is located at the top and bottom of every page. +It indicates what type of page you are currently viewing, and allows +you to go to related pages. The following table describes the labels +on the navigation bar. Note that not some labels (such as +[Parent]) are not displayed on all pages.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    LabelHighlighted when...Links to...
    [Parent](never highlighted) the parent of the current package
    [Package]viewing a packagethe package containing the current object +
    [Module]viewing a modulethe module containing the current object +
    [Class]viewing a class the class containing the current object
    [Trees]viewing the trees page the trees page
    [Index]viewing the index page the index page
    [Help]viewing the help page the help page
    + +

    The "show private" and "hide private" buttons below +the top navigation bar can be used to control whether documentation +for private objects is displayed. Private objects are usually defined +as objects whose (short) names begin with a single underscore, but do +not end with an underscore. For example, "_x", +"__pprint", and "epydoc.epytext._tokenize" +are private objects; but "re.sub", +"__init__", and "type_" are not. However, +if a module defines the "__all__" variable, then its +contents are used to decide which objects are private.

    + +

    A timestamp below the bottom navigation bar indicates when each +page was last updated.

    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/identifier-index.html Index: applications/examples/static/epydoc/identifier-index.html ================================================================== --- applications/examples/static/epydoc/identifier-index.html +++ applications/examples/static/epydoc/identifier-index.html @@ -0,0 +1,5669 @@ + + + + + Identifier Index + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +
    +

    Identifier Index

    +
    +[ + A + B + C + D + E + F + G + H + I + J + K + L + M + N + O + P + Q + R + S + T + U + V + W + X + Y + Z + _ +] +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    A

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    B

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    C

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    D

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    E

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    F

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    G

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    H

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    I

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    J

    + + + + + + + + + + + + + + + + + + + + + + +

    K

    + + + + + + + + + + + + +

    L

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    M

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    N

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    O

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    P

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Q

    + + + + + + + + +

    R

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    S

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    T

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    U

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    V

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    W

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    X

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Y

    + + + + + + + + +

    Z

    + + + + + + + + +

    _

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +

    + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/index.html Index: applications/examples/static/epydoc/index.html ================================================================== --- applications/examples/static/epydoc/index.html +++ applications/examples/static/epydoc/index.html @@ -0,0 +1,17 @@ + + + + + web2py Web Framework + + + + + + + + + ADDED applications/examples/static/epydoc/module-tree.html Index: applications/examples/static/epydoc/module-tree.html ================================================================== --- applications/examples/static/epydoc/module-tree.html +++ applications/examples/static/epydoc/module-tree.html @@ -0,0 +1,242 @@ + + + + + Module Hierarchy + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      + + + + +
    [hide private]
    [frames] | no frames]
    +
    +
    + [ Module Hierarchy + | Class Hierarchy ] +

    +

    Module Hierarchy

    +
      +
    • psycopg2: A Python driver for PostgreSQL + +psycopg is a PostgreSQL_ database adapter for the Python_ programming +language. + +
    • +
    • sqlite3.dbapi2: PyMySQL: A pure-Python drop-in replacement for MySQLdb.
    • +
    • web2py.gluon: This file is part of the web2py Web Framework Copyrighted by + Massimo Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html) +
        +
      • web2py.gluon.admin: This file is part of the web2py Web Framework Copyrighted by + Massimo Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)
      • +
      • web2py.gluon.cache: This file is part of the web2py Web Framework Copyrighted by + Massimo Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)
      • +
      • web2py.gluon.cfs: This file is part of the web2py Web Framework Copyrighted by + Massimo Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)
      • +
      • web2py.gluon.compileapp: This file is part of the web2py Web Framework Copyrighted by + Massimo Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)
      • +
      • web2py.gluon.contenttype: This file is part of the web2py Web Framework Copyrighted by + Massimo Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)
      • +
      • web2py.gluon.contrib + +
      • +
      • web2py.gluon.custom_import
      • +
      • web2py.gluon.dal: This file is part of the web2py Web Framework...
      • +
      • web2py.gluon.debug: This file is part of the web2py Web Framework Developed by + Massimo Di Pierro <mdipierro@cs.depaul.edu>, limodou + <limodou@gmail.com> and srackham + <srackham@gmail.com>.
      • +
      • web2py.gluon.decoder
      • +
      • web2py.gluon.fileutils: This file is part of the web2py Web Framework Copyrighted by + Massimo Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)
      • +
      • web2py.gluon.globals: This file is part of the web2py Web Framework...
      • +
      • web2py.gluon.highlight: This file is part of the web2py Web Framework Copyrighted by + Massimo Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)
      • +
      • web2py.gluon.html: This file is part of the web2py Web Framework Copyrighted by + Massimo Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)
      • +
      • web2py.gluon.http: This file is part of the web2py Web Framework Copyrighted by + Massimo Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)
      • +
      • web2py.gluon.import_all: This file is part of the web2py Web Framework Copyrighted by + Massimo Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)
      • +
      • web2py.gluon.languages: This file is part of the web2py Web Framework Copyrighted by + Massimo Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)
      • +
      • web2py.gluon.main: This file is part of the web2py Web Framework...
      • +
      • web2py.gluon.myregex: This file is part of the web2py Web Framework Copyrighted by + Massimo Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)
      • +
      • web2py.gluon.newcron: Created by Attila Csipa <web2py@csipa.in.rs> Modified by + Massimo Di Pierro <mdipierro@cs.depaul.edu>
      • +
      • web2py.gluon.portalocker: Cross-platform (posix/nt) API for flock-style file locking.
      • +
      • web2py.gluon.reserved_sql_keywords
      • +
      • web2py.gluon.restricted: This file is part of the web2py Web Framework Copyrighted by + Massimo Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)
      • +
      • web2py.gluon.rewrite: This file is part of the web2py Web Framework Copyrighted by + Massimo Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)
      • +
      • web2py.gluon.rocket
      • +
      • web2py.gluon.sanitizer: :
      • +
      • web2py.gluon.serializers: This file is part of the web2py Web Framework Copyrighted by + Massimo Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)
      • +
      • web2py.gluon.settings: This file is part of the web2py Web Framework Copyrighted by + Massimo Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)
      • +
      • web2py.gluon.shell: This file is part of the web2py Web Framework Developed by + Massimo Di Pierro <mdipierro@cs.depaul.edu>, limodou + <limodou@gmail.com> and srackham + <srackham@gmail.com>.
      • +
      • web2py.gluon.sql
      • +
      • web2py.gluon.sqlhtml: This file is part of the web2py Web Framework...
      • +
      • web2py.gluon.storage: This file is part of the web2py Web Framework...
      • +
      • web2py.gluon.streamer: This file is part of the web2py Web Framework Copyrighted by + Massimo Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)
      • +
      • web2py.gluon.template: This file is part of the web2py Web Framework (Copyrighted, 2007-2011).
      • +
      • web2py.gluon.tools: This file is part of the web2py Web Framework Copyrighted by + Massimo Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)
      • +
      • web2py.gluon.utils: This file is part of the web2py Web Framework Copyrighted by + Massimo Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)
      • +
      • web2py.gluon.validators: This file is part of the web2py Web Framework Copyrighted by + Massimo Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)
      • +
      • web2py.gluon.widget: This file is part of the web2py Web Framework Copyrighted by + Massimo Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)
      • +
      • web2py.gluon.xmlrpc: This file is part of the web2py Web Framework Copyrighted by + Massimo Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)
      • +
      +
    • +
    • web2py.gluon.contrib.pymysql: PyMySQL: A pure-Python drop-in replacement for MySQLdb. + +
    • +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/psycopg2-module.html Index: applications/examples/static/epydoc/psycopg2-module.html ================================================================== --- applications/examples/static/epydoc/psycopg2-module.html +++ applications/examples/static/epydoc/psycopg2-module.html @@ -0,0 +1,770 @@ + + + + + psycopg2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package psycopg2 + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Package psycopg2

    source code

    +
    +A Python driver for PostgreSQL
    +
    +psycopg is a PostgreSQL_ database adapter for the Python_ programming
    +language. This is version 2, a complete rewrite of the original code to
    +provide new-style classes for connection and cursor objects and other sweet
    +candies. Like the original, psycopg 2 was written with the aim of being very
    +small and fast, and stable as a rock.
    +
    +Homepage: http://initd.org/projects/psycopg2
    +
    +.. _PostgreSQL: http://www.postgresql.org/
    +.. _Python: http://www.python.org/
    +
    +:Groups:
    +  * `Connections creation`: connect
    +  * `Value objects constructors`: Binary, Date, DateFromTicks, Time,
    +    TimeFromTicks, Timestamp, TimestampFromTicks
    +
    +


    + +
    +

    Version: + 2.0.6 (dec mx dt ext pq3) +

    +
    + + + + + + +
    + + + + + +
    Submodules[hide private]
    +
    +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Classes[hide private]
    +
    +   + + DataError
    + Error related to problems with the processed data. +
    +   + + DatabaseError
    + Error related to the database engine. +
    +   + + Error
    + Base class for error exceptions. +
    +   + + IntegrityError
    + Error related to database integrity. +
    +   + + InterfaceError
    + Error related to the database interface. +
    +   + + InternalError
    + The database encountered an internal error. +
    +   + + NotSupportedError
    + A not supported datbase API was called. +
    +   + + OperationalError
    + Error related to database operation (disconnect, memory + allocation etc). +
    +   + + ProgrammingError
    + Error related to database programming (SQL error, table not + found etc). +
    +   + + Warning
    + A database warning. +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Functions[hide private]
    +
    +   + + + + + + +
    Binary(buffer)
    + Build an object capable to hold a bynary string value.
    + + +
    + +
    +   + + + + + + +
    Date(year, + month, + day)
    + Build an object holding a date value.
    + + +
    + +
    +   + + + + + + +
    DateFromTicks(ticks)
    + Build an object holding a date value from the given ticks + value.
    + + +
    + +
    +   + + + + + + +
    Time(hour, + minutes, + seconds, + tzinfo=None)
    + Build an object holding a time value.
    + + +
    + +
    +   + + + + + + +
    TimeFromTicks(ticks)
    + Build an object holding a time value from the given ticks + value.
    + + +
    + +
    +   + + + + + + +
    Timestamp(year, + month, + day, + hour, + minutes, + seconds, + tzinfo=None)
    + Build an object holding a timestamp value.
    + + +
    + +
    +   + + + + + + +
    TimestampFromTicks(ticks)
    + Build an object holding a timestamp value from the given ticks + value.
    + + +
    + +
    +   + + + + + + +
    connect(dsn, + ...)
    + Create a new database connection.
    + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Variables[hide private]
    +
    +   + + BINARY = <psycopg2._psycopg.type object at 0xd660a0> +
    +   + + DATETIME = <psycopg2._psycopg.type object at 0xd5ef80> +
    +   + + NUMBER = <psycopg2._psycopg.type object at 0xd5ed88> +
    +   + + ROWID = <psycopg2._psycopg.type object at 0xd660d8> +
    +   + + STRING = <psycopg2._psycopg.type object at 0xd5ef10> +
    +   + + apilevel = '2.0' +
    +   + + k = 'DatabaseError' +
    +   + + paramstyle = 'pyformat' +
    +   + + threadsafety = 1
    + PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    + + + + + + +
    + + + + + +
    Function Details[hide private]
    +
    + +
    + +
    + + +
    +

    Binary(buffer) +

    +
      +
    + + Build an object capable to hold a bynary string value. +
    +
    Returns:
    +
    +new binary object
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    Date(year, + month, + day) +

    +
      +
    + + Build an object holding a date value. +
    +
    Returns:
    +
    +new date
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    DateFromTicks(ticks) +

    +
      +
    + +

    Build an object holding a date value from the given ticks value.

    + Ticks are the number of seconds since the epoch; see the documentation + of the standard Python time module for details). +
    +
    Returns:
    +
    +new date
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    Time(hour, + minutes, + seconds, + tzinfo=None) +

    +
      +
    + + Build an object holding a time value. +
    +
    Returns:
    +
    +new time
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    TimeFromTicks(ticks) +

    +
      +
    + +

    Build an object holding a time value from the given ticks value.

    + Ticks are the number of seconds since the epoch; see the documentation + of the standard Python time module for details). +
    +
    Returns:
    +
    +new time
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    Timestamp(year, + month, + day, + hour, + minutes, + seconds, + tzinfo=None) +

    +
      +
    + + Build an object holding a timestamp value. +
    +
    Returns:
    +
    +new timestamp
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    TimestampFromTicks(ticks) +

    +
      +
    + +

    Build an object holding a timestamp value from the given ticks + value.

    + Ticks are the number of seconds since the epoch; see the documentation + of the standard Python time module for details). +
    +
    Returns:
    +
    +new timestamp
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    connect(dsn, + ...) +

    +
      +
    + +
    +Create a new database connection.
    +
    +This function supports two different but equivalent sets of arguments.
    +A single data source name or ``dsn`` string can be used to specify the
    +connection parameters, as follows::
    +
    +    psycopg2.connect("dbname=xxx user=xxx ...")
    +
    +If ``dsn`` is not provided it is possible to pass the parameters as
    +keyword arguments; e.g.::
    +
    +    psycopg2.connect(database='xxx', user='xxx', ...)
    +
    +The full list of available parameters is:
    +
    +- ``dbname`` -- database name (only in 'dsn')
    +- ``database`` -- database name (only as keyword argument)
    +- ``host`` -- host address (defaults to UNIX socket if not provided)
    +- ``port`` -- port number (defaults to 5432 if not provided)
    +- ``user`` -- user name used to authenticate
    +- ``password`` -- password used to authenticate
    +- ``sslmode`` -- SSL mode (see PostgreSQL documentation)
    +
    +If the ``connection_factory`` keyword argument is not provided this
    +function always return an instance of the `connection` class.
    +Else the given sub-class of `extensions.connection` will be used to
    +instantiate the connection object.
    +
    +:return: New database connection
    +:rtype: `extensions.connection`
    +
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/psycopg2-pysrc.html Index: applications/examples/static/epydoc/psycopg2-pysrc.html ================================================================== --- applications/examples/static/epydoc/psycopg2-pysrc.html +++ applications/examples/static/epydoc/psycopg2-pysrc.html @@ -0,0 +1,225 @@ + + + + + psycopg2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package psycopg2 + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Package psycopg2

    +
    + 1  """A Python driver for PostgreSQL 
    + 2   
    + 3  psycopg is a PostgreSQL_ database adapter for the Python_ programming 
    + 4  language. This is version 2, a complete rewrite of the original code to 
    + 5  provide new-style classes for connection and cursor objects and other sweet 
    + 6  candies. Like the original, psycopg 2 was written with the aim of being very 
    + 7  small and fast, and stable as a rock. 
    + 8   
    + 9  Homepage: http://initd.org/projects/psycopg2 
    +10   
    +11  .. _PostgreSQL: http://www.postgresql.org/ 
    +12  .. _Python: http://www.python.org/ 
    +13   
    +14  :Groups: 
    +15    * `Connections creation`: connect 
    +16    * `Value objects constructors`: Binary, Date, DateFromTicks, Time, 
    +17      TimeFromTicks, Timestamp, TimestampFromTicks 
    +18  """ 
    +19  # psycopg/__init__.py - initialization of the psycopg module 
    +20  # 
    +21  # Copyright (C) 2003-2004 Federico Di Gregorio  <fog@debian.org> 
    +22  # 
    +23  # This program is free software; you can redistribute it and/or modify 
    +24  # it under the terms of the GNU General Public License as published by the 
    +25  # Free Software Foundation; either version 2, or (at your option) any later 
    +26  # version. 
    +27  # 
    +28  # This program is distributed in the hope that it will be useful, but 
    +29  # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY 
    +30  # or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License 
    +31  # for more details. 
    +32   
    +33  # Import modules needed by _psycopg to allow tools like py2exe to do 
    +34  # their work without bothering about the module dependencies. 
    +35  #  
    +36  # TODO: we should probably use the Warnings framework to signal a missing 
    +37  # module instead of raising an exception (in case we're running a thin 
    +38  # embedded Python or something even more devious.) 
    +39   
    +40  import sys, warnings 
    +41  if sys.version_info[0] >= 2 and sys.version_info[1] >= 3: 
    +42      try: 
    +43          import datetime as _psycopg_needs_datetime 
    +44      except: 
    +45          warnings.warn( 
    +46              "can't import datetime module probably needed by _psycopg", 
    +47              RuntimeWarning) 
    +48  if sys.version_info[0] >= 2 and sys.version_info[1] >= 4: 
    +49      try: 
    +50          import decimal as _psycopg_needs_decimal 
    +51      except: 
    +52          warnings.warn( 
    +53              "can't import decimal module probably needed by _psycopg", 
    +54              RuntimeWarning) 
    +55  from psycopg2 import tz 
    +56  del sys, warnings 
    +57   
    +58  # Import the DBAPI-2.0 stuff into top-level module. 
    +59   
    +60  from _psycopg import BINARY, NUMBER, STRING, DATETIME, ROWID 
    +61   
    +62  from _psycopg import Binary, Date, Time, Timestamp 
    +63  from _psycopg import DateFromTicks, TimeFromTicks, TimestampFromTicks 
    +64   
    +65  from _psycopg import Error, Warning, DataError, DatabaseError, ProgrammingError 
    +66  from _psycopg import IntegrityError, InterfaceError, InternalError 
    +67  from _psycopg import NotSupportedError, OperationalError 
    +68   
    +69  from _psycopg import connect, apilevel, threadsafety, paramstyle 
    +70  from _psycopg import __version__ 
    +71   
    +72  __all__ = [ k for k in locals().keys() if not k.startswith('_') ] 
    +73   
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/psycopg2.DataError-class.html Index: applications/examples/static/epydoc/psycopg2.DataError-class.html ================================================================== --- applications/examples/static/epydoc/psycopg2.DataError-class.html +++ applications/examples/static/epydoc/psycopg2.DataError-class.html @@ -0,0 +1,204 @@ + + + + + psycopg2.DataError + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + psycopg2 :: + DataError :: + Class DataError + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class DataError

    source code

    +
    +              object --+                    
    +                       |                    
    +exceptions.BaseException --+                
    +                           |                
    +        exceptions.Exception --+            
    +                               |            
    +        exceptions.StandardError --+        
    +                                   |        
    +                               Error --+    
    +                                       |    
    +                           DatabaseError --+
    +                                           |
    +                                          DataError
    +
    + +
    +Error related to problems with the processed data.

    + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from exceptions.StandardError: + __init__, + __new__ +

    +

    Inherited from exceptions.BaseException: + __delattr__, + __getattribute__, + __getitem__, + __getslice__, + __reduce__, + __repr__, + __setattr__, + __setstate__, + __str__ +

    +

    Inherited from object: + __hash__, + __reduce_ex__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from exceptions.BaseException: + args, + message +

    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/psycopg2.DatabaseError-class.html Index: applications/examples/static/epydoc/psycopg2.DatabaseError-class.html ================================================================== --- applications/examples/static/epydoc/psycopg2.DatabaseError-class.html +++ applications/examples/static/epydoc/psycopg2.DatabaseError-class.html @@ -0,0 +1,212 @@ + + + + + psycopg2.DatabaseError + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + psycopg2 :: + DatabaseError :: + Class DatabaseError + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class DatabaseError

    source code

    +
    +              object --+                
    +                       |                
    +exceptions.BaseException --+            
    +                           |            
    +        exceptions.Exception --+        
    +                               |        
    +        exceptions.StandardError --+    
    +                                   |    
    +                               Error --+
    +                                       |
    +                                      DatabaseError
    +
    + +
    Known Subclasses:
    +
    + DataError, + IntegrityError, + InternalError, + NotSupportedError, + OperationalError, + ProgrammingError +
    + +
    +Error related to the database engine.

    + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from exceptions.StandardError: + __init__, + __new__ +

    +

    Inherited from exceptions.BaseException: + __delattr__, + __getattribute__, + __getitem__, + __getslice__, + __reduce__, + __repr__, + __setattr__, + __setstate__, + __str__ +

    +

    Inherited from object: + __hash__, + __reduce_ex__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from exceptions.BaseException: + args, + message +

    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/psycopg2.Error-class.html Index: applications/examples/static/epydoc/psycopg2.Error-class.html ================================================================== --- applications/examples/static/epydoc/psycopg2.Error-class.html +++ applications/examples/static/epydoc/psycopg2.Error-class.html @@ -0,0 +1,206 @@ + + + + + psycopg2.Error + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + psycopg2 :: + Error :: + Class Error + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class Error

    source code

    +
    +              object --+            
    +                       |            
    +exceptions.BaseException --+        
    +                           |        
    +        exceptions.Exception --+    
    +                               |    
    +        exceptions.StandardError --+
    +                                   |
    +                                  Error
    +
    + +
    Known Subclasses:
    +
    + DatabaseError, + InterfaceError +
    + +
    +Base class for error exceptions.

    + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from exceptions.StandardError: + __init__, + __new__ +

    +

    Inherited from exceptions.BaseException: + __delattr__, + __getattribute__, + __getitem__, + __getslice__, + __reduce__, + __repr__, + __setattr__, + __setstate__, + __str__ +

    +

    Inherited from object: + __hash__, + __reduce_ex__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from exceptions.BaseException: + args, + message +

    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/psycopg2.IntegrityError-class.html Index: applications/examples/static/epydoc/psycopg2.IntegrityError-class.html ================================================================== --- applications/examples/static/epydoc/psycopg2.IntegrityError-class.html +++ applications/examples/static/epydoc/psycopg2.IntegrityError-class.html @@ -0,0 +1,204 @@ + + + + + psycopg2.IntegrityError + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + psycopg2 :: + IntegrityError :: + Class IntegrityError + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class IntegrityError

    source code

    +
    +              object --+                    
    +                       |                    
    +exceptions.BaseException --+                
    +                           |                
    +        exceptions.Exception --+            
    +                               |            
    +        exceptions.StandardError --+        
    +                                   |        
    +                               Error --+    
    +                                       |    
    +                           DatabaseError --+
    +                                           |
    +                                          IntegrityError
    +
    + +
    +Error related to database integrity.

    + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from exceptions.StandardError: + __init__, + __new__ +

    +

    Inherited from exceptions.BaseException: + __delattr__, + __getattribute__, + __getitem__, + __getslice__, + __reduce__, + __repr__, + __setattr__, + __setstate__, + __str__ +

    +

    Inherited from object: + __hash__, + __reduce_ex__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from exceptions.BaseException: + args, + message +

    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/psycopg2.InterfaceError-class.html Index: applications/examples/static/epydoc/psycopg2.InterfaceError-class.html ================================================================== --- applications/examples/static/epydoc/psycopg2.InterfaceError-class.html +++ applications/examples/static/epydoc/psycopg2.InterfaceError-class.html @@ -0,0 +1,202 @@ + + + + + psycopg2.InterfaceError + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + psycopg2 :: + InterfaceError :: + Class InterfaceError + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class InterfaceError

    source code

    +
    +              object --+                
    +                       |                
    +exceptions.BaseException --+            
    +                           |            
    +        exceptions.Exception --+        
    +                               |        
    +        exceptions.StandardError --+    
    +                                   |    
    +                               Error --+
    +                                       |
    +                                      InterfaceError
    +
    + +
    +Error related to the database interface.

    + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from exceptions.StandardError: + __init__, + __new__ +

    +

    Inherited from exceptions.BaseException: + __delattr__, + __getattribute__, + __getitem__, + __getslice__, + __reduce__, + __repr__, + __setattr__, + __setstate__, + __str__ +

    +

    Inherited from object: + __hash__, + __reduce_ex__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from exceptions.BaseException: + args, + message +

    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/psycopg2.InternalError-class.html Index: applications/examples/static/epydoc/psycopg2.InternalError-class.html ================================================================== --- applications/examples/static/epydoc/psycopg2.InternalError-class.html +++ applications/examples/static/epydoc/psycopg2.InternalError-class.html @@ -0,0 +1,204 @@ + + + + + psycopg2.InternalError + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + psycopg2 :: + InternalError :: + Class InternalError + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class InternalError

    source code

    +
    +              object --+                    
    +                       |                    
    +exceptions.BaseException --+                
    +                           |                
    +        exceptions.Exception --+            
    +                               |            
    +        exceptions.StandardError --+        
    +                                   |        
    +                               Error --+    
    +                                       |    
    +                           DatabaseError --+
    +                                           |
    +                                          InternalError
    +
    + +
    +The database encountered an internal error.

    + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from exceptions.StandardError: + __init__, + __new__ +

    +

    Inherited from exceptions.BaseException: + __delattr__, + __getattribute__, + __getitem__, + __getslice__, + __reduce__, + __repr__, + __setattr__, + __setstate__, + __str__ +

    +

    Inherited from object: + __hash__, + __reduce_ex__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from exceptions.BaseException: + args, + message +

    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/psycopg2.NotSupportedError-class.html Index: applications/examples/static/epydoc/psycopg2.NotSupportedError-class.html ================================================================== --- applications/examples/static/epydoc/psycopg2.NotSupportedError-class.html +++ applications/examples/static/epydoc/psycopg2.NotSupportedError-class.html @@ -0,0 +1,204 @@ + + + + + psycopg2.NotSupportedError + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + psycopg2 :: + NotSupportedError :: + Class NotSupportedError + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class NotSupportedError

    source code

    +
    +              object --+                    
    +                       |                    
    +exceptions.BaseException --+                
    +                           |                
    +        exceptions.Exception --+            
    +                               |            
    +        exceptions.StandardError --+        
    +                                   |        
    +                               Error --+    
    +                                       |    
    +                           DatabaseError --+
    +                                           |
    +                                          NotSupportedError
    +
    + +
    +A not supported datbase API was called.

    + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from exceptions.StandardError: + __init__, + __new__ +

    +

    Inherited from exceptions.BaseException: + __delattr__, + __getattribute__, + __getitem__, + __getslice__, + __reduce__, + __repr__, + __setattr__, + __setstate__, + __str__ +

    +

    Inherited from object: + __hash__, + __reduce_ex__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from exceptions.BaseException: + args, + message +

    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/psycopg2.OperationalError-class.html Index: applications/examples/static/epydoc/psycopg2.OperationalError-class.html ================================================================== --- applications/examples/static/epydoc/psycopg2.OperationalError-class.html +++ applications/examples/static/epydoc/psycopg2.OperationalError-class.html @@ -0,0 +1,205 @@ + + + + + psycopg2.OperationalError + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + psycopg2 :: + OperationalError :: + Class OperationalError + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class OperationalError

    source code

    +
    +              object --+                    
    +                       |                    
    +exceptions.BaseException --+                
    +                           |                
    +        exceptions.Exception --+            
    +                               |            
    +        exceptions.StandardError --+        
    +                                   |        
    +                               Error --+    
    +                                       |    
    +                           DatabaseError --+
    +                                           |
    +                                          OperationalError
    +
    + +
    +Error related to database operation (disconnect, memory allocation + etc).

    + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from exceptions.StandardError: + __init__, + __new__ +

    +

    Inherited from exceptions.BaseException: + __delattr__, + __getattribute__, + __getitem__, + __getslice__, + __reduce__, + __repr__, + __setattr__, + __setstate__, + __str__ +

    +

    Inherited from object: + __hash__, + __reduce_ex__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from exceptions.BaseException: + args, + message +

    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/psycopg2.ProgrammingError-class.html Index: applications/examples/static/epydoc/psycopg2.ProgrammingError-class.html ================================================================== --- applications/examples/static/epydoc/psycopg2.ProgrammingError-class.html +++ applications/examples/static/epydoc/psycopg2.ProgrammingError-class.html @@ -0,0 +1,205 @@ + + + + + psycopg2.ProgrammingError + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + psycopg2 :: + ProgrammingError :: + Class ProgrammingError + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class ProgrammingError

    source code

    +
    +              object --+                    
    +                       |                    
    +exceptions.BaseException --+                
    +                           |                
    +        exceptions.Exception --+            
    +                               |            
    +        exceptions.StandardError --+        
    +                                   |        
    +                               Error --+    
    +                                       |    
    +                           DatabaseError --+
    +                                           |
    +                                          ProgrammingError
    +
    + +
    +Error related to database programming (SQL error, table not found + etc).

    + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from exceptions.StandardError: + __init__, + __new__ +

    +

    Inherited from exceptions.BaseException: + __delattr__, + __getattribute__, + __getitem__, + __getslice__, + __reduce__, + __repr__, + __setattr__, + __setstate__, + __str__ +

    +

    Inherited from object: + __hash__, + __reduce_ex__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from exceptions.BaseException: + args, + message +

    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/psycopg2.Warning-class.html Index: applications/examples/static/epydoc/psycopg2.Warning-class.html ================================================================== --- applications/examples/static/epydoc/psycopg2.Warning-class.html +++ applications/examples/static/epydoc/psycopg2.Warning-class.html @@ -0,0 +1,200 @@ + + + + + psycopg2.Warning + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + psycopg2 :: + Warning :: + Class Warning + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class Warning

    source code

    +
    +              object --+            
    +                       |            
    +exceptions.BaseException --+        
    +                           |        
    +        exceptions.Exception --+    
    +                               |    
    +        exceptions.StandardError --+
    +                                   |
    +                                  Warning
    +
    + +
    +A database warning.

    + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from exceptions.StandardError: + __init__, + __new__ +

    +

    Inherited from exceptions.BaseException: + __delattr__, + __getattribute__, + __getitem__, + __getslice__, + __reduce__, + __repr__, + __setattr__, + __setstate__, + __str__ +

    +

    Inherited from object: + __hash__, + __reduce_ex__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from exceptions.BaseException: + args, + message +

    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/psycopg2.tz-module.html Index: applications/examples/static/epydoc/psycopg2.tz-module.html ================================================================== --- applications/examples/static/epydoc/psycopg2.tz-module.html +++ applications/examples/static/epydoc/psycopg2.tz-module.html @@ -0,0 +1,207 @@ + + + + + psycopg2.tz + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package psycopg2 :: + Module tz + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Module tz

    source code

    +

    tzinfo implementations for psycopg2

    + This module holds two different tzinfo implementations that can be + used as the 'tzinfo' argument to datetime constructors, directly passed + to psycopg functions or used to set the .tzinfo_factory attribute in + cursors.

    + + + + + + + + + + + + + +
    + + + + + +
    Classes[hide private]
    +
    +   + + FixedOffsetTimezone
    + Fixed offset in minutes east from UTC. +
    +   + + LocalTimezone
    + Platform idea of local timezone. +
    + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Variables[hide private]
    +
    +   + + DSTDIFF = datetime.timedelta(0, 3600) +
    +   + + DSTOFFSET = datetime.timedelta(0, 3600) +
    +   + + LOCAL = <psycopg2.tz.LocalTimezone object at 0xd4e690> +
    +   + + STDOFFSET = datetime.timedelta(0) +
    +   + + ZERO = datetime.timedelta(0) +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/psycopg2.tz-pysrc.html Index: applications/examples/static/epydoc/psycopg2.tz-pysrc.html ================================================================== --- applications/examples/static/epydoc/psycopg2.tz-pysrc.html +++ applications/examples/static/epydoc/psycopg2.tz-pysrc.html @@ -0,0 +1,221 @@ + + + + + psycopg2.tz + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package psycopg2 :: + Module tz + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Module psycopg2.tz

    +
    +  1  """tzinfo implementations for psycopg2 
    +  2   
    +  3  This module holds two different tzinfo implementations that can be used as 
    +  4  the 'tzinfo' argument to datetime constructors, directly passed to psycopg 
    +  5  functions or used to set the .tzinfo_factory attribute in cursors.  
    +  6  """ 
    +  7  # psycopg/tz.py - tzinfo implementation 
    +  8  # 
    +  9  # Copyright (C) 2003-2004 Federico Di Gregorio  <fog@debian.org> 
    + 10  # 
    + 11  # This program is free software; you can redistribute it and/or modify 
    + 12  # it under the terms of the GNU General Public License as published by the 
    + 13  # Free Software Foundation; either version 2, or (at your option) any later 
    + 14  # version. 
    + 15  # 
    + 16  # This program is distributed in the hope that it will be useful, but 
    + 17  # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY 
    + 18  # or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License 
    + 19  # for more details. 
    + 20   
    + 21  import datetime 
    + 22  import time 
    + 23   
    + 24   
    + 25  ZERO = datetime.timedelta(0) 
    + 26   
    +
    27 -class FixedOffsetTimezone(datetime.tzinfo): +
    28 """Fixed offset in minutes east from UTC. + 29 + 30 This is exactly the implementation found in Python 2.3.x documentation, + 31 with a small change to the __init__ method to allow for pickling and a + 32 default name in the form 'sHH:MM' ('s' is the sign.) + 33 """ + 34 _name = None + 35 _offset = ZERO + 36 +
    37 - def __init__(self, offset=None, name=None): +
    38 if offset is not None: + 39 self._offset = datetime.timedelta(minutes = offset) + 40 if name is not None: + 41 self._name = name +
    42 +
    43 - def utcoffset(self, dt): +
    44 return self._offset +
    45 +
    46 - def tzname(self, dt): +
    47 if self._name is not None: + 48 return self._name + 49 else: + 50 seconds = self._offset.seconds + self._offset.days * 86400 + 51 hours, seconds = divmod(seconds, 3600) + 52 minutes = seconds/60 + 53 if minutes: + 54 return "%+03d:%d" % (hours, minutes) + 55 else: + 56 return "%+03d" % hours +
    57 +
    58 - def dst(self, dt): +
    59 return ZERO +
    60 + 61 + 62 STDOFFSET = datetime.timedelta(seconds = -time.timezone) + 63 if time.daylight: + 64 DSTOFFSET = datetime.timedelta(seconds = -time.altzone) + 65 else: + 66 DSTOFFSET = STDOFFSET + 67 DSTDIFF = DSTOFFSET - STDOFFSET + 68 +
    69 -class LocalTimezone(datetime.tzinfo): +
    70 """Platform idea of local timezone. + 71 + 72 This is the exact implementation from the Pyhton 2.3 documentation. + 73 """ + 74 +
    75 - def utcoffset(self, dt): +
    76 if self._isdst(dt): + 77 return DSTOFFSET + 78 else: + 79 return STDOFFSET +
    80 +
    81 - def dst(self, dt): +
    82 if self._isdst(dt): + 83 return DSTDIFF + 84 else: + 85 return ZERO +
    86 +
    87 - def tzname(self, dt): +
    88 return time.tzname[self._isdst(dt)] +
    89 +
    90 - def _isdst(self, dt): +
    91 tt = (dt.year, dt.month, dt.day, + 92 dt.hour, dt.minute, dt.second, + 93 dt.weekday(), 0, -1) + 94 stamp = time.mktime(tt) + 95 tt = time.localtime(stamp) + 96 return tt.tm_isdst > 0 +
    97 + 98 LOCAL = LocalTimezone() + 99 +100 # TODO: pre-generate some interesting time zones? +101 +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/psycopg2.tz.FixedOffsetTimezone-class.html Index: applications/examples/static/epydoc/psycopg2.tz.FixedOffsetTimezone-class.html ================================================================== --- applications/examples/static/epydoc/psycopg2.tz.FixedOffsetTimezone-class.html +++ applications/examples/static/epydoc/psycopg2.tz.FixedOffsetTimezone-class.html @@ -0,0 +1,416 @@ + + + + + psycopg2.tz.FixedOffsetTimezone + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + psycopg2 :: + tz :: + FixedOffsetTimezone :: + Class FixedOffsetTimezone + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class FixedOffsetTimezone

    source code

    +
    +     object --+    
    +              |    
    +datetime.tzinfo --+
    +                  |
    +                 FixedOffsetTimezone
    +
    + +
    +

    Fixed offset in minutes east from UTC.

    + This is exactly the implementation found in Python 2.3.x + documentation, with a small change to the __init__ method to allow for + pickling and a default name in the form 'sHH:MM' ('s' is the sign.)

    + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + offset=1, + name=1)
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    dst(self, + dt)
    + datetime -> DST offset in minutes east of UTC.
    + source code + +
    + +
    +   + + + + + + +
    tzname(self, + dt)
    + datetime -> string name of time zone.
    + source code + +
    + +
    +   + + + + + + +
    utcoffset(self, + dt)
    + datetime -> minutes east of UTC (negative for west of UTC).
    + source code + +
    + +
    +

    Inherited from datetime.tzinfo: + __getattribute__, + __new__, + __reduce__, + fromutc +

    +

    Inherited from object: + __delattr__, + __hash__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + _name = 1
    + PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    +   + + _offset = datetime.timedelta(0) +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + offset=1, + name=1) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    dst(self, + dt) +

    +
    source code  +
    + + datetime -> DST offset in minutes east of UTC. +
    +
    Overrides: + datetime.tzinfo.dst +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    tzname(self, + dt) +

    +
    source code  +
    + + datetime -> string name of time zone. +
    +
    Overrides: + datetime.tzinfo.tzname +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    utcoffset(self, + dt) +

    +
    source code  +
    + + datetime -> minutes east of UTC (negative for west of UTC). +
    +
    Overrides: + datetime.tzinfo.utcoffset +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/psycopg2.tz.LocalTimezone-class.html Index: applications/examples/static/epydoc/psycopg2.tz.LocalTimezone-class.html ================================================================== --- applications/examples/static/epydoc/psycopg2.tz.LocalTimezone-class.html +++ applications/examples/static/epydoc/psycopg2.tz.LocalTimezone-class.html @@ -0,0 +1,353 @@ + + + + + psycopg2.tz.LocalTimezone + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + psycopg2 :: + tz :: + LocalTimezone :: + Class LocalTimezone + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class LocalTimezone

    source code

    +
    +     object --+    
    +              |    
    +datetime.tzinfo --+
    +                  |
    +                 LocalTimezone
    +
    + +
    +

    Platform idea of local timezone.

    + This is the exact implementation from the Pyhton 2.3 + documentation.

    + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    _isdst(self, + dt) + source code + +
    + +
    +   + + + + + + +
    dst(self, + dt)
    + datetime -> DST offset in minutes east of UTC.
    + source code + +
    + +
    +   + + + + + + +
    tzname(self, + dt)
    + datetime -> string name of time zone.
    + source code + +
    + +
    +   + + + + + + +
    utcoffset(self, + dt)
    + datetime -> minutes east of UTC (negative for west of UTC).
    + source code + +
    + +
    +

    Inherited from datetime.tzinfo: + __getattribute__, + __new__, + __reduce__, + fromutc +

    +

    Inherited from object: + __delattr__, + __hash__, + __init__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    dst(self, + dt) +

    +
    source code  +
    + + datetime -> DST offset in minutes east of UTC. +
    +
    Overrides: + datetime.tzinfo.dst +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    tzname(self, + dt) +

    +
    source code  +
    + + datetime -> string name of time zone. +
    +
    Overrides: + datetime.tzinfo.tzname +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    utcoffset(self, + dt) +

    +
    source code  +
    + + datetime -> minutes east of UTC (negative for west of UTC). +
    +
    Overrides: + datetime.tzinfo.utcoffset +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/redirect.html Index: applications/examples/static/epydoc/redirect.html ================================================================== --- applications/examples/static/epydoc/redirect.html +++ applications/examples/static/epydoc/redirect.html @@ -0,0 +1,38 @@ +Epydoc Redirect Page + + + + + + + + +

    Epydoc Auto-redirect page

    + +

    When javascript is enabled, this page will redirect URLs of +the form redirect.html#dotted.name to the +documentation for the object with the given fully-qualified +dotted name.

    +

     

    + + + + + ADDED applications/examples/static/epydoc/sqlite3.dbapi2-module.html Index: applications/examples/static/epydoc/sqlite3.dbapi2-module.html ================================================================== --- applications/examples/static/epydoc/sqlite3.dbapi2-module.html +++ applications/examples/static/epydoc/sqlite3.dbapi2-module.html @@ -0,0 +1,589 @@ + + + + + sqlite3.dbapi2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package sqlite3 :: + Module dbapi2 + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Module dbapi2

    source code

    +

    PyMySQL: A pure-Python drop-in replacement for MySQLdb.

    +

    Copyright (c) 2010 PyMySQL contributors

    +

    Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, and + to permit persons to whom the Software is furnished to do so, subject to + the following conditions:

    +

    The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software.

    + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY + KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + USE OR OTHER DEALINGS IN THE SOFTWARE.

    + + + + + + + + + + + + + + + + +
    + + + + + +
    Functions[hide private]
    +
    +   + + + + + + +
    DateFromTicks(ticks) + source code + +
    + +
    +   + + + + + + +
    TimeFromTicks(ticks) + source code + +
    + +
    +   + + + + + + +
    TimestampFromTicks(ticks) + source code + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Variables[hide private]
    +
    +   + + PARSE_COLNAMES = 1
    + PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    +   + + PARSE_DECLTYPES = 1
    + PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    +   + + SQLITE_ALTER_TABLE = 26 +
    +   + + SQLITE_ANALYZE = 28 +
    +   + + SQLITE_ATTACH = 24 +
    +   + + SQLITE_CREATE_INDEX = 1
    + PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    +   + + SQLITE_CREATE_TABLE = 1
    + PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    +   + + SQLITE_CREATE_TEMP_INDEX = 3 +
    +   + + SQLITE_CREATE_TEMP_TABLE = 1
    + PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    +   + + SQLITE_CREATE_TEMP_TRIGGER = 5 +
    +   + + SQLITE_CREATE_TEMP_VIEW = 6 +
    +   + + SQLITE_CREATE_TRIGGER = 7 +
    +   + + SQLITE_CREATE_VIEW = 8 +
    +   + + SQLITE_DELETE = 9 +
    +   + + SQLITE_DENY = 1
    + PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    +   + + SQLITE_DETACH = 25 +
    +   + + SQLITE_DROP_INDEX = 10 +
    +   + + SQLITE_DROP_TABLE = 11 +
    +   + + SQLITE_DROP_TEMP_INDEX = 12 +
    +   + + SQLITE_DROP_TEMP_TABLE = 13 +
    +   + + SQLITE_DROP_TEMP_TRIGGER = 14 +
    +   + + SQLITE_DROP_TEMP_VIEW = 15 +
    +   + + SQLITE_DROP_TRIGGER = 16 +
    +   + + SQLITE_DROP_VIEW = 17 +
    +   + + SQLITE_IGNORE = 1
    + PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    +   + + SQLITE_INSERT = 18 +
    +   + + SQLITE_OK = 0 +
    +   + + SQLITE_PRAGMA = 19 +
    +   + + SQLITE_READ = 20 +
    +   + + SQLITE_REINDEX = 27 +
    +   + + SQLITE_SELECT = 21 +
    +   + + SQLITE_TRANSACTION = 22 +
    +   + + SQLITE_UPDATE = 23 +
    +   + + adapters = {(<type 'datetime.date'>, <type 'sqlite3.PreparePro... +
    +   + + apilevel = '2.0' +
    +   + + converters = {'DATE': <function convert_date at 0xd25668>, 'TI... +
    +   + + paramstyle = 'qmark' +
    +   + + sqlite_version = '3.4.2' +
    +   + + sqlite_version_info = (3, 4, 2) +
    +   + + threadsafety = 1
    + PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    +   + + version = '2.3.2' +
    +   + + version_info = (2, 3, 2) +
    +   + + x = '2' +
    + + + + + + +
    + + + + + +
    Variables Details[hide private]
    +
    + +
    + +
    +

    adapters

    + +
    +
    +
    +
    Value:
    +
    +{(<type 'datetime.date'>, <type 'sqlite3.PrepareProtocol'>): <function\
    + adapt_date at 0xd25578>,
    + (<type 'datetime.datetime'>, <type 'sqlite3.PrepareProtocol'>): <func\
    +tion adapt_datetime at 0xd255f0>}
    +
    +
    +
    +
    +
    + +
    + +
    +

    converters

    + +
    +
    +
    +
    Value:
    +
    +{'DATE': <function convert_date at 0xd25668>,
    + 'TIMESTAMP': <function convert_timestamp at 0xd256e0>}
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/sqlite3.dbapi2-pysrc.html Index: applications/examples/static/epydoc/sqlite3.dbapi2-pysrc.html ================================================================== --- applications/examples/static/epydoc/sqlite3.dbapi2-pysrc.html +++ applications/examples/static/epydoc/sqlite3.dbapi2-pysrc.html @@ -0,0 +1,217 @@ + + + + + sqlite3.dbapi2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package sqlite3 :: + Module dbapi2 + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Module sqlite3.dbapi2

    +
    + 1  #-*- coding: ISO-8859-1 -*- 
    + 2  # pysqlite2/dbapi2.py: the DB-API 2.0 interface 
    + 3  # 
    + 4  # Copyright (C) 2004-2005 Gerhard Häring <gh@ghaering.de> 
    + 5  # 
    + 6  # This file is part of pysqlite. 
    + 7  # 
    + 8  # This software is provided 'as-is', without any express or implied 
    + 9  # warranty.  In no event will the authors be held liable for any damages 
    +10  # arising from the use of this software. 
    +11  # 
    +12  # Permission is granted to anyone to use this software for any purpose, 
    +13  # including commercial applications, and to alter it and redistribute it 
    +14  # freely, subject to the following restrictions: 
    +15  # 
    +16  # 1. The origin of this software must not be misrepresented; you must not 
    +17  #    claim that you wrote the original software. If you use this software 
    +18  #    in a product, an acknowledgment in the product documentation would be 
    +19  #    appreciated but is not required. 
    +20  # 2. Altered source versions must be plainly marked as such, and must not be 
    +21  #    misrepresented as being the original software. 
    +22  # 3. This notice may not be removed or altered from any source distribution. 
    +23   
    +24  import datetime 
    +25  import time 
    +26   
    +27  from _sqlite3 import * 
    +28   
    +29  paramstyle = "qmark" 
    +30   
    +31  threadsafety = 1 
    +32   
    +33  apilevel = "2.0" 
    +34   
    +35  Date = datetime.date 
    +36   
    +37  Time = datetime.time 
    +38   
    +39  Timestamp = datetime.datetime 
    +40   
    +
    41 -def DateFromTicks(ticks): +
    42 return apply(Date, time.localtime(ticks)[:3]) +
    43 +
    44 -def TimeFromTicks(ticks): +
    45 return apply(Time, time.localtime(ticks)[3:6]) +
    46 +
    47 -def TimestampFromTicks(ticks): +
    48 return apply(Timestamp, time.localtime(ticks)[:6]) +
    49 +50 version_info = tuple([int(x) for x in version.split(".")]) +51 sqlite_version_info = tuple([int(x) for x in sqlite_version.split(".")]) +52 +53 Binary = buffer +54 +
    56 def adapt_date(val): +57 return val.isoformat() +
    58 +59 def adapt_datetime(val): +60 return val.isoformat(" ") +61 +62 def convert_date(val): +63 return datetime.date(*map(int, val.split("-"))) +64 +65 def convert_timestamp(val): +66 datepart, timepart = val.split(" ") +67 year, month, day = map(int, datepart.split("-")) +68 timepart_full = timepart.split(".") +69 hours, minutes, seconds = map(int, timepart_full[0].split(":")) +70 if len(timepart_full) == 2: +71 microseconds = int(float("0." + timepart_full[1]) * 1000000) +72 else: +73 microseconds = 0 +74 +75 val = datetime.datetime(year, month, day, hours, minutes, seconds, microseconds) +76 return val +77 +78 +79 register_adapter(datetime.date, adapt_date) +80 register_adapter(datetime.datetime, adapt_datetime) +81 register_converter("date", convert_date) +82 register_converter("timestamp", convert_timestamp) +83 +84 register_adapters_and_converters() +85 +86 # Clean up namespace +87 +88 del(register_adapters_and_converters) +89 +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/title.png Index: applications/examples/static/epydoc/title.png ================================================================== --- applications/examples/static/epydoc/title.png +++ applications/examples/static/epydoc/title.png cannot compute difference between binary files ADDED applications/examples/static/epydoc/toc-everything.html Index: applications/examples/static/epydoc/toc-everything.html ================================================================== --- applications/examples/static/epydoc/toc-everything.html +++ applications/examples/static/epydoc/toc-everything.html @@ -0,0 +1,1868 @@ + + + + + Everything + + + + + +

    Everything

    +
    +

    All Classes

    + datetime.date
    datetime.datetime
    datetime.time
    exceptions.Exception
    psycopg2.DataError
    psycopg2.DatabaseError
    psycopg2.Error
    psycopg2.IntegrityError
    psycopg2.InterfaceError
    psycopg2.InternalError
    psycopg2.NotSupportedError
    psycopg2.OperationalError
    psycopg2.ProgrammingError
    psycopg2.Warning
    psycopg2.tz.FixedOffsetTimezone
    psycopg2.tz.LocalTimezone
    web2py.gluon.cache.Cache
    + + + web2py.gluon.compileapp.LoadFactory
    web2py.gluon.compileapp.mybuiltin
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + web2py.gluon.debug.Pipe
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + web2py.gluon.main.HttpServer
    web2py.gluon.newcron.Token
    web2py.gluon.newcron.cronlauncher
    web2py.gluon.newcron.extcron
    web2py.gluon.newcron.hardcron
    web2py.gluon.newcron.softcron
    web2py.gluon.restricted.RestrictedError
    web2py.gluon.restricted.TicketStorage
    + + + + + + + + + web2py.gluon.rocket.NullHandler
    web2py.gluon.rocket.Rocket
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + web2py.gluon.template.BlockNode
    web2py.gluon.template.Content
    web2py.gluon.template.Node
    web2py.gluon.template.SuperNode
    web2py.gluon.template.TemplateParser
    web2py.gluon.tools.Auth
    web2py.gluon.tools.Crud
    web2py.gluon.tools.Mail
    web2py.gluon.tools.Mail.Attachment
    web2py.gluon.tools.PluginManager
    web2py.gluon.tools.Recaptcha
    web2py.gluon.tools.Service
    web2py.gluon.tools.Service.JsonRpcException
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + web2py.gluon.widget.IO
    web2py.gluon.widget.web2pyDialog
    + +

    All Functions

    + web2py.gluon.admin.add_path_first
    web2py.gluon.admin.apath
    web2py.gluon.admin.app_cleanup
    web2py.gluon.admin.app_compile
    web2py.gluon.admin.app_create
    web2py.gluon.admin.app_install
    web2py.gluon.admin.app_pack
    web2py.gluon.admin.app_pack_compiled
    web2py.gluon.admin.app_uninstall
    web2py.gluon.admin.check_new_version
    web2py.gluon.admin.create_missing_app_folders
    web2py.gluon.admin.create_missing_folders
    web2py.gluon.admin.plugin_install
    web2py.gluon.admin.plugin_pack
    web2py.gluon.admin.unzip
    web2py.gluon.admin.upgrade
    + web2py.gluon.compileapp.build_environment
    web2py.gluon.compileapp.compile_application
    web2py.gluon.compileapp.compile_controllers
    web2py.gluon.compileapp.compile_models
    web2py.gluon.compileapp.compile_views
    web2py.gluon.compileapp.local_import_aux
    web2py.gluon.compileapp.read_pyc
    web2py.gluon.compileapp.remove_compiled_application
    web2py.gluon.compileapp.run_controller_in
    web2py.gluon.compileapp.run_models_in
    web2py.gluon.compileapp.run_view_in
    web2py.gluon.compileapp.save_pyc
    web2py.gluon.compileapp.test
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + web2py.gluon.custom_import.custom_import_install
    web2py.gluon.custom_import.is_tracking_changes
    web2py.gluon.custom_import.track_changes
    + + + + + + + + + + + + + + + + + + web2py.gluon.debug.communicate
    web2py.gluon.debug.set_trace
    web2py.gluon.debug.stop_trace
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + web2py.gluon.main.appfactory
    + + + + + web2py.gluon.main.save_password
    + + web2py.gluon.main.wsgibase
    web2py.gluon.newcron.crondance
    web2py.gluon.newcron.parsecronline
    web2py.gluon.newcron.rangetolist
    web2py.gluon.newcron.stopcron
    + + web2py.gluon.restricted.compile2
    web2py.gluon.restricted.restricted
    + + + + + + + + + + + + + + + + + + + + web2py.gluon.rocket.CherryPyWSGIServer
    + web2py.gluon.rocket.b
    + + + web2py.gluon.rocket.u
    + + + + + + + + web2py.gluon.shell.die
    web2py.gluon.shell.env
    web2py.gluon.shell.exec_environment
    web2py.gluon.shell.exec_pythonrc
    web2py.gluon.shell.execute_from_command_line
    web2py.gluon.shell.get_usage
    web2py.gluon.shell.parse_path_info
    web2py.gluon.shell.run
    web2py.gluon.shell.test
    + + + + + + + web2py.gluon.template.get_parsed
    web2py.gluon.template.parse_template
    web2py.gluon.template.render
    + + + + + web2py.gluon.tools.fetch
    web2py.gluon.tools.geocode
    web2py.gluon.tools.prettydate
    + + + + + + + + + + + + + + + + web2py.gluon.widget.console
    web2py.gluon.widget.presentation
    web2py.gluon.widget.start
    web2py.gluon.widget.start_browser
    web2py.gluon.widget.try_start_browser
    web2py.gluon.winservice.web2py_windows_service_handler
    +

    All Variables

    + psycopg2.BINARY
    psycopg2.DATETIME
    psycopg2.NUMBER
    psycopg2.ROWID
    psycopg2.STRING
    psycopg2.apilevel
    psycopg2.k
    psycopg2.paramstyle
    psycopg2.threadsafety
    psycopg2.tz.DSTDIFF
    psycopg2.tz.DSTOFFSET
    psycopg2.tz.LOCAL
    psycopg2.tz.STDOFFSET
    psycopg2.tz.ZERO
    sqlite3.dbapi2.PARSE_COLNAMES
    sqlite3.dbapi2.PARSE_DECLTYPES
    sqlite3.dbapi2.SQLITE_ALTER_TABLE
    sqlite3.dbapi2.SQLITE_ANALYZE
    sqlite3.dbapi2.SQLITE_ATTACH
    sqlite3.dbapi2.SQLITE_CREATE_INDEX
    sqlite3.dbapi2.SQLITE_CREATE_TABLE
    sqlite3.dbapi2.SQLITE_CREATE_TEMP_INDEX
    sqlite3.dbapi2.SQLITE_CREATE_TEMP_TABLE
    sqlite3.dbapi2.SQLITE_CREATE_TEMP_TRIGGER
    sqlite3.dbapi2.SQLITE_CREATE_TEMP_VIEW
    sqlite3.dbapi2.SQLITE_CREATE_TRIGGER
    sqlite3.dbapi2.SQLITE_CREATE_VIEW
    sqlite3.dbapi2.SQLITE_DELETE
    sqlite3.dbapi2.SQLITE_DENY
    sqlite3.dbapi2.SQLITE_DETACH
    sqlite3.dbapi2.SQLITE_DROP_INDEX
    sqlite3.dbapi2.SQLITE_DROP_TABLE
    sqlite3.dbapi2.SQLITE_DROP_TEMP_INDEX
    sqlite3.dbapi2.SQLITE_DROP_TEMP_TABLE
    sqlite3.dbapi2.SQLITE_DROP_TEMP_TRIGGER
    sqlite3.dbapi2.SQLITE_DROP_TEMP_VIEW
    sqlite3.dbapi2.SQLITE_DROP_TRIGGER
    sqlite3.dbapi2.SQLITE_DROP_VIEW
    sqlite3.dbapi2.SQLITE_IGNORE
    sqlite3.dbapi2.SQLITE_INSERT
    sqlite3.dbapi2.SQLITE_OK
    sqlite3.dbapi2.SQLITE_PRAGMA
    sqlite3.dbapi2.SQLITE_READ
    sqlite3.dbapi2.SQLITE_REINDEX
    sqlite3.dbapi2.SQLITE_SELECT
    sqlite3.dbapi2.SQLITE_TRANSACTION
    sqlite3.dbapi2.SQLITE_UPDATE
    sqlite3.dbapi2.adapters
    sqlite3.dbapi2.apilevel
    sqlite3.dbapi2.converters
    sqlite3.dbapi2.paramstyle
    sqlite3.dbapi2.sqlite_version
    sqlite3.dbapi2.sqlite_version_info
    sqlite3.dbapi2.threadsafety
    sqlite3.dbapi2.version
    sqlite3.dbapi2.version_info
    sqlite3.dbapi2.x
    web2py.gluon.ON
    web2py.gluon.TAG
    + + + + web2py.gluon.compileapp.TEST_CODE
    web2py.gluon.compileapp.is_gae
    web2py.gluon.compileapp.is_jython
    web2py.gluon.compileapp.logger
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + web2py.gluon.current
    + + + + + + + + + + + + + + + + + + + + + + + + web2py.gluon.debug.debugger
    web2py.gluon.debug.logger
    web2py.gluon.debug.pipe_in
    web2py.gluon.debug.pipe_out
    + + + + + + + web2py.gluon.import_all.alert_dependency
    web2py.gluon.import_all.base_modules
    web2py.gluon.import_all.candidate
    web2py.gluon.import_all.contributed_modules
    web2py.gluon.import_all.dirs
    web2py.gluon.import_all.files
    web2py.gluon.import_all.module
    web2py.gluon.import_all.name
    web2py.gluon.import_all.py26_deprecated
    web2py.gluon.import_all.py27_deprecated
    web2py.gluon.import_all.python_version
    web2py.gluon.import_all.root
    + + + + + + + + + + + + + + + + web2py.gluon.newcron.logger
    + + + + + + web2py.gluon.reserved_sql_keywords.ADAPTERS
    web2py.gluon.reserved_sql_keywords.COMMON
    web2py.gluon.reserved_sql_keywords.DB2
    web2py.gluon.reserved_sql_keywords.FIREBIRD
    web2py.gluon.reserved_sql_keywords.FIREBIRD_NONRESERVED
    web2py.gluon.reserved_sql_keywords.INFORMIX
    web2py.gluon.reserved_sql_keywords.INGRES
    web2py.gluon.reserved_sql_keywords.JDBCPOSTGRESQL
    web2py.gluon.reserved_sql_keywords.JDBCSQLITE
    web2py.gluon.reserved_sql_keywords.MSSQL
    web2py.gluon.reserved_sql_keywords.MYSQL
    web2py.gluon.reserved_sql_keywords.ORACLE
    web2py.gluon.reserved_sql_keywords.POSTGRESQL
    web2py.gluon.reserved_sql_keywords.POSTGRESQL_NONRESERVED
    web2py.gluon.reserved_sql_keywords.SQLITE
    web2py.gluon.reserved_sql_keywords.__author__
    + + + + + + + + + + + + + + + web2py.gluon.rocket.BUF_SIZE
    web2py.gluon.rocket.DEFAULTS
    + + + + + web2py.gluon.rocket.HTTP_SERVER_SOFTWARE
    web2py.gluon.rocket.IGNORE_ERRORS_ON_CLOSE
    web2py.gluon.rocket.IS_JYTHON
    + + web2py.gluon.rocket.PY3K
    + web2py.gluon.rocket.SERVER_NAME
    web2py.gluon.rocket.SERVER_SOFTWARE
    + + web2py.gluon.rocket.VERSION
    + + + + + + + web2py.gluon.shell.logger
    web2py.gluon.sql.drivers
    + + + + + + + + + + + + + + + + + + + + + + web2py.gluon.widget.ProgramAuthor
    web2py.gluon.widget.ProgramInfo
    web2py.gluon.widget.ProgramName
    web2py.gluon.widget.ProgramVersion
    web2py.gluon.widget.logger
    web2py.gluon.widget.msg

    +[hide private] + + + + + ADDED applications/examples/static/epydoc/toc-psycopg2-module.html Index: applications/examples/static/epydoc/toc-psycopg2-module.html ================================================================== --- applications/examples/static/epydoc/toc-psycopg2-module.html +++ applications/examples/static/epydoc/toc-psycopg2-module.html @@ -0,0 +1,60 @@ + + + + + psycopg2 + + + + + +

    Module psycopg2

    +
    +

    Classes

    + DataError
    DatabaseError
    Error
    IntegrityError
    InterfaceError
    InternalError
    NotSupportedError
    OperationalError
    ProgrammingError
    Warning

    Functions

    + Binary
    Date
    DateFromTicks
    Time
    TimeFromTicks
    Timestamp
    TimestampFromTicks
    connect

    Variables

    + BINARY
    DATETIME
    NUMBER
    ROWID
    STRING
    apilevel
    k
    paramstyle
    threadsafety

    +[hide private] + + + + + ADDED applications/examples/static/epydoc/toc-psycopg2.tz-module.html Index: applications/examples/static/epydoc/toc-psycopg2.tz-module.html ================================================================== --- applications/examples/static/epydoc/toc-psycopg2.tz-module.html +++ applications/examples/static/epydoc/toc-psycopg2.tz-module.html @@ -0,0 +1,39 @@ + + + + + tz + + + + + +

    Module tz

    +
    +

    Classes

    + FixedOffsetTimezone
    LocalTimezone

    Variables

    + DSTDIFF
    DSTOFFSET
    LOCAL
    STDOFFSET
    ZERO

    +[hide private] + + + + + ADDED applications/examples/static/epydoc/toc-sqlite3.dbapi2-module.html Index: applications/examples/static/epydoc/toc-sqlite3.dbapi2-module.html ================================================================== --- applications/examples/static/epydoc/toc-sqlite3.dbapi2-module.html +++ applications/examples/static/epydoc/toc-sqlite3.dbapi2-module.html @@ -0,0 +1,78 @@ + + + + + dbapi2 + + + + + +

    Module dbapi2

    +
    +

    Functions

    + DateFromTicks
    TimeFromTicks
    TimestampFromTicks

    Variables

    + PARSE_COLNAMES
    PARSE_DECLTYPES
    SQLITE_ALTER_TABLE
    SQLITE_ANALYZE
    SQLITE_ATTACH
    SQLITE_CREATE_INDEX
    SQLITE_CREATE_TABLE
    SQLITE_CREATE_TEMP_INDEX
    SQLITE_CREATE_TEMP_TABLE
    SQLITE_CREATE_TEMP_TRIGGER
    SQLITE_CREATE_TEMP_VIEW
    SQLITE_CREATE_TRIGGER
    SQLITE_CREATE_VIEW
    SQLITE_DELETE
    SQLITE_DENY
    SQLITE_DETACH
    SQLITE_DROP_INDEX
    SQLITE_DROP_TABLE
    SQLITE_DROP_TEMP_INDEX
    SQLITE_DROP_TEMP_TABLE
    SQLITE_DROP_TEMP_TRIGGER
    SQLITE_DROP_TEMP_VIEW
    SQLITE_DROP_TRIGGER
    SQLITE_DROP_VIEW
    SQLITE_IGNORE
    SQLITE_INSERT
    SQLITE_OK
    SQLITE_PRAGMA
    SQLITE_READ
    SQLITE_REINDEX
    SQLITE_SELECT
    SQLITE_TRANSACTION
    SQLITE_UPDATE
    adapters
    apilevel
    converters
    paramstyle
    sqlite_version
    sqlite_version_info
    threadsafety
    version
    version_info
    x

    +[hide private] + + + + + ADDED applications/examples/static/epydoc/toc-web2py.gluon-module.html Index: applications/examples/static/epydoc/toc-web2py.gluon-module.html ================================================================== --- applications/examples/static/epydoc/toc-web2py.gluon-module.html +++ applications/examples/static/epydoc/toc-web2py.gluon-module.html @@ -0,0 +1,131 @@ + + + + + gluon + + + + + +

    Module gluon

    +
    +

    Classes

    + A
    B
    BEAUTIFY
    BODY
    BR
    CAT
    CENTER
    CLEANUP
    CODE
    CRYPT
    DAL
    DIV
    EM
    EMBED
    FIELDSET
    FORM
    Field
    H1
    H2
    H3
    H4
    H5
    H6
    HEAD
    HR
    HTML
    HTTP
    I
    IFRAME
    IMG
    INPUT
    IS_ALPHANUMERIC
    IS_DATE
    IS_DATETIME
    IS_DATETIME_IN_RANGE
    IS_DATE_IN_RANGE
    IS_DECIMAL_IN_RANGE
    IS_EMAIL
    IS_EMPTY_OR
    IS_EQUAL_TO
    IS_EXPR
    IS_FLOAT_IN_RANGE
    IS_IMAGE
    IS_INT_IN_RANGE
    IS_IN_DB
    IS_IN_SET
    IS_IPV4
    IS_LENGTH
    IS_LIST_OF
    IS_LOWER
    IS_MATCH
    IS_NOT_EMPTY
    IS_NOT_IN_DB
    IS_NULL_OR
    IS_SLUG
    IS_STRONG
    IS_TIME
    IS_UPLOAD_FILENAME
    IS_UPPER
    IS_URL
    LABEL
    LEGEND
    LI
    LINK
    MARKMIN
    MENU
    META
    OBJECT
    OL
    OPTGROUP
    OPTION
    P
    PRE
    SCRIPT
    SELECT
    SPAN
    SQLFORM
    SQLTABLE
    STYLE
    TABLE
    TBODY
    TD
    TEXTAREA
    TFOOT
    TH
    THEAD
    TITLE
    TR
    TT
    UL
    XHTML
    XML

    Functions

    + URL
    embed64
    redirect

    Variables

    + ON
    TAG
    current

    +[hide private] + + + + + ADDED applications/examples/static/epydoc/toc-web2py.gluon.admin-module.html Index: applications/examples/static/epydoc/toc-web2py.gluon.admin-module.html ================================================================== --- applications/examples/static/epydoc/toc-web2py.gluon.admin-module.html +++ applications/examples/static/epydoc/toc-web2py.gluon.admin-module.html @@ -0,0 +1,47 @@ + + + + + admin + + + + + +

    Module admin

    +
    +

    Functions

    + add_path_first
    apath
    app_cleanup
    app_compile
    app_create
    app_install
    app_pack
    app_pack_compiled
    app_uninstall
    check_new_version
    create_missing_app_folders
    create_missing_folders
    plugin_install
    plugin_pack
    unzip
    upgrade

    +[hide private] + + + + + ADDED applications/examples/static/epydoc/toc-web2py.gluon.cfs-module.html Index: applications/examples/static/epydoc/toc-web2py.gluon.cfs-module.html ================================================================== --- applications/examples/static/epydoc/toc-web2py.gluon.cfs-module.html +++ applications/examples/static/epydoc/toc-web2py.gluon.cfs-module.html @@ -0,0 +1,41 @@ + + + + + cfs + + + + + +

    Module cfs

    +
    +

    Functions

    +
    + getcfs
    +

    Variables

    +
    + cfs
    + +
    +[hide private] + + + + + ADDED applications/examples/static/epydoc/toc-web2py.gluon.compileapp-module.html Index: applications/examples/static/epydoc/toc-web2py.gluon.compileapp-module.html ================================================================== --- applications/examples/static/epydoc/toc-web2py.gluon.compileapp-module.html +++ applications/examples/static/epydoc/toc-web2py.gluon.compileapp-module.html @@ -0,0 +1,52 @@ + + + + + compileapp + + + + + +

    Module compileapp

    +
    +

    Classes

    + LoadFactory
    mybuiltin

    Functions

    + build_environment
    compile_application
    compile_controllers
    compile_models
    compile_views
    local_import_aux
    read_pyc
    remove_compiled_application
    run_controller_in
    run_models_in
    run_view_in
    save_pyc
    test

    Variables

    + TEST_CODE
    is_gae
    is_jython
    logger

    +[hide private] + + + + + ADDED applications/examples/static/epydoc/toc-web2py.gluon.contenttype-module.html Index: applications/examples/static/epydoc/toc-web2py.gluon.contenttype-module.html ================================================================== --- applications/examples/static/epydoc/toc-web2py.gluon.contenttype-module.html +++ applications/examples/static/epydoc/toc-web2py.gluon.contenttype-module.html @@ -0,0 +1,38 @@ + + + + + contenttype + + + + + +

    Module contenttype

    +
    +

    Functions

    + +

    Variables

    + +
    +[hide private] + + + + + ADDED applications/examples/static/epydoc/toc-web2py.gluon.contrib.pymysql-module.html Index: applications/examples/static/epydoc/toc-web2py.gluon.contrib.pymysql-module.html ================================================================== --- applications/examples/static/epydoc/toc-web2py.gluon.contrib.pymysql-module.html +++ applications/examples/static/epydoc/toc-web2py.gluon.contrib.pymysql-module.html @@ -0,0 +1,156 @@ + + + + + pymysql + + + + + +

    Module pymysql

    +
    +

    Classes

    + + + +
    + Date
    +
    + Error
    + + + + + + +
    + Time
    + + +

    Functions

    +
    + Binary
    + + + + + + + + + + + + +

    Variables

    +
    + BINARY
    +
    + DATE
    + +
    + NULL
    +
    + NUMBER
    +
    + ROWID
    +
    + STRING
    +
    + TIME
    + + + + + + +
    +[hide private] + + + + + ADDED applications/examples/static/epydoc/toc-web2py.gluon.contrib.pymysql.constants-module.html Index: applications/examples/static/epydoc/toc-web2py.gluon.contrib.pymysql.constants-module.html ================================================================== --- applications/examples/static/epydoc/toc-web2py.gluon.contrib.pymysql.constants-module.html +++ applications/examples/static/epydoc/toc-web2py.gluon.contrib.pymysql.constants-module.html @@ -0,0 +1,30 @@ + + + + + constants + + + + + +

    Module constants

    +
    +
    +[hide private] + + + + + ADDED applications/examples/static/epydoc/toc-web2py.gluon.contrib.pymysql.constants.FIELD_TYPE-module.html Index: applications/examples/static/epydoc/toc-web2py.gluon.contrib.pymysql.constants.FIELD_TYPE-module.html ================================================================== --- applications/examples/static/epydoc/toc-web2py.gluon.contrib.pymysql.constants.FIELD_TYPE-module.html +++ applications/examples/static/epydoc/toc-web2py.gluon.contrib.pymysql.constants.FIELD_TYPE-module.html @@ -0,0 +1,118 @@ + + + + + FIELD_TYPE + + + + + +

    Module FIELD_TYPE

    +
    +

    Variables

    +
    + BIT
    +
    + BLOB
    +
    + CHAR
    +
    + DATE
    + + +
    + DOUBLE
    +
    + ENUM
    +
    + FLOAT
    + +
    + INT24
    + +
    + LONG
    + + + + + +
    + NULL
    +
    + SET
    +
    + SHORT
    +
    + STRING
    +
    + TIME
    + +
    + TINY
    + + + +
    + YEAR
    +
    +[hide private] + + + + + ADDED applications/examples/static/epydoc/toc-web2py.gluon.contrib.pymysql.converters-module.html Index: applications/examples/static/epydoc/toc-web2py.gluon.contrib.pymysql.converters-module.html ================================================================== --- applications/examples/static/epydoc/toc-web2py.gluon.contrib.pymysql.converters-module.html +++ applications/examples/static/epydoc/toc-web2py.gluon.contrib.pymysql.converters-module.html @@ -0,0 +1,137 @@ + + + + + converters + + + + + +

    Module converters

    +
    +

    Functions

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Variables

    + + + + + +
    +[hide private] + + + + + ADDED applications/examples/static/epydoc/toc-web2py.gluon.custom_import-module.html Index: applications/examples/static/epydoc/toc-web2py.gluon.custom_import-module.html ================================================================== --- applications/examples/static/epydoc/toc-web2py.gluon.custom_import-module.html +++ applications/examples/static/epydoc/toc-web2py.gluon.custom_import-module.html @@ -0,0 +1,60 @@ + + + + + custom_import + + + + + +

    Module custom_import

    +
    +

    Classes

    + + + + +

    Functions

    + custom_import_install
    is_tracking_changes
    track_changes

    Variables

    + + + + +
    +[hide private] + + + + + ADDED applications/examples/static/epydoc/toc-web2py.gluon.dal-module.html Index: applications/examples/static/epydoc/toc-web2py.gluon.dal-module.html ================================================================== --- applications/examples/static/epydoc/toc-web2py.gluon.dal-module.html +++ applications/examples/static/epydoc/toc-web2py.gluon.dal-module.html @@ -0,0 +1,294 @@ + + + + + dal + + + + + +

    Module dal

    +
    +

    Classes

    + + + + +
    + DAL
    + + + +
    + Field
    + + + +
    + GAEF
    +
    + GQLDB
    + + + + + + + + + + + + + + +
    + Query
    + +
    + Row
    +
    + Rows
    + +
    + SQLALL
    + + +
    + SQLDB
    + + + +
    + SQLSet
    + + + + +
    + Set
    +
    + Table
    + + +

    Functions

    + + + + + + + + + + + + + + + + + +
    + xorify
    +

    Variables

    + + + + + + + + + + +
    + logger
    + + + + + + + + +
    + thread
    +
    +[hide private] + + + + + ADDED applications/examples/static/epydoc/toc-web2py.gluon.debug-module.html Index: applications/examples/static/epydoc/toc-web2py.gluon.debug-module.html ================================================================== --- applications/examples/static/epydoc/toc-web2py.gluon.debug-module.html +++ applications/examples/static/epydoc/toc-web2py.gluon.debug-module.html @@ -0,0 +1,41 @@ + + + + + debug + + + + + +

    Module debug

    +
    +

    Classes

    + Pipe

    Functions

    + communicate
    set_trace
    stop_trace

    Variables

    + debugger
    logger
    pipe_in
    pipe_out

    +[hide private] + + + + + ADDED applications/examples/static/epydoc/toc-web2py.gluon.decoder-module.html Index: applications/examples/static/epydoc/toc-web2py.gluon.decoder-module.html ================================================================== --- applications/examples/static/epydoc/toc-web2py.gluon.decoder-module.html +++ applications/examples/static/epydoc/toc-web2py.gluon.decoder-module.html @@ -0,0 +1,41 @@ + + + + + decoder + + + + + +

    Module decoder

    +
    +

    Functions

    + + +

    Variables

    + +
    +[hide private] + + + + + ADDED applications/examples/static/epydoc/toc-web2py.gluon.fileutils-module.html Index: applications/examples/static/epydoc/toc-web2py.gluon.fileutils-module.html ================================================================== --- applications/examples/static/epydoc/toc-web2py.gluon.fileutils-module.html +++ applications/examples/static/epydoc/toc-web2py.gluon.fileutils-module.html @@ -0,0 +1,97 @@ + + + + + fileutils + + + + + +

    Module fileutils

    +
    +

    Functions

    + + + + + + + + + +
    + mktree
    + + + +
    + tar
    + +
    + untar
    +
    + up
    + + + + + +
    +[hide private] + + + + + ADDED applications/examples/static/epydoc/toc-web2py.gluon.globals-module.html Index: applications/examples/static/epydoc/toc-web2py.gluon.globals-module.html ================================================================== --- applications/examples/static/epydoc/toc-web2py.gluon.globals-module.html +++ applications/examples/static/epydoc/toc-web2py.gluon.globals-module.html @@ -0,0 +1,47 @@ + + + + + globals + + + + + +

    Module globals

    +
    +

    Classes

    + + + +

    Variables

    + + +
    +[hide private] + + + + + ADDED applications/examples/static/epydoc/toc-web2py.gluon.highlight-module.html Index: applications/examples/static/epydoc/toc-web2py.gluon.highlight-module.html ================================================================== --- applications/examples/static/epydoc/toc-web2py.gluon.highlight-module.html +++ applications/examples/static/epydoc/toc-web2py.gluon.highlight-module.html @@ -0,0 +1,38 @@ + + + + + highlight + + + + + +

    Module highlight

    +
    +

    Classes

    + +

    Functions

    + +
    +[hide private] + + + + + ADDED applications/examples/static/epydoc/toc-web2py.gluon.html-module.html Index: applications/examples/static/epydoc/toc-web2py.gluon.html-module.html ================================================================== --- applications/examples/static/epydoc/toc-web2py.gluon.html-module.html +++ applications/examples/static/epydoc/toc-web2py.gluon.html-module.html @@ -0,0 +1,264 @@ + + + + + html + + + + + +

    Module html

    +
    +

    Classes

    +
    + A
    +
    + B
    + +
    + BODY
    +
    + BR
    +
    + BUTTON
    +
    + CAT
    +
    + CENTER
    +
    + CODE
    +
    + COL
    + +
    + DIV
    +
    + EM
    +
    + EMBED
    + +
    + FORM
    +
    + H1
    +
    + H2
    +
    + H3
    +
    + H4
    +
    + H5
    +
    + H6
    +
    + HEAD
    +
    + HR
    +
    + HTML
    +
    + I
    +
    + IFRAME
    +
    + IMG
    +
    + INPUT
    +
    + LABEL
    +
    + LEGEND
    +
    + LI
    +
    + LINK
    + +
    + MENU
    +
    + META
    +
    + OBJECT
    +
    + OL
    + +
    + OPTION
    +
    + P
    +
    + PRE
    +
    + SCRIPT
    +
    + SELECT
    +
    + SPAN
    +
    + STYLE
    +
    + TABLE
    +
    + TBODY
    +
    + TD
    + +
    + TFOOT
    +
    + TH
    +
    + THEAD
    +
    + TITLE
    +
    + TR
    +
    + TT
    +
    + UL
    +
    + XHTML
    +
    + XML
    + + + +

    Functions

    + + +
    + URL
    + + + +
    + join
    + + +
    + test
    + + +

    Variables

    +
    + ON
    +
    + TAG
    + +
    +[hide private] + + + + + ADDED applications/examples/static/epydoc/toc-web2py.gluon.http-module.html Index: applications/examples/static/epydoc/toc-web2py.gluon.http-module.html ================================================================== --- applications/examples/static/epydoc/toc-web2py.gluon.http-module.html +++ applications/examples/static/epydoc/toc-web2py.gluon.http-module.html @@ -0,0 +1,45 @@ + + + + + http + + + + + +

    Module http

    +
    +

    Classes

    + +
    + HTTP
    +

    Functions

    + +

    Variables

    + +
    +[hide private] + + + + + ADDED applications/examples/static/epydoc/toc-web2py.gluon.import_all-module.html Index: applications/examples/static/epydoc/toc-web2py.gluon.import_all-module.html ================================================================== --- applications/examples/static/epydoc/toc-web2py.gluon.import_all-module.html +++ applications/examples/static/epydoc/toc-web2py.gluon.import_all-module.html @@ -0,0 +1,43 @@ + + + + + import_all + + + + + +

    Module import_all

    +
    +

    Variables

    + alert_dependency
    base_modules
    candidate
    contributed_modules
    dirs
    files
    module
    name
    py26_deprecated
    py27_deprecated
    python_version
    root

    +[hide private] + + + + + ADDED applications/examples/static/epydoc/toc-web2py.gluon.languages-module.html Index: applications/examples/static/epydoc/toc-web2py.gluon.languages-module.html ================================================================== --- applications/examples/static/epydoc/toc-web2py.gluon.languages-module.html +++ applications/examples/static/epydoc/toc-web2py.gluon.languages-module.html @@ -0,0 +1,75 @@ + + + + + languages + + + + + +

    Module languages

    +
    +

    Classes

    +
    + lazyT
    + +

    Functions

    +
    + findT
    + + + + + + + +

    Variables

    + +
    + is_gae
    + + +
    +[hide private] + + + + + ADDED applications/examples/static/epydoc/toc-web2py.gluon.main-module.html Index: applications/examples/static/epydoc/toc-web2py.gluon.main-module.html ================================================================== --- applications/examples/static/epydoc/toc-web2py.gluon.main-module.html +++ applications/examples/static/epydoc/toc-web2py.gluon.main-module.html @@ -0,0 +1,79 @@ + + + + + main + + + + + +

    Module main

    +
    +

    Classes

    + HttpServer

    Functions

    + appfactory
    + + + + + save_password
    + + wsgibase

    Variables

    +
    + logger
    + + + + + + +
    +[hide private] + + + + + ADDED applications/examples/static/epydoc/toc-web2py.gluon.myregex-module.html Index: applications/examples/static/epydoc/toc-web2py.gluon.myregex-module.html ================================================================== --- applications/examples/static/epydoc/toc-web2py.gluon.myregex-module.html +++ applications/examples/static/epydoc/toc-web2py.gluon.myregex-module.html @@ -0,0 +1,43 @@ + + + + + myregex + + + + + +

    Module myregex

    +
    +

    Variables

    + + + + +
    +[hide private] + + + + + ADDED applications/examples/static/epydoc/toc-web2py.gluon.newcron-module.html Index: applications/examples/static/epydoc/toc-web2py.gluon.newcron-module.html ================================================================== --- applications/examples/static/epydoc/toc-web2py.gluon.newcron-module.html +++ applications/examples/static/epydoc/toc-web2py.gluon.newcron-module.html @@ -0,0 +1,46 @@ + + + + + newcron + + + + + +

    Module newcron

    +
    +

    Classes

    + Token
    cronlauncher
    extcron
    hardcron
    softcron

    Functions

    + crondance
    parsecronline
    rangetolist
    stopcron

    Variables

    + + logger

    +[hide private] + + + + + ADDED applications/examples/static/epydoc/toc-web2py.gluon.portalocker-module.html Index: applications/examples/static/epydoc/toc-web2py.gluon.portalocker-module.html ================================================================== --- applications/examples/static/epydoc/toc-web2py.gluon.portalocker-module.html +++ applications/examples/static/epydoc/toc-web2py.gluon.portalocker-module.html @@ -0,0 +1,56 @@ + + + + + portalocker + + + + + +

    Module portalocker

    +
    +

    Functions

    +
    + lock
    +
    + unlock
    +

    Variables

    + + + + +
    + logger
    + +
    +[hide private] + + + + + ADDED applications/examples/static/epydoc/toc-web2py.gluon.reserved_sql_keywords-module.html Index: applications/examples/static/epydoc/toc-web2py.gluon.reserved_sql_keywords-module.html ================================================================== --- applications/examples/static/epydoc/toc-web2py.gluon.reserved_sql_keywords-module.html +++ applications/examples/static/epydoc/toc-web2py.gluon.reserved_sql_keywords-module.html @@ -0,0 +1,47 @@ + + + + + reserved_sql_keywords + + + + + +

    Module reserved_sql_keywords

    +
    +

    Variables

    + ADAPTERS
    COMMON
    DB2
    FIREBIRD
    FIREBIRD_NONRESERVED
    INFORMIX
    INGRES
    JDBCPOSTGRESQL
    JDBCSQLITE
    MSSQL
    MYSQL
    ORACLE
    POSTGRESQL
    POSTGRESQL_NONRESERVED
    SQLITE
    __author__

    +[hide private] + + + + + ADDED applications/examples/static/epydoc/toc-web2py.gluon.restricted-module.html Index: applications/examples/static/epydoc/toc-web2py.gluon.restricted-module.html ================================================================== --- applications/examples/static/epydoc/toc-web2py.gluon.restricted-module.html +++ applications/examples/static/epydoc/toc-web2py.gluon.restricted-module.html @@ -0,0 +1,43 @@ + + + + + restricted + + + + + +

    Module restricted

    +
    +

    Classes

    + RestrictedError
    TicketStorage

    Functions

    + compile2
    restricted
    +

    Variables

    +
    + logger
    +
    +[hide private] + + + + + ADDED applications/examples/static/epydoc/toc-web2py.gluon.rewrite-module.html Index: applications/examples/static/epydoc/toc-web2py.gluon.rewrite-module.html ================================================================== --- applications/examples/static/epydoc/toc-web2py.gluon.rewrite-module.html +++ applications/examples/static/epydoc/toc-web2py.gluon.rewrite-module.html @@ -0,0 +1,135 @@ + + + + + rewrite + + + + + +

    Module rewrite

    +
    +

    Classes

    + + +

    Functions

    + + + + + + +
    + load
    + + + + + + + + + + +
    + url_in
    + +

    Variables

    + + +
    + logger
    +
    + params
    + + + + + + + + +
    + thread
    +
    +[hide private] + + + + + ADDED applications/examples/static/epydoc/toc-web2py.gluon.rocket-module.html Index: applications/examples/static/epydoc/toc-web2py.gluon.rocket-module.html ================================================================== --- applications/examples/static/epydoc/toc-web2py.gluon.rocket-module.html +++ applications/examples/static/epydoc/toc-web2py.gluon.rocket-module.html @@ -0,0 +1,146 @@ + + + + + rocket + + + + + +

    Module rocket

    +
    +

    Classes

    + + + + + + + + NullHandler
    Rocket
    + + + + +
    + Worker
    +

    Functions

    + CherryPyWSGIServer
    + b
    + demo
    + + + u

    Variables

    + + BUF_SIZE
    DEFAULTS
    + + + + + HTTP_SERVER_SOFTWARE
    IGNORE_ERRORS_ON_CLOSE
    IS_JYTHON
    + + PY3K
    + SERVER_NAME
    SERVER_SOFTWARE
    + + VERSION
    + +
    + log
    + + +
    +[hide private] + + + + + ADDED applications/examples/static/epydoc/toc-web2py.gluon.sanitizer-module.html Index: applications/examples/static/epydoc/toc-web2py.gluon.sanitizer-module.html ================================================================== --- applications/examples/static/epydoc/toc-web2py.gluon.sanitizer-module.html +++ applications/examples/static/epydoc/toc-web2py.gluon.sanitizer-module.html @@ -0,0 +1,41 @@ + + + + + sanitizer + + + + + +

    Module sanitizer

    +
    +

    Classes

    + +

    Functions

    + + +
    +[hide private] + + + + + ADDED applications/examples/static/epydoc/toc-web2py.gluon.serializers-module.html Index: applications/examples/static/epydoc/toc-web2py.gluon.serializers-module.html ================================================================== --- applications/examples/static/epydoc/toc-web2py.gluon.serializers-module.html +++ applications/examples/static/epydoc/toc-web2py.gluon.serializers-module.html @@ -0,0 +1,49 @@ + + + + + serializers + + + + + +

    Module serializers

    +
    +

    Functions

    +
    + csv
    + +
    + json
    +
    + rss
    +
    + xml
    + +
    +[hide private] + + + + + ADDED applications/examples/static/epydoc/toc-web2py.gluon.settings-module.html Index: applications/examples/static/epydoc/toc-web2py.gluon.settings-module.html ================================================================== --- applications/examples/static/epydoc/toc-web2py.gluon.settings-module.html +++ applications/examples/static/epydoc/toc-web2py.gluon.settings-module.html @@ -0,0 +1,37 @@ + + + + + settings + + + + + +

    Module settings

    +
    +

    Variables

    + + +
    +[hide private] + + + + + ADDED applications/examples/static/epydoc/toc-web2py.gluon.shell-module.html Index: applications/examples/static/epydoc/toc-web2py.gluon.shell-module.html ================================================================== --- applications/examples/static/epydoc/toc-web2py.gluon.shell-module.html +++ applications/examples/static/epydoc/toc-web2py.gluon.shell-module.html @@ -0,0 +1,42 @@ + + + + + shell + + + + + +

    Module shell

    +
    +

    Functions

    + die
    env
    exec_environment
    exec_pythonrc
    execute_from_command_line
    get_usage
    parse_path_info
    run
    test

    Variables

    + logger

    +[hide private] + + + + + ADDED applications/examples/static/epydoc/toc-web2py.gluon.sql-module.html Index: applications/examples/static/epydoc/toc-web2py.gluon.sql-module.html ================================================================== --- applications/examples/static/epydoc/toc-web2py.gluon.sql-module.html +++ applications/examples/static/epydoc/toc-web2py.gluon.sql-module.html @@ -0,0 +1,35 @@ + + + + + sql + + + + + +

    Module sql

    +
    +

    Classes

    + DAL
    Field

    Variables

    + drivers

    +[hide private] + + + + + ADDED applications/examples/static/epydoc/toc-web2py.gluon.sqlhtml-module.html Index: applications/examples/static/epydoc/toc-web2py.gluon.sqlhtml-module.html ================================================================== --- applications/examples/static/epydoc/toc-web2py.gluon.sqlhtml-module.html +++ applications/examples/static/epydoc/toc-web2py.gluon.sqlhtml-module.html @@ -0,0 +1,111 @@ + + + + + sqlhtml + + + + + +

    Module sqlhtml

    +
    +

    Classes

    + + + + + + + + + + + + + + + + + + + + +

    Functions

    + + + + +

    Variables

    + + +
    +[hide private] + + + + + ADDED applications/examples/static/epydoc/toc-web2py.gluon.storage-module.html Index: applications/examples/static/epydoc/toc-web2py.gluon.storage-module.html ================================================================== --- applications/examples/static/epydoc/toc-web2py.gluon.storage-module.html +++ applications/examples/static/epydoc/toc-web2py.gluon.storage-module.html @@ -0,0 +1,53 @@ + + + + + storage + + + + + +

    Module storage

    +
    +

    Classes

    +
    + List
    + + + + +

    Functions

    + + +
    +[hide private] + + + + + ADDED applications/examples/static/epydoc/toc-web2py.gluon.streamer-module.html Index: applications/examples/static/epydoc/toc-web2py.gluon.streamer-module.html ================================================================== --- applications/examples/static/epydoc/toc-web2py.gluon.streamer-module.html +++ applications/examples/static/epydoc/toc-web2py.gluon.streamer-module.html @@ -0,0 +1,47 @@ + + + + + streamer + + + + + +

    Module streamer

    +
    +

    Functions

    + + +

    Variables

    + + + +
    +[hide private] + + + + + ADDED applications/examples/static/epydoc/toc-web2py.gluon.template-module.html Index: applications/examples/static/epydoc/toc-web2py.gluon.template-module.html ================================================================== --- applications/examples/static/epydoc/toc-web2py.gluon.template-module.html +++ applications/examples/static/epydoc/toc-web2py.gluon.template-module.html @@ -0,0 +1,40 @@ + + + + + template + + + + + +

    Module template

    +
    +

    Classes

    + BlockNode
    Content
    Node
    SuperNode
    TemplateParser

    Functions

    + get_parsed
    parse_template
    render

    +[hide private] + + + + + ADDED applications/examples/static/epydoc/toc-web2py.gluon.tools-module.html Index: applications/examples/static/epydoc/toc-web2py.gluon.tools-module.html ================================================================== --- applications/examples/static/epydoc/toc-web2py.gluon.tools-module.html +++ applications/examples/static/epydoc/toc-web2py.gluon.tools-module.html @@ -0,0 +1,81 @@ + + + + + tools + + + + + +

    Module tools

    +
    +

    Classes

    + Auth
    Crud
    Mail
    PluginManager
    Recaptcha
    Service

    Functions

    + +
    + addrow
    + + + + fetch
    geocode
    prettydate
    + + +

    Variables

    +
    + ON
    +
    + TAG
    + +
    + logger
    + +
    +[hide private] + + + + + ADDED applications/examples/static/epydoc/toc-web2py.gluon.utils-module.html Index: applications/examples/static/epydoc/toc-web2py.gluon.utils-module.html ================================================================== --- applications/examples/static/epydoc/toc-web2py.gluon.utils-module.html +++ applications/examples/static/epydoc/toc-web2py.gluon.utils-module.html @@ -0,0 +1,56 @@ + + + + + utils + + + + + +

    Module utils

    +
    +

    Functions

    + + + + + + +

    Variables

    + +
    + logger
    +
    +[hide private] + + + + + ADDED applications/examples/static/epydoc/toc-web2py.gluon.validators-module.html Index: applications/examples/static/epydoc/toc-web2py.gluon.validators-module.html ================================================================== --- applications/examples/static/epydoc/toc-web2py.gluon.validators-module.html +++ applications/examples/static/epydoc/toc-web2py.gluon.validators-module.html @@ -0,0 +1,189 @@ + + + + + validators + + + + + +

    Module validators

    +
    +

    Classes

    + +
    + CRYPT
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + IS_URL
    + +

    Functions

    + + + + + + +
    + urlify
    +

    Variables

    + + + + + +
    + regex1
    +
    + regex2
    + + + +
    +[hide private] + + + + + ADDED applications/examples/static/epydoc/toc-web2py.gluon.widget-module.html Index: applications/examples/static/epydoc/toc-web2py.gluon.widget-module.html ================================================================== --- applications/examples/static/epydoc/toc-web2py.gluon.widget-module.html +++ applications/examples/static/epydoc/toc-web2py.gluon.widget-module.html @@ -0,0 +1,47 @@ + + + + + widget + + + + + +

    Module widget

    +
    +

    Classes

    + BaseException
    IO
    web2pyDialog

    Functions

    + console
    presentation
    start
    start_browser
    try_start_browser

    Variables

    + ProgramAuthor
    ProgramInfo
    ProgramName
    ProgramVersion
    logger
    msg

    +[hide private] + + + + + ADDED applications/examples/static/epydoc/toc-web2py.gluon.winservice-module.html Index: applications/examples/static/epydoc/toc-web2py.gluon.winservice-module.html ================================================================== --- applications/examples/static/epydoc/toc-web2py.gluon.winservice-module.html +++ applications/examples/static/epydoc/toc-web2py.gluon.winservice-module.html @@ -0,0 +1,39 @@ + + + + + winservice + + + + + +

    Module winservice

    +
    +

    Classes

    + + +

    Functions

    + web2py_windows_service_handler

    +[hide private] + + + + + ADDED applications/examples/static/epydoc/toc-web2py.gluon.xmlrpc-module.html Index: applications/examples/static/epydoc/toc-web2py.gluon.xmlrpc-module.html ================================================================== --- applications/examples/static/epydoc/toc-web2py.gluon.xmlrpc-module.html +++ applications/examples/static/epydoc/toc-web2py.gluon.xmlrpc-module.html @@ -0,0 +1,34 @@ + + + + + xmlrpc + + + + + +

    Module xmlrpc

    +
    +

    Functions

    + +
    +[hide private] + + + + + ADDED applications/examples/static/epydoc/toc.html Index: applications/examples/static/epydoc/toc.html ================================================================== --- applications/examples/static/epydoc/toc.html +++ applications/examples/static/epydoc/toc.html @@ -0,0 +1,132 @@ + + + + + Table of Contents + + + + + +

    Table of Contents

    +
    + Everything +
    +

    Modules

    + psycopg2
    psycopg2.tz
    sqlite3.dbapi2
    web2py.gluon
    web2py.gluon.admin
    web2py.gluon.cache
    + web2py.gluon.compileapp
    + + + + + web2py.gluon.custom_import
    + web2py.gluon.debug
    + + + + + + web2py.gluon.import_all
    + web2py.gluon.main
    + web2py.gluon.newcron
    + web2py.gluon.reserved_sql_keywords
    web2py.gluon.restricted
    + web2py.gluon.rocket
    + + + web2py.gluon.shell
    web2py.gluon.sql
    + + + web2py.gluon.template
    web2py.gluon.tools
    + + web2py.gluon.widget
    web2py.gluon.winservice
    +
    + [hide private] + + + + + ADDED applications/examples/static/epydoc/web2py.gluon-module.html Index: applications/examples/static/epydoc/web2py.gluon-module.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon-module.html +++ applications/examples/static/epydoc/web2py.gluon-module.html @@ -0,0 +1,1286 @@ + + + + + web2py.gluon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Package gluon

    source code

    +This file is part of the web2py Web Framework Copyrighted by Massimo + Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html) +

    Web2Py framework modules



    + + + + + + + + +
    + + + + + +
    Submodules[hide private]
    +
    +
      +
    • web2py.gluon.admin: This file is part of the web2py Web Framework Copyrighted by + Massimo Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)
    • +
    • web2py.gluon.cache: This file is part of the web2py Web Framework Copyrighted by + Massimo Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)
    • +
    • web2py.gluon.cfs: This file is part of the web2py Web Framework Copyrighted by + Massimo Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)
    • +
    • web2py.gluon.compileapp: This file is part of the web2py Web Framework Copyrighted by + Massimo Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)
    • +
    • web2py.gluon.contenttype: This file is part of the web2py Web Framework Copyrighted by + Massimo Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)
    • +
    • web2py.gluon.contrib + +
    • +
    • web2py.gluon.custom_import
    • +
    • web2py.gluon.dal: This file is part of the web2py Web Framework...
    • +
    • web2py.gluon.debug: This file is part of the web2py Web Framework Developed by + Massimo Di Pierro <mdipierro@cs.depaul.edu>, limodou + <limodou@gmail.com> and srackham + <srackham@gmail.com>.
    • +
    • web2py.gluon.decoder
    • +
    • web2py.gluon.fileutils: This file is part of the web2py Web Framework Copyrighted by + Massimo Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)
    • +
    • web2py.gluon.globals: This file is part of the web2py Web Framework...
    • +
    • web2py.gluon.highlight: This file is part of the web2py Web Framework Copyrighted by + Massimo Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)
    • +
    • web2py.gluon.html: This file is part of the web2py Web Framework Copyrighted by + Massimo Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)
    • +
    • web2py.gluon.http: This file is part of the web2py Web Framework Copyrighted by + Massimo Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)
    • +
    • web2py.gluon.import_all: This file is part of the web2py Web Framework Copyrighted by + Massimo Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)
    • +
    • web2py.gluon.languages: This file is part of the web2py Web Framework Copyrighted by + Massimo Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)
    • +
    • web2py.gluon.main: This file is part of the web2py Web Framework...
    • +
    • web2py.gluon.myregex: This file is part of the web2py Web Framework Copyrighted by + Massimo Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)
    • +
    • web2py.gluon.newcron: Created by Attila Csipa <web2py@csipa.in.rs> Modified by + Massimo Di Pierro <mdipierro@cs.depaul.edu>
    • +
    • web2py.gluon.portalocker: Cross-platform (posix/nt) API for flock-style file locking.
    • +
    • web2py.gluon.reserved_sql_keywords
    • +
    • web2py.gluon.restricted: This file is part of the web2py Web Framework Copyrighted by + Massimo Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)
    • +
    • web2py.gluon.rewrite: This file is part of the web2py Web Framework Copyrighted by + Massimo Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)
    • +
    • web2py.gluon.rocket
    • +
    • web2py.gluon.sanitizer: :
    • +
    • web2py.gluon.serializers: This file is part of the web2py Web Framework Copyrighted by + Massimo Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)
    • +
    • web2py.gluon.settings: This file is part of the web2py Web Framework Copyrighted by + Massimo Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)
    • +
    • web2py.gluon.shell: This file is part of the web2py Web Framework Developed by + Massimo Di Pierro <mdipierro@cs.depaul.edu>, limodou + <limodou@gmail.com> and srackham + <srackham@gmail.com>.
    • +
    • web2py.gluon.sql
    • +
    • web2py.gluon.sqlhtml: This file is part of the web2py Web Framework...
    • +
    • web2py.gluon.storage: This file is part of the web2py Web Framework...
    • +
    • web2py.gluon.streamer: This file is part of the web2py Web Framework Copyrighted by + Massimo Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)
    • +
    • web2py.gluon.template: This file is part of the web2py Web Framework (Copyrighted, 2007-2011).
    • +
    • web2py.gluon.tools: This file is part of the web2py Web Framework Copyrighted by + Massimo Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)
    • +
    • web2py.gluon.utils: This file is part of the web2py Web Framework Copyrighted by + Massimo Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)
    • +
    • web2py.gluon.validators: This file is part of the web2py Web Framework Copyrighted by + Massimo Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)
    • +
    • web2py.gluon.widget: This file is part of the web2py Web Framework Copyrighted by + Massimo Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)
    • +
    • web2py.gluon.xmlrpc: This file is part of the web2py Web Framework Copyrighted by + Massimo Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)
    • +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Classes[hide private]
    +
    +   + + HTTP +
    +   + + DAL
    + an instance of this class represents a database connection +
    +   + + Field
    + an instance of this class represents a database field +
    +   + + SQLFORM
    + SQLFORM is used to map a table (and a current record) into an HTML form + +given a SQLTable stored in db.table + +generates an insert form:: + + SQLFORM(db.table) + +generates an update form:: + + record=db.table[some_id] + SQLFORM(db.table, record) + +generates an update with a delete button:: + + SQLFORM(db.table, record, deletable=True) + +if record is an int:: + + record=db.table[record] + +optional arguments: + +:param fields: a list of fields that should be placed in the form, + default is all. +
    +   + + SQLTABLE
    + given a Rows object, as returned by a db().select(), generates +an html table with the rows. +
    +   + + A +
    +   + + B +
    +   + + BEAUTIFY
    + example:: + + >>> BEAUTIFY(['a', 'b', {'hello': 'world'}]).xml() + '<div><table><tr><td><div>a</div></td></tr><tr><td><div>b</div></td></tr><tr><td><div><table><tr><td style="font-weight:bold;">hello</td><td valign="top">:</td><td><div>world</div></td></tr></table></div></td></tr></table></div>' + +turns any list, dictionary, etc into decent looking html. +
    +   + + BODY +
    +   + + BR +
    +   + + CAT +
    +   + + CENTER +
    +   + + CLEANUP
    + example: +
    +   + + CODE
    + displays code in HTML with syntax highlighting. +
    +   + + CRYPT
    + example: +
    +   + + DIV
    + HTML helper, for easy generating and manipulating a DOM + structure. +
    +   + + EM +
    +   + + EMBED +
    +   + + FIELDSET +
    +   + + FORM
    + example: +
    +   + + H1 +
    +   + + H2 +
    +   + + H3 +
    +   + + H4 +
    +   + + H5 +
    +   + + H6 +
    +   + + HEAD +
    +   + + HR +
    +   + + HTML
    + There are four predefined document type definitions. +
    +   + + I +
    +   + + IFRAME +
    +   + + IMG +
    +   + + INPUT
    + INPUT Component + +examples:: + + >>> INPUT(_type='text', _name='name', value='Max').xml() + '<input name="name" type="text" value="Max" />' + + >>> INPUT(_type='checkbox', _name='checkbox', value='on').xml() + '<input checked="checked" name="checkbox" type="checkbox" value="on" />' + + >>> INPUT(_type='radio', _name='radio', _value='yes', value='yes').xml() + '<input checked="checked" name="radio" type="radio" value="yes" />' + + >>> INPUT(_type='radio', _name='radio', _value='no', value='yes').xml() + '<input name="radio" type="radio" value="no" />' + +the input helper takes two special attributes value= and requires=. +
    +   + + IS_ALPHANUMERIC
    + example: +
    +   + + IS_DATE
    + example: +
    +   + + IS_DATETIME
    + example: +
    +   + + IS_DATETIME_IN_RANGE
    + example: +
    +   + + IS_DATE_IN_RANGE
    + example: +
    +   + + IS_DECIMAL_IN_RANGE
    + Determine that the argument is (or can be represented as) a + Python Decimal, and that it falls within the specified inclusive + range. +
    +   + + IS_EMAIL
    + Checks if field's value is a valid email address. +
    +   + + IS_EMPTY_OR
    + dummy class for testing IS_EMPTY_OR +
    +   + + IS_EQUAL_TO
    + example: +
    +   + + IS_EXPR
    + example: +
    +   + + IS_FLOAT_IN_RANGE
    + Determine that the argument is (or can be represented as) a + float, and that it falls within the specified inclusive range. +
    +   + + IS_IMAGE
    + Checks if file uploaded through file input was saved in one of + selected image formats and has dimensions (width and height) within + given boundaries. +
    +   + + IS_INT_IN_RANGE
    + Determine that the argument is (or can be represented as) an + int, and that it falls within the specified range. +
    +   + + IS_IN_DB
    + example: +
    +   + + IS_IN_SET
    + example: +
    +   + + IS_IPV4
    + Checks if field's value is an IP version 4 address in decimal form. +
    +   + + IS_LENGTH
    + Checks if length of field's value fits between given + boundaries. +
    +   + + IS_LIST_OF +
    +   + + IS_LOWER
    + convert to lower case +
    +   + + IS_MATCH
    + example: +
    +   + + IS_NOT_EMPTY
    + example: +
    +   + + IS_NOT_IN_DB
    + example: +
    +   + + IS_NULL_OR
    + dummy class for testing IS_EMPTY_OR +
    +   + + IS_SLUG
    + convert arbitrary text string to a slug +
    +   + + IS_STRONG
    + example: +
    +   + + IS_TIME
    + example: +
    +   + + IS_UPLOAD_FILENAME
    + Checks if name and extension of file uploaded through file input matches +given criteria. +
    +   + + IS_UPPER
    + convert to upper case +
    +   + + IS_URL
    + Rejects a URL string if any of the following is true: + * The string is empty or None + * The string uses characters that are not allowed in a URL + * The string breaks any of the HTTP syntactic rules + * The URL scheme specified (if one is specified) is not 'http' or 'https' + * The top-level domain (if a host name is specified) does not exist + +(These rules are based on RFC 2616: http://www.faqs.org/rfcs/rfc2616.html) + +This function only checks the URL's syntax. +
    +   + + LABEL +
    +   + + LEGEND +
    +   + + LI +
    +   + + LINK +
    +   + + MARKMIN
    + For documentation: + http://web2py.com/examples/static/markmin.html +
    +   + + MENU
    + Used to build menus... +
    +   + + META +
    +   + + OBJECT +
    +   + + OL +
    +   + + OPTGROUP +
    +   + + OPTION +
    +   + + P
    + Will replace ``\n`` by ``<br />`` if the `cr2br` attribute + is provided. +
    +   + + PRE +
    +   + + SCRIPT +
    +   + + SELECT
    + example: +
    +   + + SPAN +
    +   + + STYLE +
    +   + + TABLE
    + TABLE Component. +
    +   + + TBODY +
    +   + + TD +
    +   + + TEXTAREA
    + example: +
    +   + + TFOOT +
    +   + + TH +
    +   + + THEAD +
    +   + + TITLE +
    +   + + TR
    + TR Component. +
    +   + + TT +
    +   + + UL
    + UL Component. +
    +   + + XHTML
    + This is XHTML version of the HTML helper. +
    +   + + XML
    + use it to wrap a string that contains XML/HTML so that it will + not be escaped by the template +
    + + + + + + + + + + + + + + + +
    + + + + + +
    Functions[hide private]
    +
    +   + + + + + + +
    redirect(location, + how=303) + source code + +
    + +
    +   + + + + + + +
    URL(a=1, + c=1, + f=1, + r=1, + args=[], + vars={}, + anchor='', + extension=1, + env=1, + hmac_key=1, + hash_vars=True, + salt=1, + user_signature=1, + scheme=1, + host=1, + port=1)
    + generate a URL + +example:: + + >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'], + ...
    + source code + +
    + +
    +   + + + + + + +
    embed64(filename=1, + file=1, + data=1, + extension='image/gif')
    + helper to encode the provided (binary) data into base64.
    + source code + +
    + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    Variables[hide private]
    +
    +   + + current = threading.local() +
    +   + + ON = True +
    +   + + TAG = __TAG__() +
    + + + + + + +
    + + + + + +
    Function Details[hide private]
    +
    + +
    + +
    + + +
    +

    URL(a=1, + c=1, + f=1, + r=1, + args=[], + vars={}, + anchor='', + extension=1, + env=1, + hmac_key=1, + hash_vars=True, + salt=1, + user_signature=1, + scheme=1, + host=1, + port=1) +

    +
    source code  +
    + +
    +
    +generate a URL
    +
    +example::
    +
    +    >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'],
    +    ...     vars={'p':1, 'q':2}, anchor='1'))
    +    '/a/c/f/x/y/z?p=1&q=2#1'
    +
    +    >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'],
    +    ...     vars={'p':(1,3), 'q':2}, anchor='1'))
    +    '/a/c/f/x/y/z?p=1&p=3&q=2#1'
    +
    +    >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'],
    +    ...     vars={'p':(3,1), 'q':2}, anchor='1'))
    +    '/a/c/f/x/y/z?p=3&p=1&q=2#1'
    +
    +    >>> str(URL(a='a', c='c', f='f', anchor='1+2'))
    +    '/a/c/f#1%2B2'
    +
    +    >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'],
    +    ...     vars={'p':(1,3), 'q':2}, anchor='1', hmac_key='key'))
    +    '/a/c/f/x/y/z?p=1&p=3&q=2&_signature=5d06bb8a4a6093dd325da2ee591c35c61afbd3c6#1'
    +
    +generates a url '/a/c/f' corresponding to application a, controller c
    +and function f. If r=request is passed, a, c, f are set, respectively,
    +to r.application, r.controller, r.function.
    +
    +The more typical usage is:
    +
    +URL(r=request, f='index') that generates a url for the index function
    +within the present application and controller.
    +
    +:param a: application (default to current if r is given)
    +:param c: controller (default to current if r is given)
    +:param f: function (default to current if r is given)
    +:param r: request (optional)
    +:param args: any arguments (optional)
    +:param vars: any variables (optional)
    +:param anchor: anchorname, without # (optional)
    +:param hmac_key: key to use when generating hmac signature (optional)
    +:param hash_vars: which of the vars to include in our hmac signature
    +    True (default) - hash all vars, False - hash none of the vars,
    +    iterable - hash only the included vars ['key1','key2']
    +:param scheme: URI scheme (True, 'http' or 'https', etc); forces absolute URL (optional)
    +:param host: string to force absolute URL with host (True means http_host)
    +:param port: optional port number (forces absolute URL)
    +
    +:raises SyntaxError: when no application, controller or function is
    +    available
    +:raises SyntaxError: when a CRLF is found in the generated url
    +
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    embed64(filename=1, + file=1, + data=1, + extension='image/gif') +

    +
    source code  +
    + +

    helper to encode the provided (binary) data into base64.

    + :param filename: if provided, opens and reads this file in 'rb' mode + :param file: if provided, reads this file :param data: if provided, uses + the provided data +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon-pysrc.html Index: applications/examples/static/epydoc/web2py.gluon-pysrc.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon-pysrc.html +++ applications/examples/static/epydoc/web2py.gluon-pysrc.html @@ -0,0 +1,144 @@ + + + + + web2py.gluon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Package web2py.gluon

    +
    + 1  #!/usr/bin/env python 
    + 2  # -*- coding: utf-8 -*- 
    + 3  """ 
    + 4  This file is part of the web2py Web Framework 
    + 5  Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> 
    + 6  License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) 
    + 7   
    + 8   
    + 9  Web2Py framework modules 
    +10  ======================== 
    +11  """ 
    +12   
    +13  __all__ = ['A', 'B', 'BEAUTIFY', 'BODY', 'BR', 'CAT', 'CENTER', 'CLEANUP', 'CODE', 'CRYPT', 'DAL', 'DIV', 'EM', 'EMBED', 'FIELDSET', 'FORM', 'Field', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'HEAD', 'HR', 'HTML', 'HTTP', 'I', 'IFRAME', 'IMG', 'INPUT', 'IS_ALPHANUMERIC', 'IS_DATE', 'IS_DATETIME', 'IS_DATETIME_IN_RANGE', 'IS_DATE_IN_RANGE', 'IS_DECIMAL_IN_RANGE', 'IS_EMAIL', 'IS_EMPTY_OR', 'IS_EQUAL_TO', 'IS_EXPR', 'IS_FLOAT_IN_RANGE', 'IS_IMAGE', 'IS_INT_IN_RANGE', 'IS_IN_DB', 'IS_IN_SET', 'IS_IPV4', 'IS_LENGTH', 'IS_LIST_OF', 'IS_LOWER', 'IS_MATCH', 'IS_NOT_EMPTY', 'IS_NOT_IN_DB', 'IS_NULL_OR', 'IS_SLUG', 'IS_STRONG', 'IS_TIME', 'IS_UPLOAD_FILENAME', 'IS_UPPER', 'IS_URL', 'LABEL', 'LEGEND', 'LI', 'LINK', 'MARKMIN', 'MENU', 'META', 'OBJECT', 'OL', 'ON', 'OPTGROUP', 'OPTION', 'P', 'PRE', 'SCRIPT', 'SELECT', 'SPAN', 'SQLFORM', 'SQLTABLE', 'STYLE', 'TABLE', 'TAG', 'TBODY', 'TD', 'TEXTAREA', 'TFOOT', 'TH', 'THEAD', 'TITLE', 'TR', 'TT', 'UL', 'URL', 'XHTML', 'XML','redirect','current','embed64'] 
    +14   
    +15  from globals import current 
    +16  from html import * 
    +17  from validators import * 
    +18  from http import redirect, HTTP 
    +19  from dal import DAL, Field 
    +20  from sqlhtml import SQLFORM, SQLTABLE 
    +21   
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.admin-module.html Index: applications/examples/static/epydoc/web2py.gluon.admin-module.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.admin-module.html +++ applications/examples/static/epydoc/web2py.gluon.admin-module.html @@ -0,0 +1,898 @@ + + + + + web2py.gluon.admin + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module admin + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Module admin

    source code

    +This file is part of the web2py Web Framework Copyrighted by Massimo + Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html) +

    Utility functions for the Admin application



    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Functions[hide private]
    +
    +   + + + + + + +
    apath(path='', + r=1)
    + Builds a path inside an application folder...
    + source code + +
    + +
    +   + + + + + + +
    app_pack(app, + request)
    + Builds a w2p package for the application...
    + source code + +
    + +
    +   + + + + + + +
    app_pack_compiled(app, + request)
    + Builds a w2p bytecode-compiled package for the application...
    + source code + +
    + +
    +   + + + + + + +
    app_cleanup(app, + request)
    + Removes session, cache and error files...
    + source code + +
    + +
    +   + + + + + + +
    app_compile(app, + request)
    + Compiles the application...
    + source code + +
    + +
    +   + + + + + + +
    app_create(app, + request, + force=True, + key=1)
    + Create a copy of welcome.w2p (scaffolding) app...
    + source code + +
    + +
    +   + + + + + + +
    app_install(app, + fobj, + request, + filename, + overwrite=1)
    + Installs an application: + +- Identifies file type by filename +- Writes `fobj` contents to the `../deposit/` folder +- Calls `w2p_unpack()` to do the job.
    + source code + +
    + +
    +   + + + + + + +
    app_uninstall(app, + request)
    + Uninstalls the application.
    + source code + +
    + +
    +   + + + + + + +
    plugin_pack(app, + plugin_name, + request)
    + Builds a w2p package for the application...
    + source code + +
    + +
    +   + + + + + + +
    plugin_install(app, + fobj, + request, + filename)
    + Installs an application: + +- Identifies file type by filename +- Writes `fobj` contents to the `../deposit/` folder +- Calls `w2p_unpack()` to do the job.
    + source code + +
    + +
    +   + + + + + + +
    check_new_version(myversion, + version_URL)
    + Compares current web2py's version with the latest stable web2py version.
    + source code + +
    + +
    +   + + + + + + +
    unzip(filename, + dir, + subfolder='')
    + Unzips filename into dir (.zip only, no .gz etc) if subfolder!='' + it unzip only files in subfolder
    + source code + +
    + +
    +   + + + + + + +
    upgrade(request, + url='http://web2py.com')
    + Upgrades web2py (src, osx, win) is a new version is posted.
    + source code + +
    + +
    +   + + + + + + +
    add_path_first(path) + source code + +
    + +
    +   + + + + + + +
    create_missing_folders() + source code + +
    + +
    +   + + + + + + +
    create_missing_app_folders(request) + source code + +
    + +
    + + + + + + +
    + + + + + +
    Function Details[hide private]
    +
    + +
    + +
    + + +
    +

    apath(path='', + r=1) +

    +
    source code  +
    + +
    +
    +Builds a path inside an application folder
    +
    +Parameters
    +----------
    +path:
    +    path within the application folder
    +r:
    +    the global request object
    +
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    app_pack(app, + request) +

    +
    source code  +
    + +
    +
    +Builds a w2p package for the application
    +
    +Parameters
    +----------
    +app:
    +    application name
    +request:
    +    the global request object
    +
    +Returns
    +-------
    +filename:
    +    filename of the w2p file or None on error
    +
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    app_pack_compiled(app, + request) +

    +
    source code  +
    + +
    +
    +Builds a w2p bytecode-compiled package for the application
    +
    +Parameters
    +----------
    +app:
    +    application name
    +request:
    +    the global request object
    +
    +Returns
    +-------
    +filename:
    +    filename of the w2p file or None on error
    +
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    app_cleanup(app, + request) +

    +
    source code  +
    + +
    +
    +Removes session, cache and error files
    +
    +Parameters
    +----------
    +app:
    +    application name
    +request:
    +    the global request object
    +
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    app_compile(app, + request) +

    +
    source code  +
    + +
    +
    +Compiles the application
    +
    +Parameters
    +----------
    +app:
    +    application name
    +request:
    +    the global request object
    +
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    app_create(app, + request, + force=True, + key=1) +

    +
    source code  +
    + +
    +
    +Create a copy of welcome.w2p (scaffolding) app
    +
    +Parameters
    +----------
    +app:
    +    application name
    +request:
    +    the global request object
    +
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    app_install(app, + fobj, + request, + filename, + overwrite=1) +

    +
    source code  +
    + +
    +
    +Installs an application:
    +
    +- Identifies file type by filename
    +- Writes `fobj` contents to the `../deposit/` folder
    +- Calls `w2p_unpack()` to do the job.
    +
    +Parameters
    +----------
    +app:
    +    new application name
    +fobj:
    +    file object containing the application to be installed
    +request:
    +    the global request object
    +filename:
    +    original filename of the `fobj`, required to determine extension
    +
    +Returns
    +-------
    +upname:
    +    name of the file where app is temporarily stored or `None` on failure
    +
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    app_uninstall(app, + request) +

    +
    source code  +
    + +
    +
    +Uninstalls the application.
    +
    +Parameters
    +----------
    +app:
    +    application name
    +request:
    +    the global request object
    +
    +Returns
    +-------
    +`True` on success, `False` on failure
    +
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    plugin_pack(app, + plugin_name, + request) +

    +
    source code  +
    + +
    +
    +Builds a w2p package for the application
    +
    +Parameters
    +----------
    +app:
    +    application name
    +plugin_name:
    +    the name of the plugin without plugin_ prefix
    +request:
    +    the current request app
    +
    +Returns
    +-------
    +filename:
    +    filename of the w2p file or None on error
    +
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    plugin_install(app, + fobj, + request, + filename) +

    +
    source code  +
    + +
    +
    +Installs an application:
    +
    +- Identifies file type by filename
    +- Writes `fobj` contents to the `../deposit/` folder
    +- Calls `w2p_unpack()` to do the job.
    +
    +Parameters
    +----------
    +app:
    +    new application name
    +fobj:
    +    file object containing the application to be installed
    +request:
    +    the global request object
    +filename:
    +    original filename of the `fobj`, required to determine extension
    +
    +Returns
    +-------
    +upname:
    +    name of the file where app is temporarily stored or `None` on failure
    +
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    check_new_version(myversion, + version_URL) +

    +
    source code  +
    + +
    +
    +Compares current web2py's version with the latest stable web2py version.
    +
    +Parameters
    +----------
    +myversion:
    +    the current version as stored in file `web2py/VERSION`
    +version_URL:
    +    the URL that contains the version of the latest stable release
    +
    +Returns
    +-------
    +state:
    +    `True` if upgrade available, `False` if current version if up-to-date,
    +    -1 on error
    +version:
    +    the most up-to-version available
    +
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    upgrade(request, + url='http://web2py.com') +

    +
    source code  +
    + +
    +
    +Upgrades web2py (src, osx, win) is a new version is posted.
    +It detects whether src, osx or win is running and downloads the right one
    +
    +Parameters
    +----------
    +request:
    +    the current request object, required to determine version and path
    +url:
    +    the incomplete url where to locate the latest web2py
    +    actual url is url+'/examples/static/web2py_(src|osx|win).zip'
    +
    +Returns
    +-------
    +    True on success, False on failure (network problem or old version)
    +
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.admin-pysrc.html Index: applications/examples/static/epydoc/web2py.gluon.admin-pysrc.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.admin-pysrc.html +++ applications/examples/static/epydoc/web2py.gluon.admin-pysrc.html @@ -0,0 +1,599 @@ + + + + + web2py.gluon.admin + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module admin + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Module web2py.gluon.admin

    +
    +  1  """ 
    +  2  This file is part of the web2py Web Framework 
    +  3  Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> 
    +  4  License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) 
    +  5   
    +  6  Utility functions for the Admin application 
    +  7  =========================================== 
    +  8  """ 
    +  9  import os 
    + 10  import sys 
    + 11  import traceback 
    + 12  import zipfile 
    + 13  import urllib 
    + 14  from shutil import rmtree 
    + 15  from utils import web2py_uuid 
    + 16  from fileutils import w2p_pack, w2p_unpack, w2p_pack_plugin, w2p_unpack_plugin 
    + 17  from fileutils import up, fix_newlines, abspath, recursive_unlink 
    + 18  from fileutils import read_file, write_file 
    + 19  from restricted import RestrictedError 
    + 20  from settings import global_settings 
    + 21   
    +
    22 -def apath(path='', r=None): +
    23 """ + 24 Builds a path inside an application folder + 25 + 26 Parameters + 27 ---------- + 28 path: + 29 path within the application folder + 30 r: + 31 the global request object + 32 + 33 """ + 34 + 35 opath = up(r.folder) + 36 while path[:3] == '../': + 37 (opath, path) = (up(opath), path[3:]) + 38 return os.path.join(opath, path).replace('\\', '/') +
    39 + 40 +
    41 -def app_pack(app, request): +
    42 """ + 43 Builds a w2p package for the application + 44 + 45 Parameters + 46 ---------- + 47 app: + 48 application name + 49 request: + 50 the global request object + 51 + 52 Returns + 53 ------- + 54 filename: + 55 filename of the w2p file or None on error + 56 """ + 57 try: + 58 app_cleanup(app, request) + 59 filename = apath('../deposit/%s.w2p' % app, request) + 60 w2p_pack(filename, apath(app, request)) + 61 return filename + 62 except Exception: + 63 return False +
    64 + 65 +
    66 -def app_pack_compiled(app, request): +
    67 """ + 68 Builds a w2p bytecode-compiled package for the application + 69 + 70 Parameters + 71 ---------- + 72 app: + 73 application name + 74 request: + 75 the global request object + 76 + 77 Returns + 78 ------- + 79 filename: + 80 filename of the w2p file or None on error + 81 """ + 82 + 83 try: + 84 filename = apath('../deposit/%s.w2p' % app, request) + 85 w2p_pack(filename, apath(app, request), compiled=True) + 86 return filename + 87 except Exception: + 88 return None +
    89 +
    90 -def app_cleanup(app, request): +
    91 """ + 92 Removes session, cache and error files + 93 + 94 Parameters + 95 ---------- + 96 app: + 97 application name + 98 request: + 99 the global request object +100 """ +101 r = True +102 +103 # Remove error files +104 path = apath('%s/errors/' % app, request) +105 if os.path.exists(path): +106 for f in os.listdir(path): +107 try: +108 if f[:1]!='.': os.unlink(os.path.join(path,f)) +109 except IOError: +110 r = False +111 +112 # Remove session files +113 path = apath('%s/sessions/' % app, request) +114 if os.path.exists(path): +115 for f in os.listdir(path): +116 try: +117 if f[:1]!='.': recursive_unlink(os.path.join(path,f)) +118 except IOError: +119 r = False +120 +121 # Remove cache files +122 path = apath('%s/sessions/' % app, request) +123 if os.path.exists(path): +124 for f in os.listdir(path): +125 try: +126 if f[:1]!='.': os.unlink(os.path.join(path,f)) +127 except IOError: +128 r = False +129 return r +
    130 +131 +
    132 -def app_compile(app, request): +
    133 """ +134 Compiles the application +135 +136 Parameters +137 ---------- +138 app: +139 application name +140 request: +141 the global request object +142 """ +143 from compileapp import compile_application, remove_compiled_application +144 folder = apath(app, request) +145 try: +146 compile_application(folder) +147 return None +148 except (Exception, RestrictedError): +149 tb = traceback.format_exc(sys.exc_info) +150 remove_compiled_application(folder) +151 return tb +
    152 +
    153 -def app_create(app, request,force=False,key=None): +
    154 """ +155 Create a copy of welcome.w2p (scaffolding) app +156 +157 Parameters +158 ---------- +159 app: +160 application name +161 request: +162 the global request object +163 +164 """ +165 try: +166 path = apath(app, request) +167 os.mkdir(path) +168 except: +169 if not force: +170 return False +171 try: +172 w2p_unpack('welcome.w2p', path) +173 for subfolder in ['models','views','controllers', 'databases', +174 'modules','cron','errors','sessions', +175 'languages','static','private','uploads']: +176 subpath = os.path.join(path,subfolder) +177 if not os.path.exists(subpath): +178 os.mkdir(subpath) +179 db = os.path.join(path, 'models', 'db.py') +180 if os.path.exists(db): +181 data = read_file(db) +182 data = data.replace('<your secret key>', +183 'sha512:'+(key or web2py_uuid())) +184 write_file(db, data) +185 return True +186 except: +187 rmtree(path) +188 return False +
    189 +190 +
    191 -def app_install(app, fobj, request, filename, overwrite=None): +
    192 """ +193 Installs an application: +194 +195 - Identifies file type by filename +196 - Writes `fobj` contents to the `../deposit/` folder +197 - Calls `w2p_unpack()` to do the job. +198 +199 Parameters +200 ---------- +201 app: +202 new application name +203 fobj: +204 file object containing the application to be installed +205 request: +206 the global request object +207 filename: +208 original filename of the `fobj`, required to determine extension +209 +210 Returns +211 ------- +212 upname: +213 name of the file where app is temporarily stored or `None` on failure +214 """ +215 did_mkdir = False +216 if filename[-4:] == '.w2p': +217 extension = 'w2p' +218 elif filename[-7:] == '.tar.gz': +219 extension = 'tar.gz' +220 else: +221 extension = 'tar' +222 upname = apath('../deposit/%s.%s' % (app, extension), request) +223 +224 try: +225 write_file(upname, fobj.read(), 'wb') +226 path = apath(app, request) +227 if not overwrite: +228 os.mkdir(path) +229 did_mkdir = True +230 w2p_unpack(upname, path) +231 if extension != 'tar': +232 os.unlink(upname) +233 fix_newlines(path) +234 return upname +235 except Exception: +236 if did_mkdir: +237 rmtree(path) +238 return False +
    239 +240 +
    241 -def app_uninstall(app, request): +
    242 """ +243 Uninstalls the application. +244 +245 Parameters +246 ---------- +247 app: +248 application name +249 request: +250 the global request object +251 +252 Returns +253 ------- +254 `True` on success, `False` on failure +255 """ +256 try: +257 # Hey App, this is your end... +258 path = apath(app, request) +259 rmtree(path) +260 return True +261 except Exception: +262 return False +
    263 +
    264 -def plugin_pack(app, plugin_name, request): +
    265 """ +266 Builds a w2p package for the application +267 +268 Parameters +269 ---------- +270 app: +271 application name +272 plugin_name: +273 the name of the plugin without plugin_ prefix +274 request: +275 the current request app +276 +277 Returns +278 ------- +279 filename: +280 filename of the w2p file or None on error +281 """ +282 try: +283 filename = apath('../deposit/web2py.plugin.%s.w2p' % plugin_name, request) +284 w2p_pack_plugin(filename, apath(app, request), plugin_name) +285 return filename +286 except Exception: +287 return False +
    288 +
    289 -def plugin_install(app, fobj, request, filename): +
    290 """ +291 Installs an application: +292 +293 - Identifies file type by filename +294 - Writes `fobj` contents to the `../deposit/` folder +295 - Calls `w2p_unpack()` to do the job. +296 +297 Parameters +298 ---------- +299 app: +300 new application name +301 fobj: +302 file object containing the application to be installed +303 request: +304 the global request object +305 filename: +306 original filename of the `fobj`, required to determine extension +307 +308 Returns +309 ------- +310 upname: +311 name of the file where app is temporarily stored or `None` on failure +312 """ +313 +314 upname = apath('../deposit/%s' % filename, request) +315 +316 try: +317 write_file(upname, fobj.read(), 'wb') +318 path = apath(app, request) +319 w2p_unpack_plugin(upname, path) +320 fix_newlines(path) +321 return upname +322 except Exception: +323 os.unlink(upname) +324 return False +
    325 +
    326 -def check_new_version(myversion, version_URL): +
    327 """ +328 Compares current web2py's version with the latest stable web2py version. +329 +330 Parameters +331 ---------- +332 myversion: +333 the current version as stored in file `web2py/VERSION` +334 version_URL: +335 the URL that contains the version of the latest stable release +336 +337 Returns +338 ------- +339 state: +340 `True` if upgrade available, `False` if current version if up-to-date, +341 -1 on error +342 version: +343 the most up-to-version available +344 """ +345 try: +346 from urllib import urlopen +347 version = urlopen(version_URL).read() +348 except Exception: +349 return -1, myversion +350 +351 if version > myversion: +352 return True, version +353 else: +354 return False, version +
    355 +
    356 -def unzip(filename, dir, subfolder=''): +
    357 """ +358 Unzips filename into dir (.zip only, no .gz etc) +359 if subfolder!='' it unzip only files in subfolder +360 """ +361 filename = abspath(filename) +362 if not zipfile.is_zipfile(filename): +363 raise RuntimeError, 'Not a valid zipfile' +364 zf = zipfile.ZipFile(filename) +365 if not subfolder.endswith('/'): +366 subfolder = subfolder + '/' +367 n = len(subfolder) +368 for name in sorted(zf.namelist()): +369 if not name.startswith(subfolder): +370 continue +371 #print name[n:] +372 if name.endswith('/'): +373 folder = os.path.join(dir,name[n:]) +374 if not os.path.exists(folder): +375 os.mkdir(folder) +376 else: +377 write_file(os.path.join(dir, name[n:]), zf.read(name), 'wb') +
    378 +379 +
    380 -def upgrade(request, url='http://web2py.com'): +
    381 """ +382 Upgrades web2py (src, osx, win) is a new version is posted. +383 It detects whether src, osx or win is running and downloads the right one +384 +385 Parameters +386 ---------- +387 request: +388 the current request object, required to determine version and path +389 url: +390 the incomplete url where to locate the latest web2py +391 actual url is url+'/examples/static/web2py_(src|osx|win).zip' +392 +393 Returns +394 ------- +395 True on success, False on failure (network problem or old version) +396 """ +397 web2py_version = request.env.web2py_version +398 gluon_parent = request.env.gluon_parent +399 if not gluon_parent.endswith('/'): +400 gluon_parent = gluon_parent + '/' +401 (check, version) = check_new_version(web2py_version, +402 url+'/examples/default/version') +403 if not check: +404 return (False, 'Already latest version') +405 if os.path.exists(os.path.join(gluon_parent, 'web2py.exe')): +406 version_type = 'win' +407 destination = gluon_parent +408 subfolder = 'web2py/' +409 elif gluon_parent.endswith('/Contents/Resources/'): +410 version_type = 'osx' +411 destination = gluon_parent[:-len('/Contents/Resources/')] +412 subfolder = 'web2py/web2py.app/' +413 else: +414 version_type = 'src' +415 destination = gluon_parent +416 subfolder = 'web2py/' +417 +418 full_url = url + '/examples/static/web2py_%s.zip' % version_type +419 filename = abspath('web2py_%s_downloaded.zip' % version_type) +420 file = None +421 try: +422 write_file(filename, urllib.urlopen(full_url).read(), 'wb') +423 except Exception,e: +424 return False, e +425 try: +426 unzip(filename, destination, subfolder) +427 return True, None +428 except Exception,e: +429 return False, e +
    430 +
    431 -def add_path_first(path): +
    432 sys.path = [path]+[p for p in sys.path if (not p==path and not p==(path+'/'))] +
    433 +
    435 if not global_settings.web2py_runtime_gae: +436 for path in ('applications', 'deposit', 'site-packages', 'logs'): +437 path = abspath(path, gluon=True) +438 if not os.path.exists(path): +439 os.mkdir(path) +440 paths = (global_settings.gluon_parent, abspath('site-packages', gluon=True), abspath('gluon', gluon=True), '') +441 [add_path_first(path) for path in paths] +
    442 +
    443 -def create_missing_app_folders(request): +
    444 if not global_settings.web2py_runtime_gae: +445 if request.folder not in global_settings.app_folders: +446 for subfolder in ('models', 'views', 'controllers', 'databases', +447 'modules', 'cron', 'errors', 'sessions', +448 'languages', 'static', 'private', 'uploads'): +449 path = os.path.join(request.folder, subfolder) +450 if not os.path.exists(path): +451 os.mkdir(path) +452 global_settings.app_folders.add(request.folder) +
    453 +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.cfs-module.html Index: applications/examples/static/epydoc/web2py.gluon.cfs-module.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.cfs-module.html +++ applications/examples/static/epydoc/web2py.gluon.cfs-module.html @@ -0,0 +1,244 @@ + + + + + web2py.gluon.cfs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module cfs + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Module cfs

    source code

    +This file is part of the web2py Web Framework Copyrighted by Massimo + Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html) +

    Functions required to execute app components

    + FOR INTERNAL USE ONLY

    + + + + + + + + + + +
    + + + + + +
    Functions[hide private]
    +
    +   + + + + + + +
    getcfs(key, + filename, + filter=1)
    + Caches the *filtered* file `filename` with `key` until the file is +modified.
    + source code + +
    + +
    + + + + + + + + + + + + +
    + + + + + +
    Variables[hide private]
    +
    +   + + cfs = {} +
    +   + + cfs_lock = thread.allocate_lock() +
    + + + + + + +
    + + + + + +
    Function Details[hide private]
    +
    + +
    + +
    + + +
    +

    getcfs(key, + filename, + filter=1) +

    +
    source code  +
    + +
    +
    +Caches the *filtered* file `filename` with `key` until the file is
    +modified.
    +
    +:param key: the cache key
    +:param filename: the file to cache
    +:param filter: is the function used for filtering. Normally `filename` is a
    +    .py file and `filter` is a function that bytecode compiles the file.
    +    In this way the bytecode compiled file is cached. (Default = None)
    +
    +This is used on Google App Engine since pyc files cannot be saved.
    +
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.cfs-pysrc.html Index: applications/examples/static/epydoc/web2py.gluon.cfs-pysrc.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.cfs-pysrc.html +++ applications/examples/static/epydoc/web2py.gluon.cfs-pysrc.html @@ -0,0 +1,177 @@ + + + + + web2py.gluon.cfs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module cfs + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Module web2py.gluon.cfs

    +
    + 1  #!/usr/bin/env python 
    + 2  # -*- coding: utf-8 -*- 
    + 3   
    + 4  """ 
    + 5  This file is part of the web2py Web Framework 
    + 6  Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> 
    + 7  License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) 
    + 8   
    + 9  Functions required to execute app components 
    +10  ============================================ 
    +11   
    +12  FOR INTERNAL USE ONLY 
    +13  """ 
    +14   
    +15  import os 
    +16  import stat 
    +17  import thread 
    +18  from fileutils import read_file 
    +19   
    +20  cfs = {}  # for speed-up 
    +21  cfs_lock = thread.allocate_lock()  # and thread safety 
    +22   
    +23   
    +
    24 -def getcfs(key, filename, filter=None): +
    25 """ +26 Caches the *filtered* file `filename` with `key` until the file is +27 modified. +28 +29 :param key: the cache key +30 :param filename: the file to cache +31 :param filter: is the function used for filtering. Normally `filename` is a +32 .py file and `filter` is a function that bytecode compiles the file. +33 In this way the bytecode compiled file is cached. (Default = None) +34 +35 This is used on Google App Engine since pyc files cannot be saved. +36 """ +37 t = os.stat(filename)[stat.ST_MTIME] +38 cfs_lock.acquire() +39 item = cfs.get(key, None) +40 cfs_lock.release() +41 if item and item[0] == t: +42 return item[1] +43 if not filter: +44 data = read_file(filename) +45 else: +46 data = filter() +47 cfs_lock.acquire() +48 cfs[key] = (t, data) +49 cfs_lock.release() +50 return data +
    51 +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.compileapp-module.html Index: applications/examples/static/epydoc/web2py.gluon.compileapp-module.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.compileapp-module.html +++ applications/examples/static/epydoc/web2py.gluon.compileapp-module.html @@ -0,0 +1,678 @@ + + + + + web2py.gluon.compileapp + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module compileapp + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Module compileapp

    source code

    +This file is part of the web2py Web Framework Copyrighted by Massimo + Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html) +

    Functions required to execute app components

    + FOR INTERNAL USE ONLY

    + + + + + + + + + + + + + +
    + + + + + +
    Classes[hide private]
    +
    +   + + mybuiltin
    + NOTE could simple use a dict and populate it, NOTE not sure if + this changes things though if monkey patching import..... +
    +   + + LoadFactory
    + Attention: this helper is new and experimental +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Functions[hide private]
    +
    +   + + + + + + +
    local_import_aux(name, + force=True, + app='welcome')
    + In apps, instead of importing a local module +(in applications/app/modules) with:: + + import a.b.c as d + +you should do:: + + d = local_import('a.b.c') + +or (to force a reload): + + d = local_import('a.b.c', reload=True) + +This prevents conflict between applications and un-necessary execs.
    + source code + +
    + +
    +   + + + + + + +
    build_environment(request, + response, + session, + store_current=True)
    + Build the environment dictionary into which web2py files are + executed.
    + source code + +
    + +
    +   + + + + + + +
    save_pyc(filename)
    + Bytecode compiles the file `filename`
    + source code + +
    + +
    +   + + + + + + +
    read_pyc(filename)
    + Read the code inside a bytecode compiled file if the MAGIC number + is compatible
    + source code + +
    + +
    +   + + + + + + +
    compile_views(folder)
    + Compiles all the views in the application specified by + `folder`
    + source code + +
    + +
    +   + + + + + + +
    compile_models(folder)
    + Compiles all the models in the application specified by + `folder`
    + source code + +
    + +
    +   + + + + + + +
    compile_controllers(folder)
    + Compiles all the controllers in the application specified by + `folder`
    + source code + +
    + +
    +   + + + + + + +
    run_models_in(environment)
    + Runs all models (in the app specified by the current folder) It + tries pre-compiled models first before compiling them.
    + source code + +
    + +
    +   + + + + + + +
    run_controller_in(controller, + function, + environment)
    + Runs the controller.function() (for the app specified by the + current folder).
    + source code + +
    + +
    +   + + + + + + +
    run_view_in(environment)
    + Executes the view for the requested action.
    + source code + +
    + +
    +   + + + + + + +
    remove_compiled_application(folder)
    + Deletes the folder `compiled` containing the compiled + application.
    + source code + +
    + +
    +   + + + + + + +
    compile_application(folder)
    + Compiles all models, views, controller for the application in + `folder`.
    + source code + +
    + +
    +   + + + + + + +
    test()
    + Example:
    + source code + +
    + +
    + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Variables[hide private]
    +
    +   + + logger = logging.getLogger("web2py") +
    +   + + is_gae = 1
    + PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    +   + + is_jython = True +
    +   + + TEST_CODE = '\ndef _TEST():\n import doctest, sys, cStringI... +
    + + + + + + +
    + + + + + +
    Function Details[hide private]
    +
    + +
    + +
    + + +
    +

    local_import_aux(name, + force=True, + app='welcome') +

    +
    source code  +
    + +
    +
    +In apps, instead of importing a local module
    +(in applications/app/modules) with::
    +
    +   import a.b.c as d
    +
    +you should do::
    +
    +   d = local_import('a.b.c')
    +
    +or (to force a reload):
    +
    +   d = local_import('a.b.c', reload=True)
    +
    +This prevents conflict between applications and un-necessary execs.
    +It can be used to import any module, including regular Python modules.
    +
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    read_pyc(filename) +

    +
    source code  +
    + +

    Read the code inside a bytecode compiled file if the MAGIC number is + compatible

    + :returns: a code object +
    +
    +
    +
    + +
    + +
    + + +
    +

    run_controller_in(controller, + function, + environment) +

    +
    source code  +
    + + Runs the controller.function() (for the app specified by the current + folder). It tries pre-compiled controller_function.pyc first before + compiling it. +
    +
    +
    +
    + +
    + +
    + + +
    +

    run_view_in(environment) +

    +
    source code  +
    + + Executes the view for the requested action. The view is the one + specified in `response.view` or determined by the url or + `view/generic.extension` It tries the pre-compiled + views_controller_function.pyc before compiling it. +
    +
    +
    +
    + +
    + +
    + + +
    +

    test() +

    +
    source code  +
    + + Example: +
    +   >>> import traceback, types
    +   >>> environment={'x':1}
    +   >>> open('a.py', 'w').write('print 1/x')
    +   >>> save_pyc('a.py')
    +   >>> os.unlink('a.py')
    +   >>> if type(read_pyc('a.pyc'))==types.CodeType: print 'code'
    +   code
    +   >>> exec read_pyc('a.pyc') in environment
    +   1
    +
    +
    +
    +
    +
    +
    + + + + + + +
    + + + + + +
    Variables Details[hide private]
    +
    + +
    + +
    +

    TEST_CODE

    + +
    +
    +
    +
    Value:
    +
    +'''
    +def _TEST():
    +    import doctest, sys, cStringIO, types, cgi, gluon.fileutils
    +    if not gluon.fileutils.check_credentials(request):
    +        raise HTTP(401, web2py_error=\'invalid credentials\')
    +    stdout = sys.stdout
    +    html = \'<h2>Testing controller "%s.py" ... done.</h2><br/>\\n\' \\
    +\
    +...
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.compileapp-pysrc.html Index: applications/examples/static/epydoc/web2py.gluon.compileapp-pysrc.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.compileapp-pysrc.html +++ applications/examples/static/epydoc/web2py.gluon.compileapp-pysrc.html @@ -0,0 +1,814 @@ + + + + + web2py.gluon.compileapp + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module compileapp + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Module web2py.gluon.compileapp

    +
    +  1  #!/usr/bin/env python 
    +  2  # -*- coding: utf-8 -*- 
    +  3   
    +  4  """ 
    +  5  This file is part of the web2py Web Framework 
    +  6  Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> 
    +  7  License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) 
    +  8   
    +  9  Functions required to execute app components 
    + 10  ============================================ 
    + 11   
    + 12  FOR INTERNAL USE ONLY 
    + 13  """ 
    + 14   
    + 15  import re 
    + 16  import sys 
    + 17  import fnmatch 
    + 18  import os 
    + 19  import copy 
    + 20  import random 
    + 21  import __builtin__ 
    + 22  from storage import Storage, List 
    + 23  from template import parse_template 
    + 24  from restricted import restricted, compile2 
    + 25  from fileutils import mktree, listdir, read_file, write_file 
    + 26  from myregex import regex_expose 
    + 27  from languages import translator 
    + 28  from dal import BaseAdapter, SQLDB, SQLField, DAL, Field 
    + 29  from sqlhtml import SQLFORM, SQLTABLE 
    + 30  from cache import Cache 
    + 31  from globals import current 
    + 32  import settings 
    + 33  from cfs import getcfs 
    + 34  import html 
    + 35  import validators 
    + 36  from http import HTTP, redirect 
    + 37  import marshal 
    + 38  import shutil 
    + 39  import imp 
    + 40  import logging 
    + 41  logger = logging.getLogger("web2py") 
    + 42  import rewrite 
    + 43   
    + 44  try: 
    + 45      import py_compile 
    + 46  except: 
    + 47      logger.warning('unable to import py_compile') 
    + 48   
    + 49  is_gae = settings.global_settings.web2py_runtime_gae 
    + 50  is_jython = settings.global_settings.is_jython = 'java' in sys.platform.lower() or hasattr(sys, 'JYTHON_JAR') or str(sys.copyright).find('Jython') > 0 
    + 51   
    + 52  TEST_CODE = \ 
    + 53      r""" 
    + 54  def _TEST(): 
    + 55      import doctest, sys, cStringIO, types, cgi, gluon.fileutils 
    + 56      if not gluon.fileutils.check_credentials(request): 
    + 57          raise HTTP(401, web2py_error='invalid credentials') 
    + 58      stdout = sys.stdout 
    + 59      html = '<h2>Testing controller "%s.py" ... done.</h2><br/>\n' \ 
    + 60          % request.controller 
    + 61      for key in sorted([key for key in globals() if not key in __symbols__+['_TEST']]): 
    + 62          eval_key = eval(key) 
    + 63          if type(eval_key) == types.FunctionType: 
    + 64              number_doctests = sum([len(ds.examples) for ds in doctest.DocTestFinder().find(eval_key)]) 
    + 65              if number_doctests>0: 
    + 66                  sys.stdout = cStringIO.StringIO() 
    + 67                  name = '%s/controllers/%s.py in %s.__doc__' \ 
    + 68                      % (request.folder, request.controller, key) 
    + 69                  doctest.run_docstring_examples(eval_key, 
    + 70                      globals(), False, name=name) 
    + 71                  report = sys.stdout.getvalue().strip() 
    + 72                  if report: 
    + 73                      pf = 'failed' 
    + 74                  else: 
    + 75                      pf = 'passed' 
    + 76                  html += '<h3 class="%s">Function %s [%s]</h3>\n' \ 
    + 77                      % (pf, key, pf) 
    + 78                  if report: 
    + 79                      html += CODE(report, language='web2py', \ 
    + 80                          link='/examples/global/vars/').xml() 
    + 81                  html += '<br/>\n' 
    + 82              else: 
    + 83                  html += \ 
    + 84                      '<h3 class="nodoctests">Function %s [no doctests]</h3><br/>\n' \ 
    + 85                      % (key) 
    + 86      response._vars = html 
    + 87      sys.stdout = stdout 
    + 88  _TEST() 
    + 89  """ 
    + 90   
    +
    91 -class mybuiltin(object): +
    92 """ + 93 NOTE could simple use a dict and populate it, + 94 NOTE not sure if this changes things though if monkey patching import..... + 95 """ + 96 #__builtins__ +
    97 - def __getitem__(self, key): +
    98 try: + 99 return getattr(__builtin__, key) +100 except AttributeError: +101 raise KeyError, key +
    102 - def __setitem__(self, key, value): +
    103 setattr(self, key, value) +
    104 +
    105 -class LoadFactory(object): +
    106 """ +107 Attention: this helper is new and experimental +108 """ +
    109 - def __init__(self,environment): +
    110 self.environment = environment +
    111 - def __call__(self, c=None, f='index', args=[], vars={}, +112 extension=None, target=None,ajax=False,ajax_trap=False, +113 url=None,user_signature=False, content='loading...',**attr): +
    114 import globals +115 target = target or 'c'+str(random.random())[2:] +116 attr['_id']=target +117 request = self.environment['request'] +118 if not isinstance(vars,Storage): +119 vars = Storage(vars) +120 if '.' in f: +121 f, extension = f.split('.',1) +122 if url or ajax: +123 url = url or html.URL(request.application, c, f, r=request, +124 args=args, vars=vars, extension=extension, +125 user_signature=user_signature) +126 script = html.SCRIPT('web2py_component("%s","%s")' % (url, target), +127 _type="text/javascript") +128 return html.TAG[''](script, html.DIV(content,**attr)) +129 else: +130 if not isinstance(args,(list,tuple)): +131 args = [args] +132 c = c or request.controller +133 +134 other_request = Storage() +135 for key, value in request.items(): +136 other_request[key] = value +137 other_request['env'] = Storage() +138 for key, value in request.env.items(): +139 other_request.env['key'] = value +140 other_request.controller = c +141 other_request.function = f +142 other_request.extension = extension or request.extension +143 other_request.args = List(args) +144 other_request.vars = vars +145 other_request.get_vars = vars +146 other_request.post_vars = Storage() +147 other_response = globals.Response() +148 other_request.env.path_info = '/' + \ +149 '/'.join([request.application,c,f] + \ +150 map(str, other_request.args)) +151 other_request.env.query_string = \ +152 vars and html.URL(vars=vars).split('?')[1] or '' +153 other_request.env.http_web2py_component_location = \ +154 request.env.path_info +155 other_request.cid = target +156 other_request.env.http_web2py_component_element = target +157 other_response.view = '%s/%s.%s' % (c,f, other_request.extension) +158 other_environment = copy.copy(self.environment) +159 other_response._view_environment = other_environment +160 other_response.generic_patterns = \ +161 copy.copy(current.response.generic_patterns) +162 other_environment['request'] = other_request +163 other_environment['response'] = other_response +164 +165 ## some magic here because current are thread-locals +166 +167 original_request, current.request = current.request, other_request +168 original_response, current.response = current.response, other_response +169 page = run_controller_in(c, f, other_environment) +170 if isinstance(page, dict): +171 other_response._vars = page +172 for key in page: +173 other_response._view_environment[key] = page[key] +174 run_view_in(other_response._view_environment) +175 page = other_response.body.getvalue() +176 current.request, current.response = original_request, original_response +177 js = None +178 if ajax_trap: +179 link = html.URL(request.application, c, f, r=request, +180 args=args, vars=vars, extension=extension, +181 user_signature=user_signature) +182 js = "web2py_trap_form('%s','%s');" % (link, target) +183 script = js and html.SCRIPT(js,_type="text/javascript") or '' +184 return html.TAG[''](html.DIV(html.XML(page),**attr),script) +
    185 +186 +
    187 -def local_import_aux(name, force=False, app='welcome'): +
    188 """ +189 In apps, instead of importing a local module +190 (in applications/app/modules) with:: +191 +192 import a.b.c as d +193 +194 you should do:: +195 +196 d = local_import('a.b.c') +197 +198 or (to force a reload): +199 +200 d = local_import('a.b.c', reload=True) +201 +202 This prevents conflict between applications and un-necessary execs. +203 It can be used to import any module, including regular Python modules. +204 """ +205 items = name.replace('/','.') +206 name = "applications.%s.modules.%s" % (app, items) +207 module = __import__(name) +208 for item in name.split(".")[1:]: +209 module = getattr(module, item) +210 if force: +211 reload(module) +212 return module +
    213 +214 +215 """ +216 OLD IMPLEMENTATION: +217 items = name.replace('/','.').split('.') +218 filename, modulepath = items[-1], os.path.join(apath,'modules',*items[:-1]) +219 imp.acquire_lock() +220 try: +221 file=None +222 (file,path,desc) = imp.find_module(filename,[modulepath]+sys.path) +223 if not path in sys.modules or reload: +224 if is_gae: +225 module={} +226 execfile(path,{},module) +227 module=Storage(module) +228 else: +229 module = imp.load_module(path,file,path,desc) +230 sys.modules[path] = module +231 else: +232 module = sys.modules[path] +233 except Exception, e: +234 module = None +235 if file: +236 file.close() +237 imp.release_lock() +238 if not module: +239 raise ImportError, "cannot find module %s in %s" % (filename, modulepath) +240 return module +241 """ +242 +
    243 -def build_environment(request, response, session, store_current=True): +
    244 """ +245 Build the environment dictionary into which web2py files are executed. +246 """ +247 +248 environment = {} +249 for key in html.__all__: +250 environment[key] = getattr(html, key) +251 for key in validators.__all__: +252 environment[key] = getattr(validators, key) +253 if not request.env: +254 request.env = Storage() +255 +256 t = environment['T'] = translator(request) +257 c = environment['cache'] = Cache(request) +258 if store_current: +259 current.request = request +260 current.response = response +261 current.session = session +262 current.T = t +263 current.cache = c +264 +265 global __builtins__ +266 if is_jython: # jython hack +267 __builtins__ = mybuiltin() +268 else: +269 __builtins__['__import__'] = __builtin__.__import__ +270 environment['__builtins__'] = __builtins__ +271 environment['HTTP'] = HTTP +272 environment['redirect'] = redirect +273 environment['request'] = request +274 environment['response'] = response +275 environment['session'] = session +276 environment['DAL'] = DAL +277 environment['Field'] = Field +278 environment['SQLDB'] = SQLDB # for backward compatibility +279 environment['SQLField'] = SQLField # for backward compatibility +280 environment['SQLFORM'] = SQLFORM +281 environment['SQLTABLE'] = SQLTABLE +282 environment['LOAD'] = LoadFactory(environment) +283 environment['local_import'] = \ +284 lambda name, reload=False, app=request.application:\ +285 local_import_aux(name,reload,app) +286 BaseAdapter.set_folder(os.path.join(request.folder, 'databases')) +287 response._view_environment = copy.copy(environment) +288 return environment +
    289 +290 +
    291 -def save_pyc(filename): +
    292 """ +293 Bytecode compiles the file `filename` +294 """ +295 py_compile.compile(filename) +
    296 +297 +
    298 -def read_pyc(filename): +
    299 """ +300 Read the code inside a bytecode compiled file if the MAGIC number is +301 compatible +302 +303 :returns: a code object +304 """ +305 data = read_file(filename, 'rb') +306 if not is_gae and data[:4] != imp.get_magic(): +307 raise SystemError, 'compiled code is incompatible' +308 return marshal.loads(data[8:]) +
    309 +310 +
    311 -def compile_views(folder): +
    312 """ +313 Compiles all the views in the application specified by `folder` +314 """ +315 +316 path = os.path.join(folder, 'views') +317 for file in listdir(path, '^[\w/]+\.\w+$'): +318 data = parse_template(file, path) +319 filename = ('views/%s.py' % file).replace('/', '_').replace('\\', '_') +320 filename = os.path.join(folder, 'compiled', filename) +321 write_file(filename, data) +322 save_pyc(filename) +323 os.unlink(filename) +
    324 +325 +
    326 -def compile_models(folder): +
    327 """ +328 Compiles all the models in the application specified by `folder` +329 """ +330 +331 path = os.path.join(folder, 'models') +332 for file in listdir(path, '.+\.py$'): +333 data = read_file(os.path.join(path, file)) +334 filename = os.path.join(folder, 'compiled','models',file) +335 mktree(filename) +336 write_file(filename, data) +337 save_pyc(filename) +338 os.unlink(filename) +
    339 +340 +
    341 -def compile_controllers(folder): +
    342 """ +343 Compiles all the controllers in the application specified by `folder` +344 """ +345 +346 path = os.path.join(folder, 'controllers') +347 for file in listdir(path, '.+\.py$'): +348 ### why is this here? save_pyc(os.path.join(path, file)) +349 data = read_file(os.path.join(path,file)) +350 exposed = regex_expose.findall(data) +351 for function in exposed: +352 command = data + "\nresponse._vars=response._caller(%s)\n" % \ +353 function +354 filename = os.path.join(folder, 'compiled', ('controllers/' +355 + file[:-3]).replace('/', '_') +356 + '_' + function + '.py') +357 write_file(filename, command) +358 save_pyc(filename) +359 os.unlink(filename) +
    360 +361 +
    362 -def run_models_in(environment): +
    363 """ +364 Runs all models (in the app specified by the current folder) +365 It tries pre-compiled models first before compiling them. +366 """ +367 +368 folder = environment['request'].folder +369 c = environment['request'].controller +370 f = environment['request'].function +371 cpath = os.path.join(folder, 'compiled') +372 if os.path.exists(cpath): +373 for model in listdir(cpath, '^models_\w+\.pyc$', 0): +374 restricted(read_pyc(model), environment, layer=model) +375 path = os.path.join(cpath, 'models') +376 models = listdir(path, '^\w+\.pyc$',0,sort=False) +377 compiled=True +378 else: +379 path = os.path.join(folder, 'models') +380 models = listdir(path, '^\w+\.py$',0,sort=False) +381 compiled=False +382 paths = (path, os.path.join(path,c), os.path.join(path,c,f)) +383 for model in models: +384 if not os.path.split(model)[0] in paths and c!='appadmin': +385 continue +386 elif compiled: +387 code = read_pyc(model) +388 elif is_gae: +389 code = getcfs(model, model, +390 lambda: compile2(read_file(model), model)) +391 else: +392 code = getcfs(model, model, None) +393 restricted(code, environment, layer=model) +
    394 +395 +
    396 -def run_controller_in(controller, function, environment): +
    397 """ +398 Runs the controller.function() (for the app specified by +399 the current folder). +400 It tries pre-compiled controller_function.pyc first before compiling it. +401 """ +402 +403 # if compiled should run compiled! +404 +405 folder = environment['request'].folder +406 path = os.path.join(folder, 'compiled') +407 badc = 'invalid controller (%s/%s)' % (controller, function) +408 badf = 'invalid function (%s/%s)' % (controller, function) +409 if os.path.exists(path): +410 filename = os.path.join(path, 'controllers_%s_%s.pyc' +411 % (controller, function)) +412 if not os.path.exists(filename): +413 raise HTTP(404, +414 rewrite.thread.routes.error_message % badf, +415 web2py_error=badf) +416 restricted(read_pyc(filename), environment, layer=filename) +417 elif function == '_TEST': +418 # TESTING: adjust the path to include site packages +419 from settings import global_settings +420 from admin import abspath, add_path_first +421 paths = (global_settings.gluon_parent, abspath('site-packages', gluon=True), abspath('gluon', gluon=True), '') +422 [add_path_first(path) for path in paths] +423 # TESTING END +424 +425 filename = os.path.join(folder, 'controllers/%s.py' +426 % controller) +427 if not os.path.exists(filename): +428 raise HTTP(404, +429 rewrite.thread.routes.error_message % badc, +430 web2py_error=badc) +431 environment['__symbols__'] = environment.keys() +432 code = read_file(filename) +433 code += TEST_CODE +434 restricted(code, environment, layer=filename) +435 else: +436 filename = os.path.join(folder, 'controllers/%s.py' +437 % controller) +438 if not os.path.exists(filename): +439 raise HTTP(404, +440 rewrite.thread.routes.error_message % badc, +441 web2py_error=badc) +442 code = read_file(filename) +443 exposed = regex_expose.findall(code) +444 if not function in exposed: +445 raise HTTP(404, +446 rewrite.thread.routes.error_message % badf, +447 web2py_error=badf) +448 code = "%s\nresponse._vars=response._caller(%s)\n" % (code, function) +449 if is_gae: +450 layer = filename + ':' + function +451 code = getcfs(layer, filename, lambda: compile2(code,layer)) +452 restricted(code, environment, filename) +453 response = environment['response'] +454 vars=response._vars +455 if response.postprocessing: +456 for p in response.postprocessing: +457 vars = p(vars) +458 if isinstance(vars,unicode): +459 vars = vars.encode('utf8') +460 if hasattr(vars,'xml'): +461 vars = vars.xml() +462 return vars +
    463 +
    464 -def run_view_in(environment): +
    465 """ +466 Executes the view for the requested action. +467 The view is the one specified in `response.view` or determined by the url +468 or `view/generic.extension` +469 It tries the pre-compiled views_controller_function.pyc before compiling it. +470 """ +471 +472 request = environment['request'] +473 response = environment['response'] +474 folder = request.folder +475 path = os.path.join(folder, 'compiled') +476 badv = 'invalid view (%s)' % response.view +477 patterns = response.generic_patterns or [] +478 regex = re.compile('|'.join(fnmatch.translate(r) for r in patterns)) +479 short_action = '%(controller)s/%(function)s.%(extension)s' % request +480 allow_generic = patterns and regex.search(short_action) +481 if not isinstance(response.view, str): +482 ccode = parse_template(response.view, os.path.join(folder, 'views'), +483 context=environment) +484 restricted(ccode, environment, 'file stream') +485 elif os.path.exists(path): +486 x = response.view.replace('/', '_') +487 files = ['views_%s.pyc' % x] +488 if allow_generic: +489 files.append('views_generic.%s.pyc' % request.extension) +490 # for backward compatibility +491 if request.extension == 'html': +492 files.append('views_%s.pyc' % x[:-5]) +493 if allow_generic: +494 files.append('views_generic.pyc') +495 # end backward compatibility code +496 for f in files: +497 filename = os.path.join(path,f) +498 if os.path.exists(filename): +499 code = read_pyc(filename) +500 restricted(code, environment, layer=filename) +501 return +502 raise HTTP(404, +503 rewrite.thread.routes.error_message % badv, +504 web2py_error=badv) +505 else: +506 filename = os.path.join(folder, 'views', response.view) +507 if not os.path.exists(filename) and allow_generic: +508 response.view = 'generic.' + request.extension +509 filename = os.path.join(folder, 'views', response.view) +510 if not os.path.exists(filename): +511 raise HTTP(404, +512 rewrite.thread.routes.error_message % badv, +513 web2py_error=badv) +514 layer = filename +515 if is_gae: +516 ccode = getcfs(layer, filename, +517 lambda: compile2(parse_template(response.view, +518 os.path.join(folder, 'views'), +519 context=environment),layer)) +520 else: +521 ccode = parse_template(response.view, +522 os.path.join(folder, 'views'), +523 context=environment) +524 restricted(ccode, environment, layer) +
    525 +
    526 -def remove_compiled_application(folder): +
    527 """ +528 Deletes the folder `compiled` containing the compiled application. +529 """ +530 try: +531 shutil.rmtree(os.path.join(folder, 'compiled')) +532 path = os.path.join(folder, 'controllers') +533 for file in listdir(path,'.*\.pyc$',drop=False): +534 os.unlink(file) +535 except OSError: +536 pass +
    537 +538 +
    539 -def compile_application(folder): +
    540 """ +541 Compiles all models, views, controller for the application in `folder`. +542 """ +543 remove_compiled_application(folder) +544 os.mkdir(os.path.join(folder, 'compiled')) +545 compile_models(folder) +546 compile_controllers(folder) +547 compile_views(folder) +
    548 +549 +
    550 -def test(): +
    551 """ +552 Example:: +553 +554 >>> import traceback, types +555 >>> environment={'x':1} +556 >>> open('a.py', 'w').write('print 1/x') +557 >>> save_pyc('a.py') +558 >>> os.unlink('a.py') +559 >>> if type(read_pyc('a.pyc'))==types.CodeType: print 'code' +560 code +561 >>> exec read_pyc('a.pyc') in environment +562 1 +563 """ +564 +565 return +
    566 +567 +568 if __name__ == '__main__': +569 import doctest +570 doctest.testmod() +571 +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.compileapp.LoadFactory-class.html Index: applications/examples/static/epydoc/web2py.gluon.compileapp.LoadFactory-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.compileapp.LoadFactory-class.html +++ applications/examples/static/epydoc/web2py.gluon.compileapp.LoadFactory-class.html @@ -0,0 +1,275 @@ + + + + + web2py.gluon.compileapp.LoadFactory + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module compileapp :: + Class LoadFactory + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class LoadFactory

    source code

    +
    +object --+
    +         |
    +        LoadFactory
    +
    + +
    +Attention: this helper is new and experimental

    + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + environment)
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    __call__(self, + c=1, + f='index', + args=[], + vars={}, + extension=1, + target=1, + ajax=True, + ajax_trap=True, + url=1, + user_signature=True, + content='loading...', + **attr) + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + environment) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.compileapp.mybuiltin-class.html Index: applications/examples/static/epydoc/web2py.gluon.compileapp.mybuiltin-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.compileapp.mybuiltin-class.html +++ applications/examples/static/epydoc/web2py.gluon.compileapp.mybuiltin-class.html @@ -0,0 +1,220 @@ + + + + + web2py.gluon.compileapp.mybuiltin + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module compileapp :: + Class mybuiltin + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class mybuiltin

    source code

    +
    +object --+
    +         |
    +        mybuiltin
    +
    + +
    +NOTE could simple use a dict and populate it, NOTE not sure if this + changes things though if monkey patching import.....

    + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __getitem__(self, + key) + source code + +
    + +
    +   + + + + + + +
    __setitem__(self, + key, + value) + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __init__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.contenttype-module.html Index: applications/examples/static/epydoc/web2py.gluon.contenttype-module.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.contenttype-module.html +++ applications/examples/static/epydoc/web2py.gluon.contenttype-module.html @@ -0,0 +1,236 @@ + + + + + web2py.gluon.contenttype + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module contenttype + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Module contenttype

    source code

    +

    This file is part of the web2py Web Framework Copyrighted by Massimo + Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)

    + CONTENT_TYPE dictionary created against freedesktop.org' shared mime + info database version 0.70.

    + + + + + + + + + + +
    + + + + + +
    Functions[hide private]
    +
    +   + + + + + + +
    contenttype(filename, + default='text/plain')
    + Returns the Content-Type string matching extension of the given + filename.
    + source code + +
    + +
    + + + + + + + + + +
    + + + + + +
    Variables[hide private]
    +
    +   + + CONTENT_TYPE = {'.123': 'application/vnd.lotus-1-2-3', '.3ds':... +
    + + + + + + +
    + + + + + +
    Variables Details[hide private]
    +
    + +
    + +
    +

    CONTENT_TYPE

    + +
    +
    +
    +
    Value:
    +
    +{'.123': 'application/vnd.lotus-1-2-3',
    + '.3ds': 'image/x-3ds',
    + '.3g2': 'video/3gpp',
    + '.3ga': 'video/3gpp',
    + '.3gp': 'video/3gpp',
    + '.3gpp': 'video/3gpp',
    + '.602': 'application/x-t602',
    + '.669': 'audio/x-mod',
    +...
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.contenttype-pysrc.html Index: applications/examples/static/epydoc/web2py.gluon.contenttype-pysrc.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.contenttype-pysrc.html +++ applications/examples/static/epydoc/web2py.gluon.contenttype-pysrc.html @@ -0,0 +1,840 @@ + + + + + web2py.gluon.contenttype + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module contenttype + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Module web2py.gluon.contenttype

    +
    +  1  #!/usr/bin/env python 
    +  2  # -*- coding: utf-8 -*- 
    +  3   
    +  4  """ 
    +  5  This file is part of the web2py Web Framework 
    +  6  Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> 
    +  7  License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) 
    +  8   
    +  9  CONTENT_TYPE dictionary created against freedesktop.org' shared mime info 
    + 10  database version 0.70. 
    + 11  """ 
    + 12   
    + 13  __all__ = ['contenttype'] 
    + 14   
    + 15  CONTENT_TYPE = { 
    + 16      '.load': 'text/html', 
    + 17      '.123': 'application/vnd.lotus-1-2-3', 
    + 18      '.3ds': 'image/x-3ds', 
    + 19      '.3g2': 'video/3gpp', 
    + 20      '.3ga': 'video/3gpp', 
    + 21      '.3gp': 'video/3gpp', 
    + 22      '.3gpp': 'video/3gpp', 
    + 23      '.602': 'application/x-t602', 
    + 24      '.669': 'audio/x-mod', 
    + 25      '.7z': 'application/x-7z-compressed', 
    + 26      '.a': 'application/x-archive', 
    + 27      '.aac': 'audio/mp4', 
    + 28      '.abw': 'application/x-abiword', 
    + 29      '.abw.crashed': 'application/x-abiword', 
    + 30      '.abw.gz': 'application/x-abiword', 
    + 31      '.ac3': 'audio/ac3', 
    + 32      '.ace': 'application/x-ace', 
    + 33      '.adb': 'text/x-adasrc', 
    + 34      '.ads': 'text/x-adasrc', 
    + 35      '.afm': 'application/x-font-afm', 
    + 36      '.ag': 'image/x-applix-graphics', 
    + 37      '.ai': 'application/illustrator', 
    + 38      '.aif': 'audio/x-aiff', 
    + 39      '.aifc': 'audio/x-aiff', 
    + 40      '.aiff': 'audio/x-aiff', 
    + 41      '.al': 'application/x-perl', 
    + 42      '.alz': 'application/x-alz', 
    + 43      '.amr': 'audio/amr', 
    + 44      '.ani': 'application/x-navi-animation', 
    + 45      '.anim[1-9j]': 'video/x-anim', 
    + 46      '.anx': 'application/annodex', 
    + 47      '.ape': 'audio/x-ape', 
    + 48      '.arj': 'application/x-arj', 
    + 49      '.arw': 'image/x-sony-arw', 
    + 50      '.as': 'application/x-applix-spreadsheet', 
    + 51      '.asc': 'text/plain', 
    + 52      '.asf': 'video/x-ms-asf', 
    + 53      '.asp': 'application/x-asp', 
    + 54      '.ass': 'text/x-ssa', 
    + 55      '.asx': 'audio/x-ms-asx', 
    + 56      '.atom': 'application/atom+xml', 
    + 57      '.au': 'audio/basic', 
    + 58      '.avi': 'video/x-msvideo', 
    + 59      '.aw': 'application/x-applix-word', 
    + 60      '.awb': 'audio/amr-wb', 
    + 61      '.awk': 'application/x-awk', 
    + 62      '.axa': 'audio/annodex', 
    + 63      '.axv': 'video/annodex', 
    + 64      '.bak': 'application/x-trash', 
    + 65      '.bcpio': 'application/x-bcpio', 
    + 66      '.bdf': 'application/x-font-bdf', 
    + 67      '.bib': 'text/x-bibtex', 
    + 68      '.bin': 'application/octet-stream', 
    + 69      '.blend': 'application/x-blender', 
    + 70      '.blender': 'application/x-blender', 
    + 71      '.bmp': 'image/bmp', 
    + 72      '.bz': 'application/x-bzip', 
    + 73      '.bz2': 'application/x-bzip', 
    + 74      '.c': 'text/x-csrc', 
    + 75      '.c++': 'text/x-c++src', 
    + 76      '.cab': 'application/vnd.ms-cab-compressed', 
    + 77      '.cb7': 'application/x-cb7', 
    + 78      '.cbr': 'application/x-cbr', 
    + 79      '.cbt': 'application/x-cbt', 
    + 80      '.cbz': 'application/x-cbz', 
    + 81      '.cc': 'text/x-c++src', 
    + 82      '.cdf': 'application/x-netcdf', 
    + 83      '.cdr': 'application/vnd.corel-draw', 
    + 84      '.cer': 'application/x-x509-ca-cert', 
    + 85      '.cert': 'application/x-x509-ca-cert', 
    + 86      '.cgm': 'image/cgm', 
    + 87      '.chm': 'application/x-chm', 
    + 88      '.chrt': 'application/x-kchart', 
    + 89      '.class': 'application/x-java', 
    + 90      '.cls': 'text/x-tex', 
    + 91      '.cmake': 'text/x-cmake', 
    + 92      '.cpio': 'application/x-cpio', 
    + 93      '.cpio.gz': 'application/x-cpio-compressed', 
    + 94      '.cpp': 'text/x-c++src', 
    + 95      '.cr2': 'image/x-canon-cr2', 
    + 96      '.crt': 'application/x-x509-ca-cert', 
    + 97      '.crw': 'image/x-canon-crw', 
    + 98      '.cs': 'text/x-csharp', 
    + 99      '.csh': 'application/x-csh', 
    +100      '.css': 'text/css', 
    +101      '.cssl': 'text/css', 
    +102      '.csv': 'text/csv', 
    +103      '.cue': 'application/x-cue', 
    +104      '.cur': 'image/x-win-bitmap', 
    +105      '.cxx': 'text/x-c++src', 
    +106      '.d': 'text/x-dsrc', 
    +107      '.dar': 'application/x-dar', 
    +108      '.dbf': 'application/x-dbf', 
    +109      '.dc': 'application/x-dc-rom', 
    +110      '.dcl': 'text/x-dcl', 
    +111      '.dcm': 'application/dicom', 
    +112      '.dcr': 'image/x-kodak-dcr', 
    +113      '.dds': 'image/x-dds', 
    +114      '.deb': 'application/x-deb', 
    +115      '.der': 'application/x-x509-ca-cert', 
    +116      '.desktop': 'application/x-desktop', 
    +117      '.dia': 'application/x-dia-diagram', 
    +118      '.diff': 'text/x-patch', 
    +119      '.divx': 'video/x-msvideo', 
    +120      '.djv': 'image/vnd.djvu', 
    +121      '.djvu': 'image/vnd.djvu', 
    +122      '.dng': 'image/x-adobe-dng', 
    +123      '.doc': 'application/msword', 
    +124      '.docbook': 'application/docbook+xml', 
    +125      '.docm': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 
    +126      '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 
    +127      '.dot': 'text/vnd.graphviz', 
    +128      '.dsl': 'text/x-dsl', 
    +129      '.dtd': 'application/xml-dtd', 
    +130      '.dtx': 'text/x-tex', 
    +131      '.dv': 'video/dv', 
    +132      '.dvi': 'application/x-dvi', 
    +133      '.dvi.bz2': 'application/x-bzdvi', 
    +134      '.dvi.gz': 'application/x-gzdvi', 
    +135      '.dwg': 'image/vnd.dwg', 
    +136      '.dxf': 'image/vnd.dxf', 
    +137      '.e': 'text/x-eiffel', 
    +138      '.egon': 'application/x-egon', 
    +139      '.eif': 'text/x-eiffel', 
    +140      '.el': 'text/x-emacs-lisp', 
    +141      '.emf': 'image/x-emf', 
    +142      '.emp': 'application/vnd.emusic-emusic_package', 
    +143      '.ent': 'application/xml-external-parsed-entity', 
    +144      '.eps': 'image/x-eps', 
    +145      '.eps.bz2': 'image/x-bzeps', 
    +146      '.eps.gz': 'image/x-gzeps', 
    +147      '.epsf': 'image/x-eps', 
    +148      '.epsf.bz2': 'image/x-bzeps', 
    +149      '.epsf.gz': 'image/x-gzeps', 
    +150      '.epsi': 'image/x-eps', 
    +151      '.epsi.bz2': 'image/x-bzeps', 
    +152      '.epsi.gz': 'image/x-gzeps', 
    +153      '.epub': 'application/epub+zip', 
    +154      '.erl': 'text/x-erlang', 
    +155      '.es': 'application/ecmascript', 
    +156      '.etheme': 'application/x-e-theme', 
    +157      '.etx': 'text/x-setext', 
    +158      '.exe': 'application/x-ms-dos-executable', 
    +159      '.exr': 'image/x-exr', 
    +160      '.ez': 'application/andrew-inset', 
    +161      '.f': 'text/x-fortran', 
    +162      '.f90': 'text/x-fortran', 
    +163      '.f95': 'text/x-fortran', 
    +164      '.fb2': 'application/x-fictionbook+xml', 
    +165      '.fig': 'image/x-xfig', 
    +166      '.fits': 'image/fits', 
    +167      '.fl': 'application/x-fluid', 
    +168      '.flac': 'audio/x-flac', 
    +169      '.flc': 'video/x-flic', 
    +170      '.fli': 'video/x-flic', 
    +171      '.flv': 'video/x-flv', 
    +172      '.flw': 'application/x-kivio', 
    +173      '.fo': 'text/x-xslfo', 
    +174      '.for': 'text/x-fortran', 
    +175      '.g3': 'image/fax-g3', 
    +176      '.gb': 'application/x-gameboy-rom', 
    +177      '.gba': 'application/x-gba-rom', 
    +178      '.gcrd': 'text/directory', 
    +179      '.ged': 'application/x-gedcom', 
    +180      '.gedcom': 'application/x-gedcom', 
    +181      '.gen': 'application/x-genesis-rom', 
    +182      '.gf': 'application/x-tex-gf', 
    +183      '.gg': 'application/x-sms-rom', 
    +184      '.gif': 'image/gif', 
    +185      '.glade': 'application/x-glade', 
    +186      '.gmo': 'application/x-gettext-translation', 
    +187      '.gnc': 'application/x-gnucash', 
    +188      '.gnd': 'application/gnunet-directory', 
    +189      '.gnucash': 'application/x-gnucash', 
    +190      '.gnumeric': 'application/x-gnumeric', 
    +191      '.gnuplot': 'application/x-gnuplot', 
    +192      '.gp': 'application/x-gnuplot', 
    +193      '.gpg': 'application/pgp-encrypted', 
    +194      '.gplt': 'application/x-gnuplot', 
    +195      '.gra': 'application/x-graphite', 
    +196      '.gsf': 'application/x-font-type1', 
    +197      '.gsm': 'audio/x-gsm', 
    +198      '.gtar': 'application/x-tar', 
    +199      '.gv': 'text/vnd.graphviz', 
    +200      '.gvp': 'text/x-google-video-pointer', 
    +201      '.gz': 'application/x-gzip', 
    +202      '.h': 'text/x-chdr', 
    +203      '.h++': 'text/x-c++hdr', 
    +204      '.hdf': 'application/x-hdf', 
    +205      '.hh': 'text/x-c++hdr', 
    +206      '.hp': 'text/x-c++hdr', 
    +207      '.hpgl': 'application/vnd.hp-hpgl', 
    +208      '.hpp': 'text/x-c++hdr', 
    +209      '.hs': 'text/x-haskell', 
    +210      '.htm': 'text/html', 
    +211      '.html': 'text/html', 
    +212      '.hwp': 'application/x-hwp', 
    +213      '.hwt': 'application/x-hwt', 
    +214      '.hxx': 'text/x-c++hdr', 
    +215      '.ica': 'application/x-ica', 
    +216      '.icb': 'image/x-tga', 
    +217      '.icns': 'image/x-icns', 
    +218      '.ico': 'image/vnd.microsoft.icon', 
    +219      '.ics': 'text/calendar', 
    +220      '.idl': 'text/x-idl', 
    +221      '.ief': 'image/ief', 
    +222      '.iff': 'image/x-iff', 
    +223      '.ilbm': 'image/x-ilbm', 
    +224      '.ime': 'text/x-imelody', 
    +225      '.imy': 'text/x-imelody', 
    +226      '.ins': 'text/x-tex', 
    +227      '.iptables': 'text/x-iptables', 
    +228      '.iso': 'application/x-cd-image', 
    +229      '.iso9660': 'application/x-cd-image', 
    +230      '.it': 'audio/x-it', 
    +231      '.j2k': 'image/jp2', 
    +232      '.jad': 'text/vnd.sun.j2me.app-descriptor', 
    +233      '.jar': 'application/x-java-archive', 
    +234      '.java': 'text/x-java', 
    +235      '.jng': 'image/x-jng', 
    +236      '.jnlp': 'application/x-java-jnlp-file', 
    +237      '.jp2': 'image/jp2', 
    +238      '.jpc': 'image/jp2', 
    +239      '.jpe': 'image/jpeg', 
    +240      '.jpeg': 'image/jpeg', 
    +241      '.jpf': 'image/jp2', 
    +242      '.jpg': 'image/jpeg', 
    +243      '.jpr': 'application/x-jbuilder-project', 
    +244      '.jpx': 'image/jp2', 
    +245      '.js': 'application/javascript', 
    +246      '.json': 'application/json', 
    +247      '.k25': 'image/x-kodak-k25', 
    +248      '.kar': 'audio/midi', 
    +249      '.karbon': 'application/x-karbon', 
    +250      '.kdc': 'image/x-kodak-kdc', 
    +251      '.kdelnk': 'application/x-desktop', 
    +252      '.kexi': 'application/x-kexiproject-sqlite3', 
    +253      '.kexic': 'application/x-kexi-connectiondata', 
    +254      '.kexis': 'application/x-kexiproject-shortcut', 
    +255      '.kfo': 'application/x-kformula', 
    +256      '.kil': 'application/x-killustrator', 
    +257      '.kino': 'application/smil', 
    +258      '.kml': 'application/vnd.google-earth.kml+xml', 
    +259      '.kmz': 'application/vnd.google-earth.kmz', 
    +260      '.kon': 'application/x-kontour', 
    +261      '.kpm': 'application/x-kpovmodeler', 
    +262      '.kpr': 'application/x-kpresenter', 
    +263      '.kpt': 'application/x-kpresenter', 
    +264      '.kra': 'application/x-krita', 
    +265      '.ksp': 'application/x-kspread', 
    +266      '.kud': 'application/x-kugar', 
    +267      '.kwd': 'application/x-kword', 
    +268      '.kwt': 'application/x-kword', 
    +269      '.la': 'application/x-shared-library-la', 
    +270      '.latex': 'text/x-tex', 
    +271      '.ldif': 'text/x-ldif', 
    +272      '.lha': 'application/x-lha', 
    +273      '.lhs': 'text/x-literate-haskell', 
    +274      '.lhz': 'application/x-lhz', 
    +275      '.log': 'text/x-log', 
    +276      '.ltx': 'text/x-tex', 
    +277      '.lua': 'text/x-lua', 
    +278      '.lwo': 'image/x-lwo', 
    +279      '.lwob': 'image/x-lwo', 
    +280      '.lws': 'image/x-lws', 
    +281      '.ly': 'text/x-lilypond', 
    +282      '.lyx': 'application/x-lyx', 
    +283      '.lz': 'application/x-lzip', 
    +284      '.lzh': 'application/x-lha', 
    +285      '.lzma': 'application/x-lzma', 
    +286      '.lzo': 'application/x-lzop', 
    +287      '.m': 'text/x-matlab', 
    +288      '.m15': 'audio/x-mod', 
    +289      '.m2t': 'video/mpeg', 
    +290      '.m3u': 'audio/x-mpegurl', 
    +291      '.m3u8': 'audio/x-mpegurl', 
    +292      '.m4': 'application/x-m4', 
    +293      '.m4a': 'audio/mp4', 
    +294      '.m4b': 'audio/x-m4b', 
    +295      '.m4v': 'video/mp4', 
    +296      '.mab': 'application/x-markaby', 
    +297      '.man': 'application/x-troff-man', 
    +298      '.mbox': 'application/mbox', 
    +299      '.md': 'application/x-genesis-rom', 
    +300      '.mdb': 'application/vnd.ms-access', 
    +301      '.mdi': 'image/vnd.ms-modi', 
    +302      '.me': 'text/x-troff-me', 
    +303      '.med': 'audio/x-mod', 
    +304      '.metalink': 'application/metalink+xml', 
    +305      '.mgp': 'application/x-magicpoint', 
    +306      '.mid': 'audio/midi', 
    +307      '.midi': 'audio/midi', 
    +308      '.mif': 'application/x-mif', 
    +309      '.minipsf': 'audio/x-minipsf', 
    +310      '.mka': 'audio/x-matroska', 
    +311      '.mkv': 'video/x-matroska', 
    +312      '.ml': 'text/x-ocaml', 
    +313      '.mli': 'text/x-ocaml', 
    +314      '.mm': 'text/x-troff-mm', 
    +315      '.mmf': 'application/x-smaf', 
    +316      '.mml': 'text/mathml', 
    +317      '.mng': 'video/x-mng', 
    +318      '.mo': 'application/x-gettext-translation', 
    +319      '.mo3': 'audio/x-mo3', 
    +320      '.moc': 'text/x-moc', 
    +321      '.mod': 'audio/x-mod', 
    +322      '.mof': 'text/x-mof', 
    +323      '.moov': 'video/quicktime', 
    +324      '.mov': 'video/quicktime', 
    +325      '.movie': 'video/x-sgi-movie', 
    +326      '.mp+': 'audio/x-musepack', 
    +327      '.mp2': 'video/mpeg', 
    +328      '.mp3': 'audio/mpeg', 
    +329      '.mp4': 'video/mp4', 
    +330      '.mpc': 'audio/x-musepack', 
    +331      '.mpe': 'video/mpeg', 
    +332      '.mpeg': 'video/mpeg', 
    +333      '.mpg': 'video/mpeg', 
    +334      '.mpga': 'audio/mpeg', 
    +335      '.mpp': 'audio/x-musepack', 
    +336      '.mrl': 'text/x-mrml', 
    +337      '.mrml': 'text/x-mrml', 
    +338      '.mrw': 'image/x-minolta-mrw', 
    +339      '.ms': 'text/x-troff-ms', 
    +340      '.msi': 'application/x-msi', 
    +341      '.msod': 'image/x-msod', 
    +342      '.msx': 'application/x-msx-rom', 
    +343      '.mtm': 'audio/x-mod', 
    +344      '.mup': 'text/x-mup', 
    +345      '.mxf': 'application/mxf', 
    +346      '.n64': 'application/x-n64-rom', 
    +347      '.nb': 'application/mathematica', 
    +348      '.nc': 'application/x-netcdf', 
    +349      '.nds': 'application/x-nintendo-ds-rom', 
    +350      '.nef': 'image/x-nikon-nef', 
    +351      '.nes': 'application/x-nes-rom', 
    +352      '.nfo': 'text/x-nfo', 
    +353      '.not': 'text/x-mup', 
    +354      '.nsc': 'application/x-netshow-channel', 
    +355      '.nsv': 'video/x-nsv', 
    +356      '.o': 'application/x-object', 
    +357      '.obj': 'application/x-tgif', 
    +358      '.ocl': 'text/x-ocl', 
    +359      '.oda': 'application/oda', 
    +360      '.odb': 'application/vnd.oasis.opendocument.database', 
    +361      '.odc': 'application/vnd.oasis.opendocument.chart', 
    +362      '.odf': 'application/vnd.oasis.opendocument.formula', 
    +363      '.odg': 'application/vnd.oasis.opendocument.graphics', 
    +364      '.odi': 'application/vnd.oasis.opendocument.image', 
    +365      '.odm': 'application/vnd.oasis.opendocument.text-master', 
    +366      '.odp': 'application/vnd.oasis.opendocument.presentation', 
    +367      '.ods': 'application/vnd.oasis.opendocument.spreadsheet', 
    +368      '.odt': 'application/vnd.oasis.opendocument.text', 
    +369      '.oga': 'audio/ogg', 
    +370      '.ogg': 'video/x-theora+ogg', 
    +371      '.ogm': 'video/x-ogm+ogg', 
    +372      '.ogv': 'video/ogg', 
    +373      '.ogx': 'application/ogg', 
    +374      '.old': 'application/x-trash', 
    +375      '.oleo': 'application/x-oleo', 
    +376      '.opml': 'text/x-opml+xml', 
    +377      '.ora': 'image/openraster', 
    +378      '.orf': 'image/x-olympus-orf', 
    +379      '.otc': 'application/vnd.oasis.opendocument.chart-template', 
    +380      '.otf': 'application/x-font-otf', 
    +381      '.otg': 'application/vnd.oasis.opendocument.graphics-template', 
    +382      '.oth': 'application/vnd.oasis.opendocument.text-web', 
    +383      '.otp': 'application/vnd.oasis.opendocument.presentation-template', 
    +384      '.ots': 'application/vnd.oasis.opendocument.spreadsheet-template', 
    +385      '.ott': 'application/vnd.oasis.opendocument.text-template', 
    +386      '.owl': 'application/rdf+xml', 
    +387      '.oxt': 'application/vnd.openofficeorg.extension', 
    +388      '.p': 'text/x-pascal', 
    +389      '.p10': 'application/pkcs10', 
    +390      '.p12': 'application/x-pkcs12', 
    +391      '.p7b': 'application/x-pkcs7-certificates', 
    +392      '.p7s': 'application/pkcs7-signature', 
    +393      '.pack': 'application/x-java-pack200', 
    +394      '.pak': 'application/x-pak', 
    +395      '.par2': 'application/x-par2', 
    +396      '.pas': 'text/x-pascal', 
    +397      '.patch': 'text/x-patch', 
    +398      '.pbm': 'image/x-portable-bitmap', 
    +399      '.pcd': 'image/x-photo-cd', 
    +400      '.pcf': 'application/x-cisco-vpn-settings', 
    +401      '.pcf.gz': 'application/x-font-pcf', 
    +402      '.pcf.z': 'application/x-font-pcf', 
    +403      '.pcl': 'application/vnd.hp-pcl', 
    +404      '.pcx': 'image/x-pcx', 
    +405      '.pdb': 'chemical/x-pdb', 
    +406      '.pdc': 'application/x-aportisdoc', 
    +407      '.pdf': 'application/pdf', 
    +408      '.pdf.bz2': 'application/x-bzpdf', 
    +409      '.pdf.gz': 'application/x-gzpdf', 
    +410      '.pef': 'image/x-pentax-pef', 
    +411      '.pem': 'application/x-x509-ca-cert', 
    +412      '.perl': 'application/x-perl', 
    +413      '.pfa': 'application/x-font-type1', 
    +414      '.pfb': 'application/x-font-type1', 
    +415      '.pfx': 'application/x-pkcs12', 
    +416      '.pgm': 'image/x-portable-graymap', 
    +417      '.pgn': 'application/x-chess-pgn', 
    +418      '.pgp': 'application/pgp-encrypted', 
    +419      '.php': 'application/x-php', 
    +420      '.php3': 'application/x-php', 
    +421      '.php4': 'application/x-php', 
    +422      '.pict': 'image/x-pict', 
    +423      '.pict1': 'image/x-pict', 
    +424      '.pict2': 'image/x-pict', 
    +425      '.pickle': 'application/python-pickle', 
    +426      '.pk': 'application/x-tex-pk', 
    +427      '.pkipath': 'application/pkix-pkipath', 
    +428      '.pkr': 'application/pgp-keys', 
    +429      '.pl': 'application/x-perl', 
    +430      '.pla': 'audio/x-iriver-pla', 
    +431      '.pln': 'application/x-planperfect', 
    +432      '.pls': 'audio/x-scpls', 
    +433      '.pm': 'application/x-perl', 
    +434      '.png': 'image/png', 
    +435      '.pnm': 'image/x-portable-anymap', 
    +436      '.pntg': 'image/x-macpaint', 
    +437      '.po': 'text/x-gettext-translation', 
    +438      '.por': 'application/x-spss-por', 
    +439      '.pot': 'text/x-gettext-translation-template', 
    +440      '.ppm': 'image/x-portable-pixmap', 
    +441      '.pps': 'application/vnd.ms-powerpoint', 
    +442      '.ppt': 'application/vnd.ms-powerpoint', 
    +443      '.pptm': 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 
    +444      '.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 
    +445      '.ppz': 'application/vnd.ms-powerpoint', 
    +446      '.prc': 'application/x-palm-database', 
    +447      '.ps': 'application/postscript', 
    +448      '.ps.bz2': 'application/x-bzpostscript', 
    +449      '.ps.gz': 'application/x-gzpostscript', 
    +450      '.psd': 'image/vnd.adobe.photoshop', 
    +451      '.psf': 'audio/x-psf', 
    +452      '.psf.gz': 'application/x-gz-font-linux-psf', 
    +453      '.psflib': 'audio/x-psflib', 
    +454      '.psid': 'audio/prs.sid', 
    +455      '.psw': 'application/x-pocket-word', 
    +456      '.pw': 'application/x-pw', 
    +457      '.py': 'text/x-python', 
    +458      '.pyc': 'application/x-python-bytecode', 
    +459      '.pyo': 'application/x-python-bytecode', 
    +460      '.qif': 'image/x-quicktime', 
    +461      '.qt': 'video/quicktime', 
    +462      '.qtif': 'image/x-quicktime', 
    +463      '.qtl': 'application/x-quicktime-media-link', 
    +464      '.qtvr': 'video/quicktime', 
    +465      '.ra': 'audio/vnd.rn-realaudio', 
    +466      '.raf': 'image/x-fuji-raf', 
    +467      '.ram': 'application/ram', 
    +468      '.rar': 'application/x-rar', 
    +469      '.ras': 'image/x-cmu-raster', 
    +470      '.raw': 'image/x-panasonic-raw', 
    +471      '.rax': 'audio/vnd.rn-realaudio', 
    +472      '.rb': 'application/x-ruby', 
    +473      '.rdf': 'application/rdf+xml', 
    +474      '.rdfs': 'application/rdf+xml', 
    +475      '.reg': 'text/x-ms-regedit', 
    +476      '.rej': 'application/x-reject', 
    +477      '.rgb': 'image/x-rgb', 
    +478      '.rle': 'image/rle', 
    +479      '.rm': 'application/vnd.rn-realmedia', 
    +480      '.rmj': 'application/vnd.rn-realmedia', 
    +481      '.rmm': 'application/vnd.rn-realmedia', 
    +482      '.rms': 'application/vnd.rn-realmedia', 
    +483      '.rmvb': 'application/vnd.rn-realmedia', 
    +484      '.rmx': 'application/vnd.rn-realmedia', 
    +485      '.roff': 'text/troff', 
    +486      '.rp': 'image/vnd.rn-realpix', 
    +487      '.rpm': 'application/x-rpm', 
    +488      '.rss': 'application/rss+xml', 
    +489      '.rt': 'text/vnd.rn-realtext', 
    +490      '.rtf': 'application/rtf', 
    +491      '.rtx': 'text/richtext', 
    +492      '.rv': 'video/vnd.rn-realvideo', 
    +493      '.rvx': 'video/vnd.rn-realvideo', 
    +494      '.s3m': 'audio/x-s3m', 
    +495      '.sam': 'application/x-amipro', 
    +496      '.sami': 'application/x-sami', 
    +497      '.sav': 'application/x-spss-sav', 
    +498      '.scm': 'text/x-scheme', 
    +499      '.sda': 'application/vnd.stardivision.draw', 
    +500      '.sdc': 'application/vnd.stardivision.calc', 
    +501      '.sdd': 'application/vnd.stardivision.impress', 
    +502      '.sdp': 'application/sdp', 
    +503      '.sds': 'application/vnd.stardivision.chart', 
    +504      '.sdw': 'application/vnd.stardivision.writer', 
    +505      '.sgf': 'application/x-go-sgf', 
    +506      '.sgi': 'image/x-sgi', 
    +507      '.sgl': 'application/vnd.stardivision.writer', 
    +508      '.sgm': 'text/sgml', 
    +509      '.sgml': 'text/sgml', 
    +510      '.sh': 'application/x-shellscript', 
    +511      '.shar': 'application/x-shar', 
    +512      '.shn': 'application/x-shorten', 
    +513      '.siag': 'application/x-siag', 
    +514      '.sid': 'audio/prs.sid', 
    +515      '.sik': 'application/x-trash', 
    +516      '.sis': 'application/vnd.symbian.install', 
    +517      '.sisx': 'x-epoc/x-sisx-app', 
    +518      '.sit': 'application/x-stuffit', 
    +519      '.siv': 'application/sieve', 
    +520      '.sk': 'image/x-skencil', 
    +521      '.sk1': 'image/x-skencil', 
    +522      '.skr': 'application/pgp-keys', 
    +523      '.slk': 'text/spreadsheet', 
    +524      '.smaf': 'application/x-smaf', 
    +525      '.smc': 'application/x-snes-rom', 
    +526      '.smd': 'application/vnd.stardivision.mail', 
    +527      '.smf': 'application/vnd.stardivision.math', 
    +528      '.smi': 'application/x-sami', 
    +529      '.smil': 'application/smil', 
    +530      '.sml': 'application/smil', 
    +531      '.sms': 'application/x-sms-rom', 
    +532      '.snd': 'audio/basic', 
    +533      '.so': 'application/x-sharedlib', 
    +534      '.spc': 'application/x-pkcs7-certificates', 
    +535      '.spd': 'application/x-font-speedo', 
    +536      '.spec': 'text/x-rpm-spec', 
    +537      '.spl': 'application/x-shockwave-flash', 
    +538      '.spx': 'audio/x-speex', 
    +539      '.sql': 'text/x-sql', 
    +540      '.sr2': 'image/x-sony-sr2', 
    +541      '.src': 'application/x-wais-source', 
    +542      '.srf': 'image/x-sony-srf', 
    +543      '.srt': 'application/x-subrip', 
    +544      '.ssa': 'text/x-ssa', 
    +545      '.stc': 'application/vnd.sun.xml.calc.template', 
    +546      '.std': 'application/vnd.sun.xml.draw.template', 
    +547      '.sti': 'application/vnd.sun.xml.impress.template', 
    +548      '.stm': 'audio/x-stm', 
    +549      '.stw': 'application/vnd.sun.xml.writer.template', 
    +550      '.sty': 'text/x-tex', 
    +551      '.sub': 'text/x-subviewer', 
    +552      '.sun': 'image/x-sun-raster', 
    +553      '.sv4cpio': 'application/x-sv4cpio', 
    +554      '.sv4crc': 'application/x-sv4crc', 
    +555      '.svg': 'image/svg+xml', 
    +556      '.svgz': 'image/svg+xml-compressed', 
    +557      '.swf': 'application/x-shockwave-flash', 
    +558      '.sxc': 'application/vnd.sun.xml.calc', 
    +559      '.sxd': 'application/vnd.sun.xml.draw', 
    +560      '.sxg': 'application/vnd.sun.xml.writer.global', 
    +561      '.sxi': 'application/vnd.sun.xml.impress', 
    +562      '.sxm': 'application/vnd.sun.xml.math', 
    +563      '.sxw': 'application/vnd.sun.xml.writer', 
    +564      '.sylk': 'text/spreadsheet', 
    +565      '.t': 'text/troff', 
    +566      '.t2t': 'text/x-txt2tags', 
    +567      '.tar': 'application/x-tar', 
    +568      '.tar.bz': 'application/x-bzip-compressed-tar', 
    +569      '.tar.bz2': 'application/x-bzip-compressed-tar', 
    +570      '.tar.gz': 'application/x-compressed-tar', 
    +571      '.tar.lzma': 'application/x-lzma-compressed-tar', 
    +572      '.tar.lzo': 'application/x-tzo', 
    +573      '.tar.xz': 'application/x-xz-compressed-tar', 
    +574      '.tar.z': 'application/x-tarz', 
    +575      '.tbz': 'application/x-bzip-compressed-tar', 
    +576      '.tbz2': 'application/x-bzip-compressed-tar', 
    +577      '.tcl': 'text/x-tcl', 
    +578      '.tex': 'text/x-tex', 
    +579      '.texi': 'text/x-texinfo', 
    +580      '.texinfo': 'text/x-texinfo', 
    +581      '.tga': 'image/x-tga', 
    +582      '.tgz': 'application/x-compressed-tar', 
    +583      '.theme': 'application/x-theme', 
    +584      '.themepack': 'application/x-windows-themepack', 
    +585      '.tif': 'image/tiff', 
    +586      '.tiff': 'image/tiff', 
    +587      '.tk': 'text/x-tcl', 
    +588      '.tlz': 'application/x-lzma-compressed-tar', 
    +589      '.tnef': 'application/vnd.ms-tnef', 
    +590      '.tnf': 'application/vnd.ms-tnef', 
    +591      '.toc': 'application/x-cdrdao-toc', 
    +592      '.torrent': 'application/x-bittorrent', 
    +593      '.tpic': 'image/x-tga', 
    +594      '.tr': 'text/troff', 
    +595      '.ts': 'application/x-linguist', 
    +596      '.tsv': 'text/tab-separated-values', 
    +597      '.tta': 'audio/x-tta', 
    +598      '.ttc': 'application/x-font-ttf', 
    +599      '.ttf': 'application/x-font-ttf', 
    +600      '.ttx': 'application/x-font-ttx', 
    +601      '.txt': 'text/plain', 
    +602      '.txz': 'application/x-xz-compressed-tar', 
    +603      '.tzo': 'application/x-tzo', 
    +604      '.ufraw': 'application/x-ufraw', 
    +605      '.ui': 'application/x-designer', 
    +606      '.uil': 'text/x-uil', 
    +607      '.ult': 'audio/x-mod', 
    +608      '.uni': 'audio/x-mod', 
    +609      '.uri': 'text/x-uri', 
    +610      '.url': 'text/x-uri', 
    +611      '.ustar': 'application/x-ustar', 
    +612      '.vala': 'text/x-vala', 
    +613      '.vapi': 'text/x-vala', 
    +614      '.vcf': 'text/directory', 
    +615      '.vcs': 'text/calendar', 
    +616      '.vct': 'text/directory', 
    +617      '.vda': 'image/x-tga', 
    +618      '.vhd': 'text/x-vhdl', 
    +619      '.vhdl': 'text/x-vhdl', 
    +620      '.viv': 'video/vivo', 
    +621      '.vivo': 'video/vivo', 
    +622      '.vlc': 'audio/x-mpegurl', 
    +623      '.vob': 'video/mpeg', 
    +624      '.voc': 'audio/x-voc', 
    +625      '.vor': 'application/vnd.stardivision.writer', 
    +626      '.vst': 'image/x-tga', 
    +627      '.wav': 'audio/x-wav', 
    +628      '.wax': 'audio/x-ms-asx', 
    +629      '.wb1': 'application/x-quattropro', 
    +630      '.wb2': 'application/x-quattropro', 
    +631      '.wb3': 'application/x-quattropro', 
    +632      '.wbmp': 'image/vnd.wap.wbmp', 
    +633      '.wcm': 'application/vnd.ms-works', 
    +634      '.wdb': 'application/vnd.ms-works', 
    +635      '.wk1': 'application/vnd.lotus-1-2-3', 
    +636      '.wk3': 'application/vnd.lotus-1-2-3', 
    +637      '.wk4': 'application/vnd.lotus-1-2-3', 
    +638      '.wks': 'application/vnd.ms-works', 
    +639      '.wma': 'audio/x-ms-wma', 
    +640      '.wmf': 'image/x-wmf', 
    +641      '.wml': 'text/vnd.wap.wml', 
    +642      '.wmls': 'text/vnd.wap.wmlscript', 
    +643      '.wmv': 'video/x-ms-wmv', 
    +644      '.wmx': 'audio/x-ms-asx', 
    +645      '.wp': 'application/vnd.wordperfect', 
    +646      '.wp4': 'application/vnd.wordperfect', 
    +647      '.wp5': 'application/vnd.wordperfect', 
    +648      '.wp6': 'application/vnd.wordperfect', 
    +649      '.wpd': 'application/vnd.wordperfect', 
    +650      '.wpg': 'application/x-wpg', 
    +651      '.wpl': 'application/vnd.ms-wpl', 
    +652      '.wpp': 'application/vnd.wordperfect', 
    +653      '.wps': 'application/vnd.ms-works', 
    +654      '.wri': 'application/x-mswrite', 
    +655      '.wrl': 'model/vrml', 
    +656      '.wv': 'audio/x-wavpack', 
    +657      '.wvc': 'audio/x-wavpack-correction', 
    +658      '.wvp': 'audio/x-wavpack', 
    +659      '.wvx': 'audio/x-ms-asx', 
    +660      '.x3f': 'image/x-sigma-x3f', 
    +661      '.xac': 'application/x-gnucash', 
    +662      '.xbel': 'application/x-xbel', 
    +663      '.xbl': 'application/xml', 
    +664      '.xbm': 'image/x-xbitmap', 
    +665      '.xcf': 'image/x-xcf', 
    +666      '.xcf.bz2': 'image/x-compressed-xcf', 
    +667      '.xcf.gz': 'image/x-compressed-xcf', 
    +668      '.xhtml': 'application/xhtml+xml', 
    +669      '.xi': 'audio/x-xi', 
    +670      '.xla': 'application/vnd.ms-excel', 
    +671      '.xlc': 'application/vnd.ms-excel', 
    +672      '.xld': 'application/vnd.ms-excel', 
    +673      '.xlf': 'application/x-xliff', 
    +674      '.xliff': 'application/x-xliff', 
    +675      '.xll': 'application/vnd.ms-excel', 
    +676      '.xlm': 'application/vnd.ms-excel', 
    +677      '.xls': 'application/vnd.ms-excel', 
    +678      '.xlsm': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 
    +679      '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 
    +680      '.xlt': 'application/vnd.ms-excel', 
    +681      '.xlw': 'application/vnd.ms-excel', 
    +682      '.xm': 'audio/x-xm', 
    +683      '.xmf': 'audio/x-xmf', 
    +684      '.xmi': 'text/x-xmi', 
    +685      '.xml': 'application/xml', 
    +686      '.xpm': 'image/x-xpixmap', 
    +687      '.xps': 'application/vnd.ms-xpsdocument', 
    +688      '.xsl': 'application/xml', 
    +689      '.xslfo': 'text/x-xslfo', 
    +690      '.xslt': 'application/xml', 
    +691      '.xspf': 'application/xspf+xml', 
    +692      '.xul': 'application/vnd.mozilla.xul+xml', 
    +693      '.xwd': 'image/x-xwindowdump', 
    +694      '.xyz': 'chemical/x-pdb', 
    +695      '.xz': 'application/x-xz', 
    +696      '.w2p': 'application/w2p', 
    +697      '.z': 'application/x-compress', 
    +698      '.zabw': 'application/x-abiword', 
    +699      '.zip': 'application/zip', 
    +700      '.zoo': 'application/x-zoo', 
    +701      } 
    +702   
    +703   
    +
    704 -def contenttype(filename, default='text/plain'): +
    705 """ +706 Returns the Content-Type string matching extension of the given filename. +707 """ +708 +709 i = filename.rfind('.') +710 if i>=0: +711 default = CONTENT_TYPE.get(filename[i:].lower(),default) +712 j = filename.rfind('.', 0, i) +713 if j>=0: +714 default = CONTENT_TYPE.get(filename[j:].lower(),default) +715 if default.startswith('text/'): +716 default += '; charset=utf-8' +717 return default +
    718 +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.contrib.pymysql-module.html Index: applications/examples/static/epydoc/web2py.gluon.contrib.pymysql-module.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.contrib.pymysql-module.html +++ applications/examples/static/epydoc/web2py.gluon.contrib.pymysql-module.html @@ -0,0 +1,667 @@ + + + + + web2py.gluon.contrib.pymysql + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Package contrib :: + Package pymysql + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Package pymysql

    source code

    +

    PyMySQL: A pure-Python drop-in replacement for MySQLdb.

    +

    Copyright (c) 2010 PyMySQL contributors

    +

    Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, and + to permit persons to whom the Software is furnished to do so, subject to + the following conditions:

    +

    The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software.

    + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY + KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + USE OR OTHER DEALINGS IN THE SOFTWARE.

    + +
    +

    Version: + 0.4.None +

    +
    + + + + + + +
    + + + + + +
    Submodules[hide private]
    +
    +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Classes[hide private]
    +
    +   + + DBAPISet +
    +   + + DataError
    + Exception raised for errors that are due to problems with the + processed data like division by zero, numeric value out of range, + etc. +
    +   + + DatabaseError
    + Exception raised for errors that are related to the + database. +
    +   + + Date
    + date(year, month, day) --> date object +
    +   + + Error
    + Exception that is the base class of all other error exceptions + (not Warning). +
    +   + + IntegrityError
    + Exception raised when the relational integrity of the database + is affected, e.g. +
    +   + + InterfaceError
    + Exception raised for errors that are related to the database + interface rather than the database itself. +
    +   + + InternalError
    + Exception raised when the database encounters an internal error, + e.g. +
    +   + + NotSupportedError
    + Exception raised in case a method or database API was used which + is not supported by the database, e.g. +
    +   + + OperationalError
    + Exception raised for errors that are related to the database's + operation and not necessarily under the control of the programmer, + e.g. +
    +   + + ProgrammingError
    + Exception raised for programming errors, e.g. +
    +   + + Time
    + time([hour[, minute[, second[, microsecond[, tzinfo]]]]]) --> + a time object +
    +   + + Timestamp
    + datetime(year, month, day[, hour[, minute[, second[, + microsecond[,tzinfo]]]]]) +
    +   + + Warning
    + Exception raised for important warnings like data truncations + while inserting, etc. +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Functions[hide private]
    +
    +   + + + + + + +
    Binary(x)
    + Return x as a binary type.
    + source code + +
    + +
    +   + + + + + + +
    Connect(*args, + **kwargs)
    + Connect to the database; see connections.Connection.__init__() for + more information.
    + source code + +
    + +
    +   + + + + + + +
    Connection(*args, + **kwargs)
    + Connect to the database; see connections.Connection.__init__() for + more information.
    + source code + +
    + +
    +   + + + + + + +
    DateFromTicks(ticks) + + +
    + +
    +   + + + + + + +
    TimeFromTicks(ticks) + + +
    + +
    +   + + + + + + +
    TimestampFromTicks(ticks) + + +
    + +
    +   + + + + + + +
    connect(*args, + **kwargs)
    + Connect to the database; see connections.Connection.__init__() for + more information.
    + source code + +
    + +
    +   + + + + + + +
    escape_dict(val, + charset) + source code + +
    + +
    +   + + + + + + +
    escape_sequence(val, + charset) + source code + +
    + +
    +   + + + + + + +
    escape_string(value) + source code + +
    + +
    +   + + + + + + +
    get_client_info() + source code + +
    + +
    +   + + + + + + +
    install_as_MySQLdb()
    + After this function is called, any application that imports + MySQLdb or _mysql will unwittingly actually use
    + source code + +
    + +
    +   + + + + + + +
    thread_safe() + source code + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Variables[hide private]
    +
    +   + + BINARY = DBAPISet([249, 250, 251, 252]) +
    +   + + DATE = DBAPISet([10, 14]) +
    +   + + DATETIME = DBAPISet([12, 7]) +
    +   + + NULL = 'NULL' +
    +   + + NUMBER = DBAPISet([0, 1, 3, 4, 5, 8, 9, 13]) +
    +   + + ROWID = DBAPISet([]) +
    +   + + STRING = DBAPISet([253, 254, 247]) +
    +   + + TIME = DBAPISet([11]) +
    +   + + TIMESTAMP = DBAPISet([12, 7]) +
    +   + + VERSION = (0, 4, None) +
    +   + + apilevel = '2.0' +
    +   + + paramstyle = 'format' +
    +   + + threadsafety = 1
    + PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    +   + + version_info = (1, 2, 2, 'final', 0) +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.contrib.pymysql-pysrc.html Index: applications/examples/static/epydoc/web2py.gluon.contrib.pymysql-pysrc.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.contrib.pymysql-pysrc.html +++ applications/examples/static/epydoc/web2py.gluon.contrib.pymysql-pysrc.html @@ -0,0 +1,302 @@ + + + + + web2py.gluon.contrib.pymysql + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Package contrib :: + Package pymysql + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Package web2py.gluon.contrib.pymysql

    +
    +  1  ''' 
    +  2  PyMySQL: A pure-Python drop-in replacement for MySQLdb. 
    +  3   
    +  4  Copyright (c) 2010 PyMySQL contributors 
    +  5   
    +  6  Permission is hereby granted, free of charge, to any person obtaining a copy 
    +  7  of this software and associated documentation files (the "Software"), to deal 
    +  8  in the Software without restriction, including without limitation the rights 
    +  9  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
    + 10  copies of the Software, and to permit persons to whom the Software is 
    + 11  furnished to do so, subject to the following conditions: 
    + 12   
    + 13  The above copyright notice and this permission notice shall be included in 
    + 14  all copies or substantial portions of the Software. 
    + 15   
    + 16  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
    + 17  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
    + 18  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
    + 19  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
    + 20  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
    + 21  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 
    + 22  THE SOFTWARE. 
    + 23   
    + 24  ''' 
    + 25   
    + 26  VERSION = (0, 4, None) 
    + 27   
    + 28  from constants import FIELD_TYPE 
    + 29  from converters import escape_dict, escape_sequence, escape_string 
    + 30  from err import Warning, Error, InterfaceError, DataError, \ 
    + 31       DatabaseError, OperationalError, IntegrityError, InternalError, \ 
    + 32       NotSupportedError, ProgrammingError 
    + 33  from times import Date, Time, Timestamp, \ 
    + 34      DateFromTicks, TimeFromTicks, TimestampFromTicks 
    + 35   
    + 36  import sys 
    + 37   
    + 38  try: 
    + 39      frozenset 
    + 40  except NameError: 
    + 41      from sets import ImmutableSet as frozenset 
    + 42      try: 
    + 43          from sets import BaseSet as set 
    + 44      except ImportError: 
    + 45          from sets import Set as set 
    + 46   
    + 47  threadsafety = 1 
    + 48  apilevel = "2.0" 
    + 49  paramstyle = "format" 
    + 50   
    +
    51 -class DBAPISet(frozenset): +
    52 + 53 +
    54 - def __ne__(self, other): +
    55 if isinstance(other, set): + 56 return super(DBAPISet, self).__ne__(self, other) + 57 else: + 58 return other not in self +
    59 +
    60 - def __eq__(self, other): +
    61 if isinstance(other, frozenset): + 62 return frozenset.__eq__(self, other) + 63 else: + 64 return other in self +
    65 +
    66 - def __hash__(self): +
    67 return frozenset.__hash__(self) +
    68 + 69 + 70 STRING = DBAPISet([FIELD_TYPE.ENUM, FIELD_TYPE.STRING, + 71 FIELD_TYPE.VAR_STRING]) + 72 BINARY = DBAPISet([FIELD_TYPE.BLOB, FIELD_TYPE.LONG_BLOB, + 73 FIELD_TYPE.MEDIUM_BLOB, FIELD_TYPE.TINY_BLOB]) + 74 NUMBER = DBAPISet([FIELD_TYPE.DECIMAL, FIELD_TYPE.DOUBLE, FIELD_TYPE.FLOAT, + 75 FIELD_TYPE.INT24, FIELD_TYPE.LONG, FIELD_TYPE.LONGLONG, + 76 FIELD_TYPE.TINY, FIELD_TYPE.YEAR]) + 77 DATE = DBAPISet([FIELD_TYPE.DATE, FIELD_TYPE.NEWDATE]) + 78 TIME = DBAPISet([FIELD_TYPE.TIME]) + 79 TIMESTAMP = DBAPISet([FIELD_TYPE.TIMESTAMP, FIELD_TYPE.DATETIME]) + 80 DATETIME = TIMESTAMP + 81 ROWID = DBAPISet() + 82 +
    83 -def Binary(x): +
    84 """Return x as a binary type.""" + 85 return str(x) +
    86 +
    87 -def Connect(*args, **kwargs): +
    88 """ + 89 Connect to the database; see connections.Connection.__init__() for + 90 more information. + 91 """ + 92 from connections import Connection + 93 return Connection(*args, **kwargs) +
    94 +
    95 -def get_client_info(): # for MySQLdb compatibility +
    96 return '%s.%s.%s' % VERSION + 97 + 98 connect = Connection = Connect + 99 +100 # we include a doctored version_info here for MySQLdb compatibility +101 version_info = (1,2,2,"final",0) +102 +103 NULL = "NULL" +104 +105 __version__ = get_client_info() +106 +
    107 -def thread_safe(): +
    108 return True # match MySQLdb.thread_safe() +
    109 +
    110 -def install_as_MySQLdb(): +
    111 """ +112 After this function is called, any application that imports MySQLdb or +113 _mysql will unwittingly actually use +114 """ +115 sys.modules["MySQLdb"] = sys.modules["_mysql"] = sys.modules["pymysql"] +
    116 +117 __all__ = [ +118 'BINARY', 'Binary', 'Connect', 'Connection', 'DATE', 'Date', +119 'Time', 'Timestamp', 'DateFromTicks', 'TimeFromTicks', 'TimestampFromTicks', +120 'DataError', 'DatabaseError', 'Error', 'FIELD_TYPE', 'IntegrityError', +121 'InterfaceError', 'InternalError', 'MySQLError', 'NULL', 'NUMBER', +122 'NotSupportedError', 'DBAPISet', 'OperationalError', 'ProgrammingError', +123 'ROWID', 'STRING', 'TIME', 'TIMESTAMP', 'Warning', 'apilevel', 'connect', +124 'connections', 'constants', 'converters', 'cursors', 'debug', 'escape', +125 'escape_dict', 'escape_sequence', 'escape_string', 'get_client_info', +126 'paramstyle', 'string_literal', 'threadsafety', 'version_info', +127 +128 "install_as_MySQLdb", +129 +130 "NULL","__version__", +131 ] +132 +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.DBAPISet-class.html Index: applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.DBAPISet-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.DBAPISet-class.html +++ applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.DBAPISet-class.html @@ -0,0 +1,354 @@ + + + + + web2py.gluon.contrib.pymysql.DBAPISet + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Package contrib :: + Package pymysql :: + Class DBAPISet + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class DBAPISet

    source code

    +
    +object --+    
    +         |    
    + frozenset --+
    +             |
    +            DBAPISet
    +
    + +
    + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __eq__(self, + other)
    + x==y
    + source code + +
    + +
    +   + + + + + + +
    __hash__(self)
    + hash(x)
    + source code + +
    + +
    +   + + + + + + +
    __ne__(self, + other)
    + x!=y
    + source code + +
    + +
    +

    Inherited from frozenset: + __and__, + __cmp__, + __contains__, + __ge__, + __getattribute__, + __gt__, + __iter__, + __le__, + __len__, + __lt__, + __new__, + __or__, + __rand__, + __reduce__, + __repr__, + __ror__, + __rsub__, + __rxor__, + __sub__, + __xor__, + copy, + difference, + intersection, + issubset, + issuperset, + symmetric_difference, + union +

    +

    Inherited from object: + __delattr__, + __init__, + __reduce_ex__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __eq__(self, + other) +
    (Equality operator) +

    +
    source code  +
    + + x==y +
    +
    Overrides: + frozenset.__eq__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    __hash__(self) +
    (Hashing function) +

    +
    source code  +
    + + hash(x) +
    +
    Overrides: + frozenset.__hash__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    __ne__(self, + other) +

    +
    source code  +
    + + x!=y +
    +
    Overrides: + frozenset.__ne__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.constants-module.html Index: applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.constants-module.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.constants-module.html +++ applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.constants-module.html @@ -0,0 +1,141 @@ + + + + + web2py.gluon.contrib.pymysql.constants + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Package contrib :: + Package pymysql :: + Package constants + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Package constants

    source code

    + + + + + + + +
    + + + + + +
    Submodules[hide private]
    +
    +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.constants-pysrc.html Index: applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.constants-pysrc.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.constants-pysrc.html +++ applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.constants-pysrc.html @@ -0,0 +1,125 @@ + + + + + web2py.gluon.contrib.pymysql.constants + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Package contrib :: + Package pymysql :: + Package constants + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Package web2py.gluon.contrib.pymysql.constants

    +
    +1   
    +2   
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.constants.FIELD_TYPE-module.html Index: applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.constants.FIELD_TYPE-module.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.constants.FIELD_TYPE-module.html +++ applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.constants.FIELD_TYPE-module.html @@ -0,0 +1,343 @@ + + + + + web2py.gluon.contrib.pymysql.constants.FIELD_TYPE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Package contrib :: + Package pymysql :: + Package constants :: + Module FIELD_TYPE + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Module FIELD_TYPE

    source code

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Variables[hide private]
    +
    +   + + BIT = 16 +
    +   + + BLOB = 252 +
    +   + + CHAR = 1
    + PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    +   + + DATE = 10 +
    +   + + DATETIME = 12 +
    +   + + DECIMAL = 0 +
    +   + + DOUBLE = 5 +
    +   + + ENUM = 247 +
    +   + + FLOAT = 1
    + PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    +   + + GEOMETRY = 255 +
    +   + + INT24 = 9 +
    +   + + INTERVAL = 247 +
    +   + + LONG = 3 +
    +   + + LONGLONG = 8 +
    +   + + LONG_BLOB = 251 +
    +   + + MEDIUM_BLOB = 250 +
    +   + + NEWDATE = 14 +
    +   + + NEWDECIMAL = 246 +
    +   + + NULL = 6 +
    +   + + SET = 248 +
    +   + + SHORT = 1
    + PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    +   + + STRING = 254 +
    +   + + TIME = 11 +
    +   + + TIMESTAMP = 7 +
    +   + + TINY = 1
    + PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    +   + + TINY_BLOB = 249 +
    +   + + VARCHAR = 15 +
    +   + + VAR_STRING = 253 +
    +   + + YEAR = 13 +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.constants.FIELD_TYPE-pysrc.html Index: applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.constants.FIELD_TYPE-pysrc.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.constants.FIELD_TYPE-pysrc.html +++ applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.constants.FIELD_TYPE-pysrc.html @@ -0,0 +1,165 @@ + + + + + web2py.gluon.contrib.pymysql.constants.FIELD_TYPE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Package contrib :: + Package pymysql :: + Package constants :: + Module FIELD_TYPE + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Module web2py.gluon.contrib.pymysql.constants.FIELD_TYPE

    +
    + 1   
    + 2   
    + 3  DECIMAL = 0 
    + 4  TINY = 1 
    + 5  SHORT = 2 
    + 6  LONG = 3 
    + 7  FLOAT = 4 
    + 8  DOUBLE = 5 
    + 9  NULL = 6 
    +10  TIMESTAMP = 7 
    +11  LONGLONG = 8 
    +12  INT24 = 9 
    +13  DATE = 10 
    +14  TIME = 11 
    +15  DATETIME = 12 
    +16  YEAR = 13 
    +17  NEWDATE = 14 
    +18  VARCHAR = 15 
    +19  BIT = 16 
    +20  NEWDECIMAL = 246 
    +21  ENUM = 247 
    +22  SET = 248 
    +23  TINY_BLOB = 249 
    +24  MEDIUM_BLOB = 250 
    +25  LONG_BLOB = 251 
    +26  BLOB = 252 
    +27  VAR_STRING = 253 
    +28  STRING = 254 
    +29  GEOMETRY = 255 
    +30   
    +31  CHAR = TINY 
    +32  INTERVAL = ENUM 
    +33   
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.converters-module.html Index: applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.converters-module.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.converters-module.html +++ applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.converters-module.html @@ -0,0 +1,1039 @@ + + + + + web2py.gluon.contrib.pymysql.converters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Package contrib :: + Package pymysql :: + Module converters + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Module converters

    source code

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Functions[hide private]
    +
    +   + + + + + + +
    convert_bit(connection, + field, + b) + source code + +
    + +
    +   + + + + + + +
    convert_characters(connection, + field, + data) + source code + +
    + +
    +   + + + + + + +
    convert_date(connection, + field, + obj)
    + Returns a DATE column as a date object:
    + source code + +
    + +
    +   + + + + + + +
    convert_datetime(connection, + field, + obj)
    + Returns a DATETIME or TIMESTAMP column value as a datetime + object:
    + source code + +
    + +
    +   + + + + + + +
    convert_decimal(connection, + field, + data) + source code + +
    + +
    +   + + + + + + +
    convert_float(connection, + field, + data) + source code + +
    + +
    +   + + + + + + +
    convert_int(connection, + field, + data) + source code + +
    + +
    +   + + + + + + +
    convert_long(connection, + field, + data) + source code + +
    + +
    +   + + + + + + +
    convert_mysql_timestamp(connection, + field, + timestamp)
    + Convert a MySQL TIMESTAMP to a Timestamp object.
    + source code + +
    + +
    +   + + + + + + +
    convert_set(s) + source code + +
    + +
    +   + + + + + + +
    convert_time(connection, + field, + obj)
    + Returns a TIME column as a time object:
    + source code + +
    + +
    +   + + + + + + +
    convert_timedelta(connection, + field, + obj)
    + Returns a TIME column as a timedelta object:
    + source code + +
    + +
    +   + + + + + + +
    escape_None(value) + source code + +
    + +
    +   + + + + + + +
    escape_bool(value) + source code + +
    + +
    +   + + + + + + +
    escape_date(obj) + source code + +
    + +
    +   + + + + + + +
    escape_datetime(obj) + source code + +
    + +
    +   + + + + + + +
    escape_decimal(obj) + source code + +
    + +
    +   + + + + + + +
    escape_dict(val, + charset) + source code + +
    + +
    +   + + + + + + +
    escape_float(value) + source code + +
    + +
    +   + + + + + + +
    escape_int(value) + source code + +
    + +
    +   + + + + + + +
    escape_item(val, + charset) + source code + +
    + +
    +   + + + + + + +
    escape_long(value) + source code + +
    + +
    +   + + + + + + +
    escape_object(value) + source code + +
    + +
    +   + + + + + + +
    escape_sequence(val, + charset) + source code + +
    + +
    +   + + + + + + +
    escape_set(val, + charset) + source code + +
    + +
    +   + + + + + + +
    escape_string(value) + source code + +
    + +
    +   + + + + + + +
    escape_struct_time(obj) + source code + +
    + +
    +   + + + + + + +
    escape_time(obj) + source code + +
    + +
    +   + + + + + + +
    escape_timedelta(obj) + source code + +
    + +
    +   + + + + + + +
    escape_unicode(value) + source code + +
    + +
    + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Variables[hide private]
    +
    +   + + ESCAPE_MAP = {'\x00': '\\0', '\n': '\\n', '\r': '\\r', '\x1a':... +
    +   + + ESCAPE_REGEX = re.compile(r'[\x00\n\r\x1a\'"\\]') +
    +   + + conversions = {0: <function convert_decimal at 0xd3ccf8>, 1: <... +
    +   + + decoders = {0: <function convert_decimal at 0xd3ccf8>, 1: <fun... +
    +   + + encoders = {<type 'bool'>: <function escape_bool at 0xd3c2a8>,... +
    + + + + + + +
    + + + + + +
    Function Details[hide private]
    +
    + +
    + +
    + + +
    +

    convert_date(connection, + field, + obj) +

    +
    source code  +
    + + Returns a DATE column as a date object: +
    +>>> date_or_None('2007-02-26')
    +datetime.date(2007, 2, 26)
    + Illegal values are returned as None: +
    +>>> date_or_None('2007-02-31') is None
    +True
    +>>> date_or_None('0000-00-00') is None
    +True
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    convert_datetime(connection, + field, + obj) +

    +
    source code  +
    + + Returns a DATETIME or TIMESTAMP column value as a datetime object: +
    +>>> datetime_or_None('2007-02-25 23:06:20')
    +datetime.datetime(2007, 2, 25, 23, 6, 20)
    +>>> datetime_or_None('2007-02-25T23:06:20')
    +datetime.datetime(2007, 2, 25, 23, 6, 20)
    + Illegal values are returned as None: +
    +>>> datetime_or_None('2007-02-31T23:06:20') is None
    +True
    +>>> datetime_or_None('0000-00-00 00:00:00') is None
    +True
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    convert_mysql_timestamp(connection, + field, + timestamp) +

    +
    source code  +
    + +

    Convert a MySQL TIMESTAMP to a Timestamp object.

    + MySQL >= 4.1 returns TIMESTAMP in the same format as DATETIME: +
    +>>> mysql_timestamp_converter('2007-02-25 22:32:17')
    +datetime.datetime(2007, 2, 25, 22, 32, 17)
    + MySQL < 4.1 uses a big string of numbers: +
    +>>> mysql_timestamp_converter('20070225223217')
    +datetime.datetime(2007, 2, 25, 22, 32, 17)
    + Illegal values are returned as None: +
    +>>> mysql_timestamp_converter('2007-02-31 22:32:17') is None
    +True
    +>>> mysql_timestamp_converter('00000000000000') is None
    +True
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    convert_time(connection, + field, + obj) +

    +
    source code  +
    + + Returns a TIME column as a time object: +
    +>>> time_or_None('15:06:17')
    +datetime.time(15, 6, 17)
    + Illegal values are returned as None: +
    +>>> time_or_None('-25:06:17') is None
    +True
    +>>> time_or_None('random crap') is None
    +True
    +

    Note that MySQL always returns TIME columns as (+|-)HH:MM:SS, but can + accept values as (+|-)DD HH:MM:SS. The latter format will not be parsed + correctly by this function.

    + Also note that MySQL's TIME column corresponds more closely to + Python's timedelta and not time. However if you want TIME columns to be + treated as time-of-day and not a time offset, then you can use set this + function as the converter for FIELD_TYPE.TIME. +
    +
    +
    +
    + +
    + +
    + + +
    +

    convert_timedelta(connection, + field, + obj) +

    +
    source code  +
    + + Returns a TIME column as a timedelta object: +
    +>>> timedelta_or_None('25:06:17')
    +datetime.timedelta(1, 3977)
    +>>> timedelta_or_None('-25:06:17')
    +datetime.timedelta(-2, 83177)
    + Illegal values are returned as None: +
    +>>> timedelta_or_None('random crap') is None
    +True
    + Note that MySQL always returns TIME columns as (+|-)HH:MM:SS, but can + accept values as (+|-)DD HH:MM:SS. The latter format will not be parsed + correctly by this function. +
    +
    +
    +
    +
    + + + + + + +
    + + + + + +
    Variables Details[hide private]
    +
    + +
    + +
    +

    ESCAPE_MAP

    + +
    +
    +
    +
    Value:
    +
    +{'\x00': '\\0',
    + '''
    +''': '\\n',
    + '\r': '\\r',
    + '\x1a': '\\Z',
    + '"': '\\"',
    + '\'': '\\\'',
    + '\\': '\\\\'}
    +
    +
    +
    +
    +
    + +
    + +
    +

    conversions

    + +
    +
    +
    +
    Value:
    +
    +{0: <function convert_decimal at 0xd3ccf8>,
    + 1: <function convert_int at 0xd3cb90>,
    + 2: <function convert_int at 0xd3cb90>,
    + 3: <function convert_long at 0xd3cc08>,
    + 4: <function convert_float at 0xd3cc80>,
    + 5: <function convert_float at 0xd3cc80>,
    + 7: <function convert_mysql_timestamp at 0xd3c9b0>,
    + 8: <function convert_long at 0xd3cc08>,
    +...
    +
    +
    +
    +
    +
    + +
    + +
    +

    decoders

    + +
    +
    +
    +
    Value:
    +
    +{0: <function convert_decimal at 0xd3ccf8>,
    + 1: <function convert_int at 0xd3cb90>,
    + 2: <function convert_int at 0xd3cb90>,
    + 3: <function convert_long at 0xd3cc08>,
    + 4: <function convert_float at 0xd3cc80>,
    + 5: <function convert_float at 0xd3cc80>,
    + 7: <function convert_mysql_timestamp at 0xd3c9b0>,
    + 8: <function convert_long at 0xd3cc08>,
    +...
    +
    +
    +
    +
    +
    + +
    + +
    +

    encoders

    + +
    +
    +
    +
    Value:
    +
    +{<type 'bool'>: <function escape_bool at 0xd3c2a8>,
    + <type 'float'>: <function escape_float at 0xd3c398>,
    + <type 'int'>: <function escape_object at 0xd3c320>,
    + <type 'list'>: <function escape_sequence at 0xd34c80>,
    + <type 'long'>: <function escape_object at 0xd3c320>,
    + <type 'dict'>: <function escape_dict at 0xd34a28>,
    + <type 'NoneType'>: <function escape_None at 0xd3c500>,
    + <type 'set'>: <function escape_sequence at 0xd34c80>,
    +...
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.converters-pysrc.html Index: applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.converters-pysrc.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.converters-pysrc.html +++ applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.converters-pysrc.html @@ -0,0 +1,481 @@ + + + + + web2py.gluon.contrib.pymysql.converters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Package contrib :: + Package pymysql :: + Module converters + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Module web2py.gluon.contrib.pymysql.converters

    +
    +  1  import re 
    +  2  import datetime 
    +  3  import time 
    +  4   
    +  5  from constants import FIELD_TYPE, FLAG 
    +  6  from charset import charset_by_id 
    +  7   
    +  8  try: 
    +  9      set 
    + 10  except NameError: 
    + 11      try: 
    + 12          from sets import BaseSet as set 
    + 13      except ImportError: 
    + 14          from sets import Set as set 
    + 15   
    + 16  ESCAPE_REGEX = re.compile(r"[\0\n\r\032\'\"\\]") 
    + 17  ESCAPE_MAP = {'\0': '\\0', '\n': '\\n', '\r': '\\r', '\032': '\\Z', 
    + 18                '\'': '\\\'', '"': '\\"', '\\': '\\\\'} 
    + 19   
    +
    20 -def escape_item(val, charset): +
    21 if type(val) in [tuple, list, set]: + 22 return escape_sequence(val, charset) + 23 if type(val) is dict: + 24 return escape_dict(val, charset) + 25 if hasattr(val, "decode") and not isinstance(val, unicode): + 26 # deal with py3k bytes + 27 val = val.decode(charset) + 28 encoder = encoders[type(val)] + 29 val = encoder(val) + 30 if type(val) is str: + 31 return val + 32 val = val.encode(charset) + 33 return val +
    34 +
    35 -def escape_dict(val, charset): +
    36 n = {} + 37 for k, v in val.items(): + 38 quoted = escape_item(v, charset) + 39 n[k] = quoted + 40 return n +
    41 +
    42 -def escape_sequence(val, charset): +
    43 n = [] + 44 for item in val: + 45 quoted = escape_item(item, charset) + 46 n.append(quoted) + 47 return tuple(n) +
    48 +
    49 -def escape_set(val, charset): +
    50 val = map(lambda x: escape_item(x, charset), val) + 51 return ','.join(val) +
    52 +
    53 -def escape_bool(value): +
    54 return str(int(value)) +
    55 +
    56 -def escape_object(value): +
    57 return str(value) +
    58 + 59 escape_int = escape_long = escape_object + 60 +
    61 -def escape_float(value): +
    62 return ('%.15g' % value) +
    63 +
    64 -def escape_string(value): +
    65 return ("'%s'" % ESCAPE_REGEX.sub( + 66 lambda match: ESCAPE_MAP.get(match.group(0)), value)) +
    67 +
    68 -def escape_unicode(value): +
    69 return escape_string(value) +
    70 +
    71 -def escape_None(value): +
    72 return 'NULL' +
    73 +
    74 -def escape_timedelta(obj): +
    75 seconds = int(obj.seconds) % 60 + 76 minutes = int(obj.seconds // 60) % 60 + 77 hours = int(obj.seconds // 3600) % 24 + int(obj.days) * 24 + 78 return escape_string('%02d:%02d:%02d' % (hours, minutes, seconds)) +
    79 +
    80 -def escape_time(obj): +
    81 s = "%02d:%02d:%02d" % (int(obj.hour), int(obj.minute), + 82 int(obj.second)) + 83 if obj.microsecond: + 84 s += ".%f" % obj.microsecond + 85 + 86 return escape_string(s) +
    87 +
    88 -def escape_datetime(obj): +
    89 return escape_string(obj.strftime("%Y-%m-%d %H:%M:%S")) +
    90 +
    91 -def escape_date(obj): +
    92 return escape_string(obj.strftime("%Y-%m-%d")) +
    93 +
    94 -def escape_struct_time(obj): +
    95 return escape_datetime(datetime.datetime(*obj[:6])) +
    96 +
    97 -def convert_datetime(connection, field, obj): +
    98 """Returns a DATETIME or TIMESTAMP column value as a datetime object: + 99 +100 >>> datetime_or_None('2007-02-25 23:06:20') +101 datetime.datetime(2007, 2, 25, 23, 6, 20) +102 >>> datetime_or_None('2007-02-25T23:06:20') +103 datetime.datetime(2007, 2, 25, 23, 6, 20) +104 +105 Illegal values are returned as None: +106 +107 >>> datetime_or_None('2007-02-31T23:06:20') is None +108 True +109 >>> datetime_or_None('0000-00-00 00:00:00') is None +110 True +111 +112 """ +113 if not isinstance(obj, unicode): +114 obj = obj.decode(connection.charset) +115 if ' ' in obj: +116 sep = ' ' +117 elif 'T' in obj: +118 sep = 'T' +119 else: +120 return convert_date(connection, field, obj) +121 +122 try: +123 ymd, hms = obj.split(sep, 1) +124 return datetime.datetime(*[ int(x) for x in ymd.split('-')+hms.split(':') ]) +125 except ValueError: +126 return convert_date(connection, field, obj) +
    127 +
    128 -def convert_timedelta(connection, field, obj): +
    129 """Returns a TIME column as a timedelta object: +130 +131 >>> timedelta_or_None('25:06:17') +132 datetime.timedelta(1, 3977) +133 >>> timedelta_or_None('-25:06:17') +134 datetime.timedelta(-2, 83177) +135 +136 Illegal values are returned as None: +137 +138 >>> timedelta_or_None('random crap') is None +139 True +140 +141 Note that MySQL always returns TIME columns as (+|-)HH:MM:SS, but +142 can accept values as (+|-)DD HH:MM:SS. The latter format will not +143 be parsed correctly by this function. +144 """ +145 from math import modf +146 try: +147 if not isinstance(obj, unicode): +148 obj = obj.decode(connection.charset) +149 hours, minutes, seconds = tuple([int(x) for x in obj.split(':')]) +150 tdelta = datetime.timedelta( +151 hours = int(hours), +152 minutes = int(minutes), +153 seconds = int(seconds), +154 microseconds = int(modf(float(seconds))[0]*1000000), +155 ) +156 return tdelta +157 except ValueError: +158 return None +
    159 +
    160 -def convert_time(connection, field, obj): +
    161 """Returns a TIME column as a time object: +162 +163 >>> time_or_None('15:06:17') +164 datetime.time(15, 6, 17) +165 +166 Illegal values are returned as None: +167 +168 >>> time_or_None('-25:06:17') is None +169 True +170 >>> time_or_None('random crap') is None +171 True +172 +173 Note that MySQL always returns TIME columns as (+|-)HH:MM:SS, but +174 can accept values as (+|-)DD HH:MM:SS. The latter format will not +175 be parsed correctly by this function. +176 +177 Also note that MySQL's TIME column corresponds more closely to +178 Python's timedelta and not time. However if you want TIME columns +179 to be treated as time-of-day and not a time offset, then you can +180 use set this function as the converter for FIELD_TYPE.TIME. +181 """ +182 from math import modf +183 try: +184 hour, minute, second = obj.split(':') +185 return datetime.time(hour=int(hour), minute=int(minute), +186 second=int(second), +187 microsecond=int(modf(float(second))[0]*1000000)) +188 except ValueError: +189 return None +
    190 +
    191 -def convert_date(connection, field, obj): +
    192 """Returns a DATE column as a date object: +193 +194 >>> date_or_None('2007-02-26') +195 datetime.date(2007, 2, 26) +196 +197 Illegal values are returned as None: +198 +199 >>> date_or_None('2007-02-31') is None +200 True +201 >>> date_or_None('0000-00-00') is None +202 True +203 +204 """ +205 try: +206 if not isinstance(obj, unicode): +207 obj = obj.decode(connection.charset) +208 return datetime.date(*[ int(x) for x in obj.split('-', 2) ]) +209 except ValueError: +210 return None +
    211 +
    212 -def convert_mysql_timestamp(connection, field, timestamp): +
    213 """Convert a MySQL TIMESTAMP to a Timestamp object. +214 +215 MySQL >= 4.1 returns TIMESTAMP in the same format as DATETIME: +216 +217 >>> mysql_timestamp_converter('2007-02-25 22:32:17') +218 datetime.datetime(2007, 2, 25, 22, 32, 17) +219 +220 MySQL < 4.1 uses a big string of numbers: +221 +222 >>> mysql_timestamp_converter('20070225223217') +223 datetime.datetime(2007, 2, 25, 22, 32, 17) +224 +225 Illegal values are returned as None: +226 +227 >>> mysql_timestamp_converter('2007-02-31 22:32:17') is None +228 True +229 >>> mysql_timestamp_converter('00000000000000') is None +230 True +231 +232 """ +233 if not isinstance(timestamp, unicode): +234 timestamp = timestamp.decode(connection.charset) +235 +236 if timestamp[4] == '-': +237 return convert_datetime(connection, field, timestamp) +238 timestamp += "0"*(14-len(timestamp)) # padding +239 year, month, day, hour, minute, second = \ +240 int(timestamp[:4]), int(timestamp[4:6]), int(timestamp[6:8]), \ +241 int(timestamp[8:10]), int(timestamp[10:12]), int(timestamp[12:14]) +242 try: +243 return datetime.datetime(year, month, day, hour, minute, second) +244 except ValueError: +245 return None +
    246 +
    247 -def convert_set(s): +
    248 return set(s.split(",")) +
    249 +
    250 -def convert_bit(connection, field, b): +
    251 #b = "\x00" * (8 - len(b)) + b # pad w/ zeroes +252 #return struct.unpack(">Q", b)[0] +253 # +254 # the snippet above is right, but MySQLdb doesn't process bits, +255 # so we shouldn't either +256 return b +
    257 +
    258 -def convert_characters(connection, field, data): +
    259 field_charset = charset_by_id(field.charsetnr).name +260 if field.flags & FLAG.SET: +261 return convert_set(data.decode(field_charset)) +262 if field.flags & FLAG.BINARY: +263 return data +264 +265 if connection.use_unicode: +266 data = data.decode(field_charset) +267 elif connection.charset != field_charset: +268 data = data.decode(field_charset) +269 data = data.encode(connection.charset) +270 else: +271 data = data.decode(connection.charset) +272 return data +
    273 +
    274 -def convert_int(connection, field, data): +
    275 return int(data) +
    276 +
    277 -def convert_long(connection, field, data): +
    278 return long(data) +
    279 +
    280 -def convert_float(connection, field, data): +
    281 return float(data) +
    282 +283 encoders = { +284 bool: escape_bool, +285 int: escape_int, +286 long: escape_long, +287 float: escape_float, +288 str: escape_string, +289 unicode: escape_unicode, +290 tuple: escape_sequence, +291 list:escape_sequence, +292 set:escape_sequence, +293 dict:escape_dict, +294 type(None):escape_None, +295 datetime.date: escape_date, +296 datetime.datetime : escape_datetime, +297 datetime.timedelta : escape_timedelta, +298 datetime.time : escape_time, +299 time.struct_time : escape_struct_time, +300 } +301 +302 decoders = { +303 FIELD_TYPE.BIT: convert_bit, +304 FIELD_TYPE.TINY: convert_int, +305 FIELD_TYPE.SHORT: convert_int, +306 FIELD_TYPE.LONG: convert_long, +307 FIELD_TYPE.FLOAT: convert_float, +308 FIELD_TYPE.DOUBLE: convert_float, +309 FIELD_TYPE.DECIMAL: convert_float, +310 FIELD_TYPE.NEWDECIMAL: convert_float, +311 FIELD_TYPE.LONGLONG: convert_long, +312 FIELD_TYPE.INT24: convert_int, +313 FIELD_TYPE.YEAR: convert_int, +314 FIELD_TYPE.TIMESTAMP: convert_mysql_timestamp, +315 FIELD_TYPE.DATETIME: convert_datetime, +316 FIELD_TYPE.TIME: convert_timedelta, +317 FIELD_TYPE.DATE: convert_date, +318 FIELD_TYPE.SET: convert_set, +319 FIELD_TYPE.BLOB: convert_characters, +320 FIELD_TYPE.TINY_BLOB: convert_characters, +321 FIELD_TYPE.MEDIUM_BLOB: convert_characters, +322 FIELD_TYPE.LONG_BLOB: convert_characters, +323 FIELD_TYPE.STRING: convert_characters, +324 FIELD_TYPE.VAR_STRING: convert_characters, +325 FIELD_TYPE.VARCHAR: convert_characters, +326 #FIELD_TYPE.BLOB: str, +327 #FIELD_TYPE.STRING: str, +328 #FIELD_TYPE.VAR_STRING: str, +329 #FIELD_TYPE.VARCHAR: str +330 } +331 conversions = decoders # for MySQLdb compatibility +332 +333 try: +334 # python version > 2.3 +335 from decimal import Decimal +
    336 - def convert_decimal(connection, field, data): +
    337 return Decimal(data) +
    338 decoders[FIELD_TYPE.DECIMAL] = convert_decimal +339 decoders[FIELD_TYPE.NEWDECIMAL] = convert_decimal +340 +
    341 - def escape_decimal(obj): +
    342 return unicode(obj) +
    343 encoders[Decimal] = escape_decimal +344 +345 except ImportError: +346 pass +347 +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.err.DataError-class.html Index: applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.err.DataError-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.err.DataError-class.html +++ applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.err.DataError-class.html @@ -0,0 +1,209 @@ + + + + + web2py.gluon.contrib.pymysql.err.DataError + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Package contrib :: + Package pymysql :: + Module err :: + Class DataError + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class DataError



    +
    +              object --+                    
    +                       |                    
    +exceptions.BaseException --+                
    +                           |                
    +        exceptions.Exception --+            
    +                               |            
    +                      MySQLError --+        
    +                                   |        
    +                               Error --+    
    +                                       |    
    +                           DatabaseError --+
    +                                           |
    +                                          DataError
    +
    + +
    +Exception raised for errors that are due to problems with the + processed data like division by zero, numeric value out of range, + etc.

    + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from exceptions.Exception: + __init__, + __new__ +

    +

    Inherited from exceptions.BaseException: + __delattr__, + __getattribute__, + __getitem__, + __getslice__, + __reduce__, + __repr__, + __setattr__, + __setstate__, + __str__ +

    +

    Inherited from object: + __hash__, + __reduce_ex__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from exceptions.BaseException: + args, + message +

    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.err.DatabaseError-class.html Index: applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.err.DatabaseError-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.err.DatabaseError-class.html +++ applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.err.DatabaseError-class.html @@ -0,0 +1,215 @@ + + + + + web2py.gluon.contrib.pymysql.err.DatabaseError + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Package contrib :: + Package pymysql :: + Module err :: + Class DatabaseError + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class DatabaseError



    +
    +              object --+                
    +                       |                
    +exceptions.BaseException --+            
    +                           |            
    +        exceptions.Exception --+        
    +                               |        
    +                      MySQLError --+    
    +                                   |    
    +                               Error --+
    +                                       |
    +                                      DatabaseError
    +
    + +
    Known Subclasses:
    +
    + DataError, + IntegrityError, + InternalError, + NotSupportedError, + OperationalError, + ProgrammingError +
    + +
    +Exception raised for errors that are related to the database.

    + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from exceptions.Exception: + __init__, + __new__ +

    +

    Inherited from exceptions.BaseException: + __delattr__, + __getattribute__, + __getitem__, + __getslice__, + __reduce__, + __repr__, + __setattr__, + __setstate__, + __str__ +

    +

    Inherited from object: + __hash__, + __reduce_ex__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from exceptions.BaseException: + args, + message +

    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.err.Error-class.html Index: applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.err.Error-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.err.Error-class.html +++ applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.err.Error-class.html @@ -0,0 +1,210 @@ + + + + + web2py.gluon.contrib.pymysql.err.Error + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Package contrib :: + Package pymysql :: + Module err :: + Class Error + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class Error



    +
    +              object --+            
    +                       |            
    +exceptions.BaseException --+        
    +                           |        
    +        exceptions.Exception --+    
    +                               |    
    +                      MySQLError --+
    +                                   |
    +                                  Error
    +
    + +
    Known Subclasses:
    +
    + DatabaseError, + InterfaceError +
    + +
    +Exception that is the base class of all other error exceptions (not + Warning).

    + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from exceptions.Exception: + __init__, + __new__ +

    +

    Inherited from exceptions.BaseException: + __delattr__, + __getattribute__, + __getitem__, + __getslice__, + __reduce__, + __repr__, + __setattr__, + __setstate__, + __str__ +

    +

    Inherited from object: + __hash__, + __reduce_ex__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from exceptions.BaseException: + args, + message +

    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.err.IntegrityError-class.html Index: applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.err.IntegrityError-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.err.IntegrityError-class.html +++ applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.err.IntegrityError-class.html @@ -0,0 +1,208 @@ + + + + + web2py.gluon.contrib.pymysql.err.IntegrityError + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Package contrib :: + Package pymysql :: + Module err :: + Class IntegrityError + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class IntegrityError



    +
    +              object --+                    
    +                       |                    
    +exceptions.BaseException --+                
    +                           |                
    +        exceptions.Exception --+            
    +                               |            
    +                      MySQLError --+        
    +                                   |        
    +                               Error --+    
    +                                       |    
    +                           DatabaseError --+
    +                                           |
    +                                          IntegrityError
    +
    + +
    +Exception raised when the relational integrity of the database is + affected, e.g. a foreign key check fails, duplicate key, etc.

    + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from exceptions.Exception: + __init__, + __new__ +

    +

    Inherited from exceptions.BaseException: + __delattr__, + __getattribute__, + __getitem__, + __getslice__, + __reduce__, + __repr__, + __setattr__, + __setstate__, + __str__ +

    +

    Inherited from object: + __hash__, + __reduce_ex__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from exceptions.BaseException: + args, + message +

    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.err.InterfaceError-class.html Index: applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.err.InterfaceError-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.err.InterfaceError-class.html +++ applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.err.InterfaceError-class.html @@ -0,0 +1,206 @@ + + + + + web2py.gluon.contrib.pymysql.err.InterfaceError + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Package contrib :: + Package pymysql :: + Module err :: + Class InterfaceError + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class InterfaceError



    +
    +              object --+                
    +                       |                
    +exceptions.BaseException --+            
    +                           |            
    +        exceptions.Exception --+        
    +                               |        
    +                      MySQLError --+    
    +                                   |    
    +                               Error --+
    +                                       |
    +                                      InterfaceError
    +
    + +
    +Exception raised for errors that are related to the database interface + rather than the database itself.

    + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from exceptions.Exception: + __init__, + __new__ +

    +

    Inherited from exceptions.BaseException: + __delattr__, + __getattribute__, + __getitem__, + __getslice__, + __reduce__, + __repr__, + __setattr__, + __setstate__, + __str__ +

    +

    Inherited from object: + __hash__, + __reduce_ex__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from exceptions.BaseException: + args, + message +

    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.err.InternalError-class.html Index: applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.err.InternalError-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.err.InternalError-class.html +++ applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.err.InternalError-class.html @@ -0,0 +1,208 @@ + + + + + web2py.gluon.contrib.pymysql.err.InternalError + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Package contrib :: + Package pymysql :: + Module err :: + Class InternalError + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class InternalError



    +
    +              object --+                    
    +                       |                    
    +exceptions.BaseException --+                
    +                           |                
    +        exceptions.Exception --+            
    +                               |            
    +                      MySQLError --+        
    +                                   |        
    +                               Error --+    
    +                                       |    
    +                           DatabaseError --+
    +                                           |
    +                                          InternalError
    +
    + +
    +Exception raised when the database encounters an internal error, e.g. + the cursor is not valid anymore, the transaction is out of sync, etc.

    + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from exceptions.Exception: + __init__, + __new__ +

    +

    Inherited from exceptions.BaseException: + __delattr__, + __getattribute__, + __getitem__, + __getslice__, + __reduce__, + __repr__, + __setattr__, + __setstate__, + __str__ +

    +

    Inherited from object: + __hash__, + __reduce_ex__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from exceptions.BaseException: + args, + message +

    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.err.NotSupportedError-class.html Index: applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.err.NotSupportedError-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.err.NotSupportedError-class.html +++ applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.err.NotSupportedError-class.html @@ -0,0 +1,210 @@ + + + + + web2py.gluon.contrib.pymysql.err.NotSupportedError + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Package contrib :: + Package pymysql :: + Module err :: + Class NotSupportedError + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class NotSupportedError



    +
    +              object --+                    
    +                       |                    
    +exceptions.BaseException --+                
    +                           |                
    +        exceptions.Exception --+            
    +                               |            
    +                      MySQLError --+        
    +                                   |        
    +                               Error --+    
    +                                       |    
    +                           DatabaseError --+
    +                                           |
    +                                          NotSupportedError
    +
    + +
    +Exception raised in case a method or database API was used which is + not supported by the database, e.g. requesting a .rollback() on a + connection that does not support transaction or has transactions turned + off.

    + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from exceptions.Exception: + __init__, + __new__ +

    +

    Inherited from exceptions.BaseException: + __delattr__, + __getattribute__, + __getitem__, + __getslice__, + __reduce__, + __repr__, + __setattr__, + __setstate__, + __str__ +

    +

    Inherited from object: + __hash__, + __reduce_ex__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from exceptions.BaseException: + args, + message +

    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.err.OperationalError-class.html Index: applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.err.OperationalError-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.err.OperationalError-class.html +++ applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.err.OperationalError-class.html @@ -0,0 +1,211 @@ + + + + + web2py.gluon.contrib.pymysql.err.OperationalError + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Package contrib :: + Package pymysql :: + Module err :: + Class OperationalError + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class OperationalError



    +
    +              object --+                    
    +                       |                    
    +exceptions.BaseException --+                
    +                           |                
    +        exceptions.Exception --+            
    +                               |            
    +                      MySQLError --+        
    +                                   |        
    +                               Error --+    
    +                                       |    
    +                           DatabaseError --+
    +                                           |
    +                                          OperationalError
    +
    + +
    +Exception raised for errors that are related to the database's + operation and not necessarily under the control of the programmer, e.g. + an unexpected disconnect occurs, the data source name is not found, a + transaction could not be processed, a memory allocation error occurred + during processing, etc.

    + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from exceptions.Exception: + __init__, + __new__ +

    +

    Inherited from exceptions.BaseException: + __delattr__, + __getattribute__, + __getitem__, + __getslice__, + __reduce__, + __repr__, + __setattr__, + __setstate__, + __str__ +

    +

    Inherited from object: + __hash__, + __reduce_ex__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from exceptions.BaseException: + args, + message +

    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.err.ProgrammingError-class.html Index: applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.err.ProgrammingError-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.err.ProgrammingError-class.html +++ applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.err.ProgrammingError-class.html @@ -0,0 +1,209 @@ + + + + + web2py.gluon.contrib.pymysql.err.ProgrammingError + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Package contrib :: + Package pymysql :: + Module err :: + Class ProgrammingError + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class ProgrammingError



    +
    +              object --+                    
    +                       |                    
    +exceptions.BaseException --+                
    +                           |                
    +        exceptions.Exception --+            
    +                               |            
    +                      MySQLError --+        
    +                                   |        
    +                               Error --+    
    +                                       |    
    +                           DatabaseError --+
    +                                           |
    +                                          ProgrammingError
    +
    + +
    +Exception raised for programming errors, e.g. table not found or + already exists, syntax error in the SQL statement, wrong number of + parameters specified, etc.

    + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from exceptions.Exception: + __init__, + __new__ +

    +

    Inherited from exceptions.BaseException: + __delattr__, + __getattribute__, + __getitem__, + __getslice__, + __reduce__, + __repr__, + __setattr__, + __setstate__, + __str__ +

    +

    Inherited from object: + __hash__, + __reduce_ex__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from exceptions.BaseException: + args, + message +

    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.err.Warning-class.html Index: applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.err.Warning-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.err.Warning-class.html +++ applications/examples/static/epydoc/web2py.gluon.contrib.pymysql.err.Warning-class.html @@ -0,0 +1,212 @@ + + + + + web2py.gluon.contrib.pymysql.err.Warning + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Package contrib :: + Package pymysql :: + Module err :: + Class Warning + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class Warning



    +
    +              object --+            
    +                       |            
    +exceptions.BaseException --+        
    +                           |        
    +        exceptions.Exception --+    
    +                               |    
    +              exceptions.Warning --+
    +                                   |
    +              object --+           |
    +                       |           |
    +exceptions.BaseException --+       |
    +                           |       |
    +        exceptions.Exception --+   |
    +                               |   |
    +                      MySQLError --+
    +                                   |
    +                                  Warning
    +
    + +
    +Exception raised for important warnings like data truncations while + inserting, etc.

    + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from exceptions.Warning: + __init__, + __new__ +

    +

    Inherited from exceptions.BaseException: + __delattr__, + __getattribute__, + __getitem__, + __getslice__, + __reduce__, + __repr__, + __setattr__, + __setstate__, + __str__ +

    +

    Inherited from object: + __hash__, + __reduce_ex__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from exceptions.BaseException: + args, + message +

    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.custom_import-module.html Index: applications/examples/static/epydoc/web2py.gluon.custom_import-module.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.custom_import-module.html +++ applications/examples/static/epydoc/web2py.gluon.custom_import-module.html @@ -0,0 +1,352 @@ + + + + + web2py.gluon.custom_import + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module custom_import + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Module custom_import

    source code

    + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Classes[hide private]
    +
    +   + + _BaseImporter
    + The base importer. +
    +   + + _DateTrackerImporter
    + An importer tracking the date of the module files and reloading + them when they have changed. +
    +   + + _Web2pyImporter
    + The standard web2py importer. +
    +   + + _Web2pyDateTrackerImporter
    + Like _Web2pyImporter but using a _DateTrackerImporter. +
    + + + + + + + + + + + + + + + +
    + + + + + +
    Functions[hide private]
    +
    +   + + + + + + +
    custom_import_install(web2py_path) + source code + +
    + +
    +   + + + + + + +
    is_tracking_changes()
    + Returns: + True: neo_importer is tracking changes made to Python source + files.
    + source code + +
    + +
    +   + + + + + + +
    track_changes(track=True)
    + Tell neo_importer to start/stop tracking changes made to Python + modules.
    + source code + +
    + +
    + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Variables[hide private]
    +
    +   + + _web2py_importer = 1
    + PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    +   + + _web2py_date_tracker_importer = 1
    + PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    +   + + _web2py_path = 1
    + PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    +   + + _is_tracking_changes = True +
    + + + + + + +
    + + + + + +
    Function Details[hide private]
    +
    + +
    + +
    + + +
    +

    is_tracking_changes() +

    +
    source code  +
    + + +
    +
    Returns:
    +
    True: neo_importer is tracking changes made to Python source + files. False: neo_import does not reload Python modules.
    +
    +
    +
    + +
    + +
    + + +
    +

    track_changes(track=True) +

    +
    source code  +
    + + Tell neo_importer to start/stop tracking changes made to Python + modules. +
    +
    Parameters:
    +
      +
    • track - True: Start tracking changes. False: Stop tracking + changes.
    • +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.custom_import-pysrc.html Index: applications/examples/static/epydoc/web2py.gluon.custom_import-pysrc.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.custom_import-pysrc.html +++ applications/examples/static/epydoc/web2py.gluon.custom_import-pysrc.html @@ -0,0 +1,942 @@ + + + + + web2py.gluon.custom_import + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module custom_import + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Module web2py.gluon.custom_import

    +
    +  1  #!/usr/bin/env python 
    +  2  # -*- coding: utf-8 -*- 
    +  3   
    +  4  import __builtin__ 
    +  5  import os 
    +  6  import re 
    +  7  import sys 
    +  8  import threading 
    +  9   
    + 10  # Install the new import function: 
    +
    11 -def custom_import_install(web2py_path): +
    12 global _web2py_importer + 13 global _web2py_path + 14 if _web2py_importer: + 15 return # Already installed + 16 _web2py_path = web2py_path + 17 _web2py_importer = _Web2pyImporter(web2py_path) + 18 __builtin__.__import__ = _web2py_importer +
    19 +
    21 """ + 22 @return: True: neo_importer is tracking changes made to Python source + 23 files. False: neo_import does not reload Python modules. + 24 """ + 25 + 26 global _is_tracking_changes + 27 return _is_tracking_changes +
    28 +
    29 -def track_changes(track=True): +
    30 """ + 31 Tell neo_importer to start/stop tracking changes made to Python modules. + 32 @param track: True: Start tracking changes. False: Stop tracking changes. + 33 """ + 34 + 35 global _is_tracking_changes + 36 global _web2py_importer + 37 global _web2py_date_tracker_importer + 38 assert track is True or track is False, "Boolean expected." + 39 if track == _is_tracking_changes: + 40 return + 41 if track: + 42 if not _web2py_date_tracker_importer: + 43 _web2py_date_tracker_importer = \ + 44 _Web2pyDateTrackerImporter(_web2py_path) + 45 __builtin__.__import__ = _web2py_date_tracker_importer + 46 else: + 47 __builtin__.__import__ = _web2py_importer + 48 _is_tracking_changes = track +
    49 + 50 _STANDARD_PYTHON_IMPORTER = __builtin__.__import__ # Keep standard importer + 51 _web2py_importer = None # The standard web2py importer + 52 _web2py_date_tracker_importer = None # The web2py importer with date tracking + 53 _web2py_path = None # Absolute path of the web2py directory + 54 + 55 _is_tracking_changes = False # The tracking mode + 56 +
    57 -class _BaseImporter(object): +
    58 """ + 59 The base importer. Dispatch the import the call to the standard Python + 60 importer. + 61 """ + 62 +
    63 - def begin(self): +
    64 """ + 65 Many imports can be made for a single import statement. This method + 66 help the management of this aspect. + 67 """ +
    68 +
    69 - def __call__(self, name, globals={}, locals={}, fromlist=[], level=-1): +
    70 """ + 71 The import method itself. + 72 """ + 73 return _STANDARD_PYTHON_IMPORTER(name, globals, locals, fromlist, + 74 level) +
    75 +
    76 - def end(self): +
    77 """ + 78 Needed for clean up. + 79 """ +
    80 + 81 +
    82 -class _DateTrackerImporter(_BaseImporter): +
    83 """ + 84 An importer tracking the date of the module files and reloading them when + 85 they have changed. + 86 """ + 87 + 88 _PACKAGE_PATH_SUFFIX = os.path.sep+"__init__.py" + 89 +
    90 - def __init__(self): +
    91 super(_DateTrackerImporter, self).__init__() + 92 self._import_dates = {} # Import dates of the files of the modules + 93 # Avoid reloading cause by file modifications of reload: + 94 self._tl = threading.local() + 95 self._tl._modules_loaded = None +
    96 +
    97 - def begin(self): +
    98 self._tl._modules_loaded = set() +
    99 +
    100 - def __call__(self, name, globals={}, locals={}, fromlist=[], level=-1): +
    101 """ +102 The import method itself. +103 """ +104 +105 call_begin_end = self._tl._modules_loaded == None +106 if call_begin_end: +107 self.begin() +108 +109 try: +110 self._tl.globals = globals +111 self._tl.locals = locals +112 self._tl.level = level +113 +114 # Check the date and reload if needed: +115 self._update_dates(name, fromlist) +116 +117 # Try to load the module and update the dates if it works: +118 result = super(_DateTrackerImporter, self) \ +119 .__call__(name, globals, locals, fromlist, level) +120 # Module maybe loaded for the 1st time so we need to set the date +121 self._update_dates(name, fromlist) +122 return result +123 except Exception, e: +124 raise e # Don't hide something that went wrong +125 finally: +126 if call_begin_end: +127 self.end() +
    128 +
    129 - def _update_dates(self, name, fromlist): +
    130 """ +131 Update all the dates associated to the statement import. A single +132 import statement may import many modules. +133 """ +134 +135 self._reload_check(name) +136 if fromlist: +137 for fromlist_name in fromlist: +138 self._reload_check("%s.%s" % (name, fromlist_name)) +
    139 +
    140 - def _reload_check(self, name): +
    141 """ +142 Update the date associated to the module and reload the module if +143 the file has changed. +144 """ +145 +146 module = sys.modules.get(name) +147 file = self._get_module_file(module) +148 if file: +149 date = self._import_dates.get(file) +150 new_date = None +151 reload_mod = False +152 mod_to_pack = False # Module turning into a package? (special case) +153 try: +154 new_date = os.path.getmtime(file) +155 except: +156 self._import_dates.pop(file, None) # Clean up +157 # Handle module changing in package and +158 #package changing in module: +159 if file.endswith(".py"): +160 # Get path without file ext: +161 file = os.path.splitext(file)[0] +162 reload_mod = os.path.isdir(file) \ +163 and os.path.isfile(file+self._PACKAGE_PATH_SUFFIX) +164 mod_to_pack = reload_mod +165 else: # Package turning into module? +166 file += ".py" +167 reload_mod = os.path.isfile(file) +168 if reload_mod: +169 new_date = os.path.getmtime(file) # Refresh file date +170 if reload_mod or not date or new_date > date: +171 self._import_dates[file] = new_date +172 if reload_mod or (date and new_date > date): +173 if module not in self._tl._modules_loaded: +174 if mod_to_pack: +175 # Module turning into a package: +176 mod_name = module.__name__ +177 del sys.modules[mod_name] # Delete the module +178 # Reload the module: +179 super(_DateTrackerImporter, self).__call__ \ +180 (mod_name, self._tl.globals, self._tl.locals, [], +181 self._tl.level) +182 else: +183 reload(module) +184 self._tl._modules_loaded.add(module) +
    185 +
    186 - def end(self): +
    187 self._tl._modules_loaded = None +
    188 +189 @classmethod +
    190 - def _get_module_file(cls, module): +
    191 """ +192 Get the absolute path file associated to the module or None. +193 """ +194 +195 file = getattr(module, "__file__", None) +196 if file: +197 # Make path absolute if not: +198 #file = os.path.join(cls.web2py_path, file) +199 +200 file = os.path.splitext(file)[0]+".py" # Change .pyc for .py +201 if file.endswith(cls._PACKAGE_PATH_SUFFIX): +202 file = os.path.dirname(file) # Track dir for packages +203 return file +
    204 +
    205 -class _Web2pyImporter(_BaseImporter): +
    206 """ +207 The standard web2py importer. Like the standard Python importer but it +208 tries to transform import statements as something like +209 "import applications.app_name.modules.x". If the import failed, fall back +210 on _BaseImporter. +211 """ +212 +213 _RE_ESCAPED_PATH_SEP = re.escape(os.path.sep) # os.path.sep escaped for re +214 +
    215 - def __init__(self, web2py_path): +
    216 """ +217 @param web2py_path: The absolute path of the web2py installation. +218 """ +219 +220 global DEBUG +221 super(_Web2pyImporter, self).__init__() +222 self.web2py_path = web2py_path +223 self.__web2py_path_os_path_sep = self.web2py_path+os.path.sep +224 self.__web2py_path_os_path_sep_len = len(self.__web2py_path_os_path_sep) +225 self.__RE_APP_DIR = re.compile( +226 self._RE_ESCAPED_PATH_SEP.join( \ +227 ( \ +228 #"^" + re.escape(web2py_path), # Not working with Python 2.5 +229 "^(" + "applications", +230 "[^", +231 "]+)", +232 "", +233 ) )) +
    234 +
    235 - def _matchAppDir(self, file_path): +
    236 """ +237 Does the file in a directory inside the "applications" directory? +238 """ +239 +240 if file_path.startswith(self.__web2py_path_os_path_sep): +241 file_path = file_path[self.__web2py_path_os_path_sep_len:] +242 return self.__RE_APP_DIR.match(file_path) +243 return False +
    244 +
    245 - def __call__(self, name, globals={}, locals={}, fromlist=[], level=-1): +
    246 """ +247 The import method itself. +248 """ +249 +250 self.begin() +251 #try: +252 # if not relative and not from applications: +253 if not name.startswith(".") and level <= 0 \ +254 and not name.startswith("applications.") \ +255 and isinstance(globals, dict): +256 # Get the name of the file do the import +257 caller_file_name = os.path.join(self.web2py_path, \ +258 globals.get("__file__", "")) +259 # Is the path in an application directory? +260 match_app_dir = self._matchAppDir(caller_file_name) +261 if match_app_dir: +262 try: +263 # Get the prefix to add for the import +264 # (like applications.app_name.modules): +265 modules_prefix = \ +266 ".".join((match_app_dir.group(1). \ +267 replace(os.path.sep, "."), "modules")) +268 if not fromlist: +269 # import like "import x" or "import x.y" +270 return self.__import__dot(modules_prefix, name, +271 globals, locals, fromlist, level) +272 else: +273 # import like "from x import a, b, ..." +274 return super(_Web2pyImporter, self) \ +275 .__call__(modules_prefix+"."+name, +276 globals, locals, fromlist, level) +277 except ImportError: +278 pass +279 return super(_Web2pyImporter, self).__call__(name, globals, locals, +280 fromlist, level) +281 #except Exception, e: +282 # raise e # Don't hide something that went wrong +283 #finally: +284 self.end() +
    285 +
    286 - def __import__dot(self, prefix, name, globals, locals, fromlist, +287 level): +
    288 """ +289 Here we will import x.y.z as many imports like: +290 from applications.app_name.modules import x +291 from applications.app_name.modules.x import y +292 from applications.app_name.modules.x.y import z. +293 x will be the module returned. +294 """ +295 +296 result = None +297 for name in name.split("."): +298 new_mod = super(_Web2pyImporter, self).__call__(prefix, globals, +299 locals, [name], level) +300 try: +301 result = result or new_mod.__dict__[name] +302 except KeyError: +303 raise ImportError() +304 prefix += "." + name +305 return result +
    306 +
    307 -class _Web2pyDateTrackerImporter(_Web2pyImporter, _DateTrackerImporter): +
    308 """ +309 Like _Web2pyImporter but using a _DateTrackerImporter. +310 """ +
    311 +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.custom_import._BaseImporter-class.html Index: applications/examples/static/epydoc/web2py.gluon.custom_import._BaseImporter-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.custom_import._BaseImporter-class.html +++ applications/examples/static/epydoc/web2py.gluon.custom_import._BaseImporter-class.html @@ -0,0 +1,286 @@ + + + + + web2py.gluon.custom_import._BaseImporter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module custom_import :: + Class _BaseImporter + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class _BaseImporter

    source code

    +
    +object --+
    +         |
    +        _BaseImporter
    +
    + +
    Known Subclasses:
    +
    + _DateTrackerImporter, + _Web2pyImporter +
    + +
    +The base importer. Dispatch the import the call to the standard Python + importer.

    + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    begin(self)
    + Many imports can be made for a single import statement.
    + source code + +
    + +
    +   + + + + + + +
    __call__(self, + name, + globals={}, + locals={}, + fromlist=[], + level=-1)
    + The import method itself.
    + source code + +
    + +
    +   + + + + + + +
    end(self)
    + Needed for clean up.
    + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __init__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    begin(self) +

    +
    source code  +
    + + Many imports can be made for a single import statement. This method + help the management of this aspect. +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.custom_import._DateTrackerImporter-class.html Index: applications/examples/static/epydoc/web2py.gluon.custom_import._DateTrackerImporter-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.custom_import._DateTrackerImporter-class.html +++ applications/examples/static/epydoc/web2py.gluon.custom_import._DateTrackerImporter-class.html @@ -0,0 +1,505 @@ + + + + + web2py.gluon.custom_import._DateTrackerImporter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module custom_import :: + Class _DateTrackerImporter + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class _DateTrackerImporter

    source code

    +
    +   object --+    
    +            |    
    +_BaseImporter --+
    +                |
    +               _DateTrackerImporter
    +
    + +
    Known Subclasses:
    +
    + _Web2pyDateTrackerImporter +
    + +
    +An importer tracking the date of the module files and reloading them + when they have changed.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self)
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    begin(self)
    + Many imports can be made for a single import statement.
    + source code + +
    + +
    +   + + + + + + +
    __call__(self, + name, + globals={}, + locals={}, + fromlist=[], + level=-1)
    + The import method itself.
    + source code + +
    + +
    +   + + + + + + +
    _update_dates(self, + name, + fromlist)
    + Update all the dates associated to the statement import.
    + source code + +
    + +
    +   + + + + + + +
    _reload_check(self, + name)
    + Update the date associated to the module and reload the module if + the file has changed.
    + source code + +
    + +
    +   + + + + + + +
    end(self)
    + Needed for clean up.
    + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Class Methods[hide private]
    +
    +   + + + + + + +
    _get_module_file(cls, + module)
    + Get the absolute path file associated to the module or None.
    + source code + +
    + +
    + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + _PACKAGE_PATH_SUFFIX = '/__init__.py' +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    begin(self) +

    +
    source code  +
    + + Many imports can be made for a single import statement. This method + help the management of this aspect. +
    +
    Overrides: + _BaseImporter.begin +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    __call__(self, + name, + globals={}, + locals={}, + fromlist=[], + level=-1) +
    (Call operator) +

    +
    source code  +
    + + The import method itself. +
    +
    Overrides: + _BaseImporter.__call__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    _update_dates(self, + name, + fromlist) +

    +
    source code  +
    + + Update all the dates associated to the statement import. A single + import statement may import many modules. +
    +
    +
    +
    + +
    + +
    + + +
    +

    end(self) +

    +
    source code  +
    + + Needed for clean up. +
    +
    Overrides: + _BaseImporter.end +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.custom_import._Web2pyDateTrackerImporter-class.html Index: applications/examples/static/epydoc/web2py.gluon.custom_import._Web2pyDateTrackerImporter-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.custom_import._Web2pyDateTrackerImporter-class.html +++ applications/examples/static/epydoc/web2py.gluon.custom_import._Web2pyDateTrackerImporter-class.html @@ -0,0 +1,262 @@ + + + + + web2py.gluon.custom_import._Web2pyDateTrackerImporter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module custom_import :: + Class _Web2pyDateTrackerImporter + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class _Web2pyDateTrackerImporter

    source code

    +
    +      object --+        
    +               |        
    +   _BaseImporter --+    
    +                   |    
    +     _Web2pyImporter --+
    +                       |
    +      object --+       |
    +               |       |
    +   _BaseImporter --+   |
    +                   |   |
    +_DateTrackerImporter --+
    +                       |
    +                      _Web2pyDateTrackerImporter
    +
    + +
    +Like _Web2pyImporter but using a _DateTrackerImporter.

    + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from _Web2pyImporter: + __call__, + __init__ +

    +

    Inherited from _Web2pyImporter (private): + _Web2pyImporter__import__dot, + _matchAppDir +

    +

    Inherited from _DateTrackerImporter: + begin, + end +

    +

    Inherited from _DateTrackerImporter (private): + _reload_check, + _update_dates +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Class Methods[hide private]
    +
    +

    Inherited from _DateTrackerImporter (private): + _get_module_file +

    +
    + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +

    Inherited from _Web2pyImporter (private): + _RE_ESCAPED_PATH_SEP +

    +

    Inherited from _DateTrackerImporter (private): + _PACKAGE_PATH_SUFFIX +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.custom_import._Web2pyImporter-class.html Index: applications/examples/static/epydoc/web2py.gluon.custom_import._Web2pyImporter-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.custom_import._Web2pyImporter-class.html +++ applications/examples/static/epydoc/web2py.gluon.custom_import._Web2pyImporter-class.html @@ -0,0 +1,468 @@ + + + + + web2py.gluon.custom_import._Web2pyImporter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module custom_import :: + Class _Web2pyImporter + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class _Web2pyImporter

    source code

    +
    +   object --+    
    +            |    
    +_BaseImporter --+
    +                |
    +               _Web2pyImporter
    +
    + +
    Known Subclasses:
    +
    + _Web2pyDateTrackerImporter +
    + +
    +The standard web2py importer. Like the standard Python importer but it + tries to transform import statements as something like "import + applications.app_name.modules.x". If the import failed, fall back on + _BaseImporter.

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + web2py_path)
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    _matchAppDir(self, + file_path)
    + Does the file in a directory inside the "applications" + directory?
    + source code + +
    + +
    +   + + + + + + +
    __call__(self, + name, + globals={}, + locals={}, + fromlist=[], + level=-1)
    + The import method itself.
    + source code + +
    + +
    +   + + + + + + +
    __import__dot(self, + prefix, + name, + globals, + locals, + fromlist, + level)
    + Here we will import x.y.z as many imports like: from + applications.app_name.modules import x from + applications.app_name.modules.x import y from + applications.app_name.modules.x.y import z.
    + source code + +
    + +
    +   + + + + + + +
    _Web2pyImporter__import__dot(self, + prefix, + name, + globals, + locals, + fromlist, + level)
    + Here we will import x.y.z as many imports like: from + applications.app_name.modules import x from + applications.app_name.modules.x import y from + applications.app_name.modules.x.y import z.
    + source code + +
    + +
    +

    Inherited from _BaseImporter: + begin, + end +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + _RE_ESCAPED_PATH_SEP = '\\/' +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + web2py_path) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Parameters:
    +
      +
    • web2py_path - The absolute path of the web2py installation.
    • +
    +
    Overrides: + object.__init__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    __call__(self, + name, + globals={}, + locals={}, + fromlist=[], + level=-1) +
    (Call operator) +

    +
    source code  +
    + + The import method itself. +
    +
    Overrides: + _BaseImporter.__call__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    __import__dot(self, + prefix, + name, + globals, + locals, + fromlist, + level) +

    +
    source code  +
    + + Here we will import x.y.z as many imports like: from + applications.app_name.modules import x from + applications.app_name.modules.x import y from + applications.app_name.modules.x.y import z. x will be the module + returned. +
    +
    +
    +
    + +
    + +
    + + +
    +

    _Web2pyImporter__import__dot(self, + prefix, + name, + globals, + locals, + fromlist, + level) +

    +
    source code  +
    + + Here we will import x.y.z as many imports like: from + applications.app_name.modules import x from + applications.app_name.modules.x import y from + applications.app_name.modules.x.y import z. x will be the module + returned. +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.dal-module.html Index: applications/examples/static/epydoc/web2py.gluon.dal-module.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.dal-module.html +++ applications/examples/static/epydoc/web2py.gluon.dal-module.html @@ -0,0 +1,1426 @@ + + + + + web2py.gluon.dal + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module dal + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Module dal

    source code

    +
    +
    +This file is part of the web2py Web Framework
    +Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
    +License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
    +
    +Thanks to
    +    * Niall Sweeny <niall.sweeny@fonjax.com> for MS SQL support
    +    * Marcel Leuthi <mluethi@mlsystems.ch> for Oracle support
    +    * Denes
    +    * Chris Clark
    +    * clach05
    +    * Denes Lengyel
    +    * and many others who have contributed to current and previous versions
    +
    +This file contains the DAL support for many relational databases,
    +including:
    +- SQLite
    +- MySQL
    +- Postgres
    +- Oracle
    +- MS SQL
    +- DB2
    +- Interbase
    +- Ingres
    +- SapDB (experimental)
    +- Cubrid (experimental)
    +- CouchDB (experimental)
    +- MongoDB (in progress)
    +- Google:nosql
    +- Google:sql
    +
    +Example of usage:
    +
    +>>> # from dal import DAL, Field
    +
    +### create DAL connection (and create DB if not exists)
    +>>> db=DAL(('mysql://a:b@locahost/x','sqlite://storage.sqlite'),folder=None)
    +
    +### define a table 'person' (create/aster as necessary)
    +>>> person = db.define_table('person',Field('name','string'))
    +
    +### insert a record
    +>>> id = person.insert(name='James')
    +
    +### retrieve it by id
    +>>> james = person(id)
    +
    +### retrieve it by name
    +>>> james = person(name='James')
    +
    +### retrieve it by arbitrary query
    +>>> query = (person.name=='James')&(person.name.startswith('J'))
    +>>> james = db(query).select(person.ALL)[0]
    +
    +### update one record
    +>>> james.update_record(name='Jim')
    +
    +### update multiple records by query
    +>>> db(person.name.like('J%')).update(name='James')
    +1
    +
    +### delete records by query
    +>>> db(person.name.lower()=='jim').delete()
    +0
    +
    +### retrieve multiple records (rows)
    +>>> people = db(person).select(orderby=person.name,groupby=person.name,limitby=(0,100))
    +
    +### further filter them
    +>>> james = people.find(lambda row: row.name=='James').first()
    +>>> print james.id, james.name
    +1 James
    +
    +### check aggrgates
    +>>> counter = person.id.count()
    +>>> print db(person).select(counter).first()(counter)
    +1
    +
    +### delete one record
    +>>> james.delete_record()
    +1
    +
    +### delete (drop) entire database table
    +>>> person.drop()
    +
    +Supported field types:
    +id string text boolean integer double decimal password upload blob time date datetime,
    +
    +Supported DAL URI strings:
    +'sqlite://test.db'
    +'sqlite:memory'
    +'jdbc:sqlite://test.db'
    +'mysql://root:none@localhost/test'
    +'postgres://mdipierro:none@localhost/test'
    +'jdbc:postgres://mdipierro:none@localhost/test'
    +'mssql://web2py:none@A64X2/web2py_test'
    +'mssql2://web2py:none@A64X2/web2py_test' # alternate mappings
    +'oracle://username:password@database'
    +'firebird://user:password@server:3050/database'
    +'db2://DSN=dsn;UID=user;PWD=pass'
    +'firebird://username:password@hostname/database'
    +'firebird_embedded://username:password@c://path'
    +'informix://user:password@server:3050/database'
    +'informixu://user:password@server:3050/database' # unicode informix
    +'google:datastore' # for google app engine datastore
    +'google:sql' # for google app engine with sql (mysql compatible)
    +'teradata://DSN=dsn;UID=user;PWD=pass' # experimental 
    +
    +For more info:
    +help(DAL)
    +help(Field)
    +
    +


    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Classes[hide private]
    +
    +   + + GAEDecimalProperty
    + GAE decimal implementation +
    +   + + ConnectionPool +
    +   + + BaseAdapter +
    +   + + SQLiteAdapter +
    +   + + JDBCSQLiteAdapter +
    +   + + MySQLAdapter +
    +   + + PostgreSQLAdapter +
    +   + + JDBCPostgreSQLAdapter +
    +   + + OracleAdapter +
    +   + + MSSQLAdapter +
    +   + + MSSQL2Adapter +
    +   + + FireBirdAdapter +
    +   + + FireBirdEmbeddedAdapter +
    +   + + InformixAdapter +
    +   + + DB2Adapter +
    +   + + TeradataAdapter +
    +   + + IngresAdapter +
    +   + + IngresUnicodeAdapter +
    +   + + SAPDBAdapter +
    +   + + CubridAdapter +
    +   + + DatabaseStoredFile +
    +   + + UseDatabaseStoredFile +
    +   + + GoogleSQLAdapter +
    +   + + NoSQLAdapter +
    +   + + GAEF +
    +   + + GoogleDatastoreAdapter +
    +   + + CouchDBAdapter +
    +   + + MongoDBAdapter +
    +   + + Row
    + a dictionary that lets you do d['a'] as well as d.a this is only + used to store a Row +
    +   + + SQLCallableList +
    +   + + DAL
    + an instance of this class represents a database connection +
    +   + + SQLALL
    + Helper class providing a comma-separated string having all the + field names (prefixed by table name and '.') +
    +   + + Reference +
    +   + + Table
    + an instance of this class represents a database table +
    +   + + Expression +
    +   + + SQLCustomType
    + allows defining of custom SQL types... +
    +   + + Field
    + an instance of this class represents a database field +
    +   + + Query
    + a query object necessary to define a set. +
    +   + + Set
    + a Set represents a set of records in the database, +the records are identified by the query=Query(...) object. +
    +   + + Rows
    + A wrapper for the return value of a select. +
    +   + + SQLField
    + an instance of this class represents a database field +
    +   + + SQLTable
    + an instance of this class represents a database table +
    +   + + SQLXorable +
    +   + + SQLQuery
    + a query object necessary to define a set. +
    +   + + SQLSet
    + a Set represents a set of records in the database, +the records are identified by the query=Query(...) object. +
    +   + + SQLRows
    + A wrapper for the return value of a select. +
    +   + + SQLStorage
    + a dictionary that lets you do d['a'] as well as d.a this is only + used to store a Row +
    +   + + SQLDB
    + an instance of this class represents a database connection +
    +   + + GQLDB
    + an instance of this class represents a database connection +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Functions[hide private]
    +
    +   + + + + + + +
    DEFAULT() + source code + +
    + +
    +   + + + + + + +
    uuid2int(uuidv) + source code + +
    + +
    +   + + + + + + +
    int2uuid(n) + source code + +
    + +
    +   + + + + + + +
    cleanup(text)
    + validates that the given text is clean: only contains + [0-9a-zA-Z_]
    + source code + +
    + +
    +   + + + + + + +
    sqlhtml_validators(field)
    + Field type validation, using web2py's validators mechanism.
    + source code + +
    + +
    +   + + + + + + +
    bar_escape(item) + source code + +
    + +
    +   + + + + + + +
    bar_encode(items) + source code + +
    + +
    +   + + + + + + +
    bar_decode_integer(value) + source code + +
    + +
    +   + + + + + + +
    bar_decode_string(value) + source code + +
    + +
    +   + + + + + + +
    Row_unpickler(data) + source code + +
    + +
    +   + + + + + + +
    Row_pickler(data) + source code + +
    + +
    +   + + + + + + +
    Reference_unpickler(data) + source code + +
    + +
    +   + + + + + + +
    Reference_pickler(data) + source code + +
    + +
    +   + + + + + + +
    xorify(orderby) + source code + +
    + +
    +   + + + + + + +
    update_record(pack, + a={}) + source code + +
    + +
    +   + + + + + + +
    Rows_unpickler(data) + source code + +
    + +
    +   + + + + + + +
    Rows_pickler(data) + source code + +
    + +
    +   + + + + + + +
    test_all()
    + >>> if len(sys.argv)<2: db = DAL("sqlite://test.db")...
    + source code + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Variables[hide private]
    +
    +   + + MAXCHARLENGTH = 512 +
    +   + + INFINITY = 32768 +
    +   + + CALLABLETYPES = (<type 'function'>, <type 'function'>, <type '... +
    +   + + have_portalocker = True +
    +   + + have_serializers = True +
    +   + + have_validators = True +
    +   + + logger = logging.getLogger("web2py.dal") +
    +   + + sql_locker = <_RLock(None, 0)> +
    +   + + thread = threading.local() +
    +   + + regex_dbname = re.compile(r'^(\w+)(:\w+)*') +
    +   + + table_field = re.compile(r'^[\w_]+\.[\w_]+$') +
    +   + + regex_content = re.compile(r'(?P<table>[\w-]+)\.(?P<field>[\w-... +
    +   + + regex_cleanup_fn = re.compile(r'[\'"\s;]+') +
    +   + + string_unpack = re.compile(r'(?<!\|)\|(?!\|)') +
    +   + + regex_python_keywords = re.compile(r'^(and|del|from|not|while|... +
    +   + + drivers = ['SQLite3', 'pymysql', 'PostgreSQL'] +
    +   + + is_jdbc = True +
    +   + + INGRES_SEQNAME = 'ii***lineitemsequence' +
    +   + + ADAPTERS = {'couchdb': <class 'web2py.gluon.dal.CouchDBAdapter... +
    +   + + regex_quotes = re.compile(r'\'[^\']*\'') +
    + + + + + + +
    + + + + + +
    Function Details[hide private]
    +
    + +
    + +
    + + +
    +

    sqlhtml_validators(field) +

    +
    source code  +
    + +

    Field type validation, using web2py's validators mechanism.

    + makes sure the content of a field is in line with the declared + fieldtype +
    +
    +
    +
    + +
    + +
    + + +
    +

    test_all() +

    +
    source code  +
    + +
    +
    +
    + >>> if len(sys.argv)<2: db = DAL("sqlite://test.db")
    + >>> if len(sys.argv)>1: db = DAL(sys.argv[1])
    + >>> tmp = db.define_table('users',              Field('stringf', 'string', length=32, required=True),              Field('booleanf', 'boolean', default=False),              Field('passwordf', 'password', notnull=True),              Field('uploadf', 'upload'),              Field('blobf', 'blob'),              Field('integerf', 'integer', unique=True),              Field('doublef', 'double', unique=True,notnull=True),              Field('datef', 'date', default=datetime.date.today()),              Field('timef', 'time'),              Field('datetimef', 'datetime'),              migrate='test_user.table')
    +
    +Insert a field
    +
    + >>> db.users.insert(stringf='a', booleanf=True, passwordf='p', blobf='0A',                       uploadf=None, integerf=5, doublef=3.14,                       datef=datetime.date(2001, 1, 1),                       timef=datetime.time(12, 30, 15),                       datetimef=datetime.datetime(2002, 2, 2, 12, 30, 15))
    + 1
    +
    + Drop the table
    +
    + >>> db.users.drop()
    +
    + Examples of insert, select, update, delete
    +
    + >>> tmp = db.define_table('person',              Field('name'),              Field('birth','date'),              migrate='test_person.table')
    + >>> person_id = db.person.insert(name="Marco",birth='2005-06-22')
    + >>> person_id = db.person.insert(name="Massimo",birth='1971-12-21')
    +
    + commented len(db().select(db.person.ALL))
    + commented 2
    +
    + >>> me = db(db.person.id==person_id).select()[0] # test select
    + >>> me.name
    + 'Massimo'
    + >>> db(db.person.name=='Massimo').update(name='massimo') # test update
    + 1
    + >>> db(db.person.name=='Marco').select().first().delete_record() # test delete
    + 1
    +
    + Update a single record
    +
    + >>> me.update_record(name="Max")
    + >>> me.name
    + 'Max'
    +
    + Examples of complex search conditions
    +
    + >>> len(db((db.person.name=='Max')&(db.person.birth<'2003-01-01')).select())
    + 1
    + >>> len(db((db.person.name=='Max')&(db.person.birth<datetime.date(2003,01,01))).select())
    + 1
    + >>> len(db((db.person.name=='Max')|(db.person.birth<'2003-01-01')).select())
    + 1
    + >>> me = db(db.person.id==person_id).select(db.person.name)[0]
    + >>> me.name
    + 'Max'
    +
    + Examples of search conditions using extract from date/datetime/time
    +
    + >>> len(db(db.person.birth.month()==12).select())
    + 1
    + >>> len(db(db.person.birth.year()>1900).select())
    + 1
    +
    + Example of usage of NULL
    +
    + >>> len(db(db.person.birth==None).select()) ### test NULL
    + 0
    + >>> len(db(db.person.birth!=None).select()) ### test NULL
    + 1
    +
    + Examples of search conditions using lower, upper, and like
    +
    + >>> len(db(db.person.name.upper()=='MAX').select())
    + 1
    + >>> len(db(db.person.name.like('%ax')).select())
    + 1
    + >>> len(db(db.person.name.upper().like('%AX')).select())
    + 1
    + >>> len(db(~db.person.name.upper().like('%AX')).select())
    + 0
    +
    + orderby, groupby and limitby
    +
    + >>> people = db().select(db.person.name, orderby=db.person.name)
    + >>> order = db.person.name|~db.person.birth
    + >>> people = db().select(db.person.name, orderby=order)
    +
    + >>> people = db().select(db.person.name, orderby=db.person.name, groupby=db.person.name)
    +
    + >>> people = db().select(db.person.name, orderby=order, limitby=(0,100))
    +
    + Example of one 2 many relation
    +
    + >>> tmp = db.define_table('dog',               Field('name'),               Field('birth','date'),               Field('owner',db.person),               migrate='test_dog.table')
    + >>> db.dog.insert(name='Snoopy', birth=None, owner=person_id)
    + 1
    +
    + A simple JOIN
    +
    + >>> len(db(db.dog.owner==db.person.id).select())
    + 1
    +
    + >>> len(db().select(db.person.ALL, db.dog.name,left=db.dog.on(db.dog.owner==db.person.id)))
    + 1
    +
    + Drop tables
    +
    + >>> db.dog.drop()
    + >>> db.person.drop()
    +
    + Example of many 2 many relation and Set
    +
    + >>> tmp = db.define_table('author', Field('name'),                            migrate='test_author.table')
    + >>> tmp = db.define_table('paper', Field('title'),                            migrate='test_paper.table')
    + >>> tmp = db.define_table('authorship',            Field('author_id', db.author),            Field('paper_id', db.paper),            migrate='test_authorship.table')
    + >>> aid = db.author.insert(name='Massimo')
    + >>> pid = db.paper.insert(title='QCD')
    + >>> tmp = db.authorship.insert(author_id=aid, paper_id=pid)
    +
    + Define a Set
    +
    + >>> authored_papers = db((db.author.id==db.authorship.author_id)&(db.paper.id==db.authorship.paper_id))
    + >>> rows = authored_papers.select(db.author.name, db.paper.title)
    + >>> for row in rows: print row.author.name, row.paper.title
    + Massimo QCD
    +
    + Example of search condition using  belongs
    +
    + >>> set = (1, 2, 3)
    + >>> rows = db(db.paper.id.belongs(set)).select(db.paper.ALL)
    + >>> print rows[0].title
    + QCD
    +
    + Example of search condition using nested select
    +
    + >>> nested_select = db()._select(db.authorship.paper_id)
    + >>> rows = db(db.paper.id.belongs(nested_select)).select(db.paper.ALL)
    + >>> print rows[0].title
    + QCD
    +
    + Example of expressions
    +
    + >>> mynumber = db.define_table('mynumber', Field('x', 'integer'))
    + >>> db(mynumber.id>0).delete()
    + 0
    + >>> for i in range(10): tmp = mynumber.insert(x=i)
    + >>> db(mynumber.id>0).select(mynumber.x.sum())[0](mynumber.x.sum())
    + 45
    +
    + >>> db(mynumber.x+2==5).select(mynumber.x + 2)[0](mynumber.x + 2)
    + 5
    +
    + Output in csv
    +
    + >>> print str(authored_papers.select(db.author.name, db.paper.title)).strip()
    + author.name,paper.title
    + Massimo,QCD
    +
    + Delete all leftover tables
    +
    + >>> DAL.distributed_transaction_commit(db)
    +
    + >>> db.mynumber.drop()
    + >>> db.authorship.drop()
    + >>> db.author.drop()
    + >>> db.paper.drop()
    + 
    +
    +
    +
    +
    +
    +
    +
    + + + + + + +
    + + + + + +
    Variables Details[hide private]
    +
    + +
    + +
    +

    CALLABLETYPES

    + +
    +
    +
    +
    Value:
    +
    +(<type 'function'>,
    + <type 'function'>,
    + <type 'builtin_function_or_method'>,
    + <type 'instancemethod'>,
    + <type 'builtin_function_or_method'>)
    +
    +
    +
    +
    +
    + +
    + +
    +

    regex_content

    + +
    +
    +
    +
    Value:
    +
    +re.compile(r'(?P<table>[\w-]+)\.(?P<field>[\w-]+)\.(?P<uuidkey>[\w-]+)\
    +\.(?P<name>\w+)\.\w+$')
    +
    +
    +
    +
    +
    + +
    + +
    +

    regex_python_keywords

    + +
    +
    +
    +
    Value:
    +
    +re.compile(r'^(and|del|from|not|while|as|elif|global|or|with|assert|el\
    +se|if|pass|yield|break|except|import|print|class|exec|in|raise|continu\
    +e|finally|is|return|def|for|lambda|try)$')
    +
    +
    +
    +
    +
    + +
    + +
    +

    ADAPTERS

    + +
    +
    +
    +
    Value:
    +
    +{'couchdb': <class 'web2py.gluon.dal.CouchDBAdapter'>,
    + 'cubrid': <class 'web2py.gluon.dal.CubridAdapter'>,
    + 'db2': <class 'web2py.gluon.dal.DB2Adapter'>,
    + 'firebird': <class 'web2py.gluon.dal.FireBirdAdapter'>,
    + 'firebird_embedded': <class 'web2py.gluon.dal.FireBirdAdapter'>,
    + 'gae': <class 'web2py.gluon.dal.GoogleDatastoreAdapter'>,
    + 'google:datastore': <class 'web2py.gluon.dal.GoogleDatastoreAdapter'>\
    +,
    +...
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.dal-pysrc.html Index: applications/examples/static/epydoc/web2py.gluon.dal-pysrc.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.dal-pysrc.html +++ applications/examples/static/epydoc/web2py.gluon.dal-pysrc.html @@ -0,0 +1,12344 @@ + + + + + web2py.gluon.dal + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module dal + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Module web2py.gluon.dal

    +
    +   1  #!/bin/env python 
    +   2  # -*- coding: utf-8 -*- 
    +   3   
    +   4  """ 
    +   5  This file is part of the web2py Web Framework 
    +   6  Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> 
    +   7  License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) 
    +   8   
    +   9  Thanks to 
    +  10      * Niall Sweeny <niall.sweeny@fonjax.com> for MS SQL support 
    +  11      * Marcel Leuthi <mluethi@mlsystems.ch> for Oracle support 
    +  12      * Denes 
    +  13      * Chris Clark 
    +  14      * clach05 
    +  15      * Denes Lengyel 
    +  16      * and many others who have contributed to current and previous versions 
    +  17   
    +  18  This file contains the DAL support for many relational databases, 
    +  19  including: 
    +  20  - SQLite 
    +  21  - MySQL 
    +  22  - Postgres 
    +  23  - Oracle 
    +  24  - MS SQL 
    +  25  - DB2 
    +  26  - Interbase 
    +  27  - Ingres 
    +  28  - SapDB (experimental) 
    +  29  - Cubrid (experimental) 
    +  30  - CouchDB (experimental) 
    +  31  - MongoDB (in progress) 
    +  32  - Google:nosql 
    +  33  - Google:sql 
    +  34   
    +  35  Example of usage: 
    +  36   
    +  37  >>> # from dal import DAL, Field 
    +  38   
    +  39  ### create DAL connection (and create DB if not exists) 
    +  40  >>> db=DAL(('mysql://a:b@locahost/x','sqlite://storage.sqlite'),folder=None) 
    +  41   
    +  42  ### define a table 'person' (create/aster as necessary) 
    +  43  >>> person = db.define_table('person',Field('name','string')) 
    +  44   
    +  45  ### insert a record 
    +  46  >>> id = person.insert(name='James') 
    +  47   
    +  48  ### retrieve it by id 
    +  49  >>> james = person(id) 
    +  50   
    +  51  ### retrieve it by name 
    +  52  >>> james = person(name='James') 
    +  53   
    +  54  ### retrieve it by arbitrary query 
    +  55  >>> query = (person.name=='James')&(person.name.startswith('J')) 
    +  56  >>> james = db(query).select(person.ALL)[0] 
    +  57   
    +  58  ### update one record 
    +  59  >>> james.update_record(name='Jim') 
    +  60   
    +  61  ### update multiple records by query 
    +  62  >>> db(person.name.like('J%')).update(name='James') 
    +  63  1 
    +  64   
    +  65  ### delete records by query 
    +  66  >>> db(person.name.lower()=='jim').delete() 
    +  67  0 
    +  68   
    +  69  ### retrieve multiple records (rows) 
    +  70  >>> people = db(person).select(orderby=person.name,groupby=person.name,limitby=(0,100)) 
    +  71   
    +  72  ### further filter them 
    +  73  >>> james = people.find(lambda row: row.name=='James').first() 
    +  74  >>> print james.id, james.name 
    +  75  1 James 
    +  76   
    +  77  ### check aggrgates 
    +  78  >>> counter = person.id.count() 
    +  79  >>> print db(person).select(counter).first()(counter) 
    +  80  1 
    +  81   
    +  82  ### delete one record 
    +  83  >>> james.delete_record() 
    +  84  1 
    +  85   
    +  86  ### delete (drop) entire database table 
    +  87  >>> person.drop() 
    +  88   
    +  89  Supported field types: 
    +  90  id string text boolean integer double decimal password upload blob time date datetime, 
    +  91   
    +  92  Supported DAL URI strings: 
    +  93  'sqlite://test.db' 
    +  94  'sqlite:memory' 
    +  95  'jdbc:sqlite://test.db' 
    +  96  'mysql://root:none@localhost/test' 
    +  97  'postgres://mdipierro:none@localhost/test' 
    +  98  'jdbc:postgres://mdipierro:none@localhost/test' 
    +  99  'mssql://web2py:none@A64X2/web2py_test' 
    + 100  'mssql2://web2py:none@A64X2/web2py_test' # alternate mappings 
    + 101  'oracle://username:password@database' 
    + 102  'firebird://user:password@server:3050/database' 
    + 103  'db2://DSN=dsn;UID=user;PWD=pass' 
    + 104  'firebird://username:password@hostname/database' 
    + 105  'firebird_embedded://username:password@c://path' 
    + 106  'informix://user:password@server:3050/database' 
    + 107  'informixu://user:password@server:3050/database' # unicode informix 
    + 108  'google:datastore' # for google app engine datastore 
    + 109  'google:sql' # for google app engine with sql (mysql compatible) 
    + 110  'teradata://DSN=dsn;UID=user;PWD=pass' # experimental  
    + 111   
    + 112  For more info: 
    + 113  help(DAL) 
    + 114  help(Field) 
    + 115  """ 
    + 116   
    + 117  ################################################################################### 
    + 118  # this file orly exposes DAL and Field 
    + 119  ################################################################################### 
    + 120   
    + 121  __all__ = ['DAL', 'Field'] 
    + 122  MAXCHARLENGTH = 512 
    + 123  INFINITY = 2**15 # not quite but reasonable default max char length 
    + 124   
    + 125  import re 
    + 126  import sys 
    + 127  import locale 
    + 128  import os 
    + 129  import types 
    + 130  import cPickle 
    + 131  import datetime 
    + 132  import threading 
    + 133  import time 
    + 134  import cStringIO 
    + 135  import csv 
    + 136  import copy 
    + 137  import socket 
    + 138  import logging 
    + 139  import copy_reg 
    + 140  import base64 
    + 141  import shutil 
    + 142  import marshal 
    + 143  import decimal 
    + 144  import struct 
    + 145  import urllib 
    + 146  import hashlib 
    + 147  import uuid 
    + 148  import glob 
    + 149   
    + 150  CALLABLETYPES = (types.LambdaType, types.FunctionType, types.BuiltinFunctionType, 
    + 151                   types.MethodType, types.BuiltinMethodType) 
    + 152   
    + 153   
    + 154  ################################################################################### 
    + 155  # following checks allows running of dal without web2py as a standalone module 
    + 156  ################################################################################### 
    + 157  try: 
    + 158      from utils import web2py_uuid 
    + 159  except ImportError: 
    + 160      import uuid 
    +
    161 - def web2py_uuid(): return str(uuid.uuid4()) +
    162 + 163 try: + 164 import portalocker + 165 have_portalocker = True + 166 except ImportError: + 167 have_portalocker = False + 168 + 169 try: + 170 import serializers + 171 have_serializers = True + 172 except ImportError: + 173 have_serializers = False + 174 + 175 try: + 176 import validators + 177 have_validators = True + 178 except ImportError: + 179 have_validators = False + 180 + 181 logger = logging.getLogger("web2py.dal") + 182 DEFAULT = lambda:0 + 183 + 184 sql_locker = threading.RLock() + 185 thread = threading.local() + 186 + 187 # internal representation of tables with field + 188 # <table>.<field>, tables and fields may only be [a-zA-Z0-0_] + 189 + 190 regex_dbname = re.compile('^(\w+)(\:\w+)*') + 191 table_field = re.compile('^[\w_]+\.[\w_]+$') + 192 regex_content = re.compile('(?P<table>[\w\-]+)\.(?P<field>[\w\-]+)\.(?P<uuidkey>[\w\-]+)\.(?P<name>\w+)\.\w+$') + 193 regex_cleanup_fn = re.compile('[\'"\s;]+') + 194 string_unpack=re.compile('(?<!\|)\|(?!\|)') + 195 regex_python_keywords = re.compile('^(and|del|from|not|while|as|elif|global|or|with|assert|else|if|pass|yield|break|except|import|print|class|exec|in|raise|continue|finally|is|return|def|for|lambda|try)$') + 196 + 197 + 198 + 199 # list of drivers will be built on the fly + 200 # and lists only what is available + 201 drivers = [] + 202 + 203 try: + 204 from new import classobj + 205 from google.appengine.ext import db as gae + 206 from google.appengine.api import namespace_manager, rdbms + 207 from google.appengine.api.datastore_types import Key ### needed for belongs on ID + 208 from google.appengine.ext.db.polymodel import PolyModel + 209 drivers.append('google') + 210 except ImportError: + 211 pass + 212 + 213 if not 'google' in drivers: + 214 + 215 try: + 216 from pysqlite2 import dbapi2 as sqlite3 + 217 drivers.append('pysqlite2') + 218 except ImportError: + 219 try: + 220 from sqlite3 import dbapi2 as sqlite3 + 221 drivers.append('SQLite3') + 222 except ImportError: + 223 logger.debug('no sqlite3 or pysqlite2.dbapi2 driver') + 224 + 225 try: + 226 import contrib.pymysql as pymysql + 227 drivers.append('pymysql') + 228 except ImportError: + 229 logger.debug('no pymysql driver') + 230 + 231 try: + 232 import psycopg2 + 233 drivers.append('PostgreSQL') + 234 except ImportError: + 235 logger.debug('no psycopg2 driver') + 236 + 237 try: + 238 import cx_Oracle + 239 drivers.append('Oracle') + 240 except ImportError: + 241 logger.debug('no cx_Oracle driver') + 242 + 243 try: + 244 import pyodbc + 245 drivers.append('MSSQL/DB2') + 246 except ImportError: + 247 logger.debug('no MSSQL/DB2 driver') + 248 + 249 try: + 250 import kinterbasdb + 251 drivers.append('Interbase') + 252 except ImportError: + 253 logger.debug('no kinterbasdb driver') + 254 + 255 try: + 256 import firebirdsql + 257 drivers.append('Firebird') + 258 except ImportError: + 259 logger.debug('no Firebird driver') + 260 + 261 try: + 262 import informixdb + 263 drivers.append('Informix') + 264 logger.warning('Informix support is experimental') + 265 except ImportError: + 266 logger.debug('no informixdb driver') + 267 + 268 try: + 269 import sapdb + 270 drivers.append('SAPDB') + 271 logger.warning('SAPDB support is experimental') + 272 except ImportError: + 273 logger.debug('no sapdb driver') + 274 + 275 try: + 276 import cubriddb + 277 drivers.append('Cubrid') + 278 logger.warning('Cubrid support is experimental') + 279 except ImportError: + 280 logger.debug('no cubriddb driver') + 281 + 282 try: + 283 from com.ziclix.python.sql import zxJDBC + 284 import java.sql + 285 # Try sqlite jdbc driver from http://www.zentus.com/sqlitejdbc/ + 286 from org.sqlite import JDBC # required by java.sql; ensure we have it + 287 drivers.append('zxJDBC') + 288 logger.warning('zxJDBC support is experimental') + 289 is_jdbc = True + 290 except ImportError: + 291 logger.debug('no zxJDBC driver') + 292 is_jdbc = False + 293 + 294 try: + 295 import ingresdbi + 296 drivers.append('Ingres') + 297 except ImportError: + 298 logger.debug('no Ingres driver') + 299 # NOTE could try JDBC....... + 300 + 301 try: + 302 import couchdb + 303 drivers.append('CouchDB') + 304 except ImportError: + 305 logger.debug('no couchdb driver') + 306 + 307 try: + 308 import pymongo + 309 drivers.append('mongoDB') + 310 except: + 311 logger.debug('no mongoDB driver') + 312 + 313 + 314 if 'google' in drivers: + 315 + 316 is_jdbc = False + 317 +
    318 - class GAEDecimalProperty(gae.Property): +
    319 """ + 320 GAE decimal implementation + 321 """ + 322 data_type = decimal.Decimal + 323 +
    324 - def __init__(self, precision, scale, **kwargs): +
    325 super(GAEDecimalProperty, self).__init__(self, **kwargs) + 326 d = '1.' + 327 for x in range(scale): + 328 d += '0' + 329 self.round = decimal.Decimal(d) +
    330 +
    331 - def get_value_for_datastore(self, model_instance): +
    332 value = super(GAEDecimalProperty, self).get_value_for_datastore(model_instance) + 333 if value: + 334 return str(value) + 335 else: + 336 return None +
    337 +
    338 - def make_value_from_datastore(self, value): +
    339 if value: + 340 return decimal.Decimal(value).quantize(self.round) + 341 else: + 342 return None +
    343 +
    344 - def validate(self, value): +
    345 value = super(GAEDecimalProperty, self).validate(value) + 346 if value is None or isinstance(value, decimal.Decimal): + 347 return value + 348 elif isinstance(value, basestring): + 349 return decimal.Decimal(value) + 350 raise gae.BadValueError("Property %s must be a Decimal or string." % self.name) +
    351 + 352 ################################################################################### + 353 # class that handles connection pooling (all adapters derived form this one) + 354 ################################################################################### + 355 +
    356 -class ConnectionPool(object): +
    357 + 358 pools = {} + 359 + 360 @staticmethod +
    361 - def set_folder(folder): +
    362 thread.folder = folder +
    363 + 364 # ## this allows gluon to commit/rollback all dbs in this thread + 365 + 366 @staticmethod +
    367 - def close_all_instances(action): +
    368 """ to close cleanly databases in a multithreaded environment """ + 369 if not hasattr(thread,'instances'): + 370 return + 371 while thread.instances: + 372 instance = thread.instances.pop() + 373 getattr(instance,action)() + 374 # ## if you want pools, recycle this connection + 375 really = True + 376 if instance.pool_size: + 377 sql_locker.acquire() + 378 pool = ConnectionPool.pools[instance.uri] + 379 if len(pool) < instance.pool_size: + 380 pool.append(instance.connection) + 381 really = False + 382 sql_locker.release() + 383 if really: + 384 getattr(instance,'close')() + 385 return +
    386 +
    387 - def find_or_make_work_folder(self): +
    388 """ this actually does not make the folder. it has to be there """ + 389 if hasattr(thread,'folder'): + 390 self.folder = thread.folder + 391 else: + 392 self.folder = thread.folder = '' + 393 + 394 # Creating the folder if it does not exist + 395 if False and self.folder and not os.path.exists(self.folder): + 396 os.mkdir(self.folder) +
    397 +
    398 - def pool_connection(self, f): +
    399 if not self.pool_size: + 400 self.connection = f() + 401 else: + 402 uri = self.uri + 403 sql_locker.acquire() + 404 if not uri in ConnectionPool.pools: + 405 ConnectionPool.pools[uri] = [] + 406 if ConnectionPool.pools[uri]: + 407 self.connection = ConnectionPool.pools[uri].pop() + 408 sql_locker.release() + 409 else: + 410 sql_locker.release() + 411 self.connection = f() + 412 if not hasattr(thread,'instances'): + 413 thread.instances = [] + 414 thread.instances.append(self) +
    415 + 416 + 417 ################################################################################### + 418 # this is a generic adapter that does nothing; all others are derived form this one + 419 ################################################################################### + 420 +
    421 -class BaseAdapter(ConnectionPool): +
    422 + 423 driver = None + 424 maxcharlength = INFINITY + 425 commit_on_alter_table = False + 426 support_distributed_transaction = False + 427 uploads_in_blob = False + 428 types = { + 429 'boolean': 'CHAR(1)', + 430 'string': 'CHAR(%(length)s)', + 431 'text': 'TEXT', + 432 'password': 'CHAR(%(length)s)', + 433 'blob': 'BLOB', + 434 'upload': 'CHAR(%(length)s)', + 435 'integer': 'INTEGER', + 436 'double': 'DOUBLE', + 437 'decimal': 'DOUBLE', + 438 'date': 'DATE', + 439 'time': 'TIME', + 440 'datetime': 'TIMESTAMP', + 441 'id': 'INTEGER PRIMARY KEY AUTOINCREMENT', + 442 'reference': 'INTEGER REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s', + 443 'list:integer': 'TEXT', + 444 'list:string': 'TEXT', + 445 'list:reference': 'TEXT', + 446 } + 447 +
    448 - def integrity_error(self): +
    449 return self.driver.IntegrityError +
    450 +
    451 - def file_exists(self, filename): +
    452 """ + 453 to be used ONLY for files that on GAE may not be on filesystem + 454 """ + 455 return os.path.exists(filename) +
    456 +
    457 - def file_open(self, filename, mode='rb', lock=True): +
    458 """ + 459 to be used ONLY for files that on GAE may not be on filesystem + 460 """ + 461 fileobj = open(filename,mode) + 462 if have_portalocker and lock: + 463 if mode in ('r','rb'): + 464 portalocker.lock(fileobj,portalocker.LOCK_SH) + 465 elif mode in ('w','wb','a'): + 466 portalocker.lock(fileobj,portalocker.LOCK_EX) + 467 else: + 468 fileobj.close() + 469 raise RuntimeError, "Unsupported file_open mode" + 470 return fileobj +
    471 +
    472 - def file_close(self, fileobj, unlock=True): +
    473 """ + 474 to be used ONLY for files that on GAE may not be on filesystem + 475 """ + 476 if fileobj: + 477 if have_portalocker and unlock: + 478 portalocker.unlock(fileobj) + 479 fileobj.close() +
    480 +
    481 - def file_delete(self, filename): +
    482 os.unlink(filename) +
    483 +
    484 - def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8', + 485 credential_decoder=lambda x:x, driver_args={}, + 486 adapter_args={}): +
    487 self.db = db + 488 self.dbengine = "None" + 489 self.uri = uri + 490 self.pool_size = pool_size + 491 self.folder = folder + 492 self.db_codec = db_codec + 493 class Dummy(object): + 494 lastrowid = 1 + 495 def __getattr__(self, value): + 496 return lambda *a, **b: [] +
    497 self.connection = Dummy() + 498 self.cursor = Dummy() + 499 +
    500 - def sequence_name(self,tablename): +
    501 return '%s_sequence' % tablename +
    502 +
    503 - def trigger_name(self,tablename): +
    504 return '%s_sequence' % tablename +
    505 + 506 +
    507 - def create_table(self, table, migrate=True, fake_migrate=False, polymodel=None): +
    508 fields = [] + 509 sql_fields = {} + 510 sql_fields_aux = {} + 511 TFK = {} + 512 tablename = table._tablename + 513 sortable = 0 + 514 for field in table: + 515 sortable += 1 + 516 k = field.name + 517 if isinstance(field.type,SQLCustomType): + 518 ftype = field.type.native or field.type.type + 519 elif field.type.startswith('reference'): + 520 referenced = field.type[10:].strip() + 521 constraint_name = self.constraint_name(tablename, field.name) + 522 if hasattr(table,'_primarykey'): + 523 rtablename,rfieldname = referenced.split('.') + 524 rtable = table._db[rtablename] + 525 rfield = rtable[rfieldname] + 526 # must be PK reference or unique + 527 if rfieldname in rtable._primarykey or rfield.unique: + 528 ftype = self.types[rfield.type[:9]] % dict(length=rfield.length) + 529 # multicolumn primary key reference? + 530 if not rfield.unique and len(rtable._primarykey)>1 : + 531 # then it has to be a table level FK + 532 if rtablename not in TFK: + 533 TFK[rtablename] = {} + 534 TFK[rtablename][rfieldname] = field.name + 535 else: + 536 ftype = ftype + \ + 537 self.types['reference FK'] %dict(\ + 538 constraint_name=constraint_name, + 539 table_name=tablename, + 540 field_name=field.name, + 541 foreign_key='%s (%s)'%(rtablename, rfieldname), + 542 on_delete_action=field.ondelete) + 543 else: + 544 # make a guess here for circular references + 545 id_fieldname = referenced in table._db and table._db[referenced]._id.name or 'id' + 546 ftype = self.types[field.type[:9]]\ + 547 % dict(table_name=tablename, + 548 field_name=field.name, + 549 constraint_name=constraint_name, + 550 foreign_key=referenced + ('(%s)' % id_fieldname), + 551 on_delete_action=field.ondelete) + 552 elif field.type.startswith('list:reference'): + 553 ftype = self.types[field.type[:14]] + 554 elif field.type.startswith('decimal'): + 555 precision, scale = map(int,field.type[8:-1].split(',')) + 556 ftype = self.types[field.type[:7]] % \ + 557 dict(precision=precision,scale=scale) + 558 elif not field.type in self.types: + 559 raise SyntaxError, 'Field: unknown field type: %s for %s' % \ + 560 (field.type, field.name) + 561 else: + 562 ftype = self.types[field.type]\ + 563 % dict(length=field.length) + 564 if not field.type.startswith('id') and not field.type.startswith('reference'): + 565 if field.notnull: + 566 ftype += ' NOT NULL' + 567 else: + 568 ftype += self.ALLOW_NULL() + 569 if field.unique: + 570 ftype += ' UNIQUE' + 571 + 572 # add to list of fields + 573 sql_fields[field.name] = dict(sortable=sortable, + 574 type=str(field.type), + 575 sql=ftype) + 576 + 577 if isinstance(field.default,(str,int,float)): + 578 # caveat: sql_fields and sql_fields_aux differ for default values + 579 # sql_fields is used to trigger migrations and sql_fields_aux + 580 # are used for create table + 581 # the reason is that we do not want to trigger a migration simply + 582 # because a default value changes + 583 not_null = self.NOT_NULL(field.default,field.type) + 584 ftype = ftype.replace('NOT NULL',not_null) + 585 sql_fields_aux[field.name] = dict(sql=ftype) + 586 + 587 fields.append('%s %s' % (field.name, ftype)) + 588 other = ';' + 589 + 590 # backend-specific extensions to fields + 591 if self.dbengine == 'mysql': + 592 if not hasattr(table, "_primarykey"): + 593 fields.append('PRIMARY KEY(%s)' % table._id.name) + 594 other = ' ENGINE=InnoDB CHARACTER SET utf8;' + 595 + 596 fields = ',\n '.join(fields) + 597 for rtablename in TFK: + 598 rfields = TFK[rtablename] + 599 pkeys = table._db[rtablename]._primarykey + 600 fkeys = [ rfields[k] for k in pkeys ] + 601 fields = fields + ',\n ' + \ + 602 self.types['reference TFK'] %\ + 603 dict(table_name=tablename, + 604 field_name=', '.join(fkeys), + 605 foreign_table=rtablename, + 606 foreign_key=', '.join(pkeys), + 607 on_delete_action=field.ondelete) + 608 + 609 if hasattr(table,'_primarykey'): + 610 query = '''CREATE TABLE %s(\n %s,\n %s) %s''' % \ + 611 (tablename, fields, self.PRIMARY_KEY(', '.join(table._primarykey)),other) + 612 else: + 613 query = '''CREATE TABLE %s(\n %s\n)%s''' % \ + 614 (tablename, fields, other) + 615 + 616 if self.uri.startswith('sqlite:///'): + 617 path_encoding = sys.getfilesystemencoding() or locale.getdefaultlocale()[1] or 'utf8' + 618 dbpath = self.uri[9:self.uri.rfind('/')].decode('utf8').encode(path_encoding) + 619 else: + 620 dbpath = self.folder + 621 + 622 if not migrate: + 623 return query + 624 elif self.uri.startswith('sqlite:memory'): + 625 table._dbt = None + 626 elif isinstance(migrate, str): + 627 table._dbt = os.path.join(dbpath, migrate) + 628 else: + 629 table._dbt = os.path.join(dbpath, '%s_%s.table' \ + 630 % (table._db._uri_hash, tablename)) + 631 if table._dbt: + 632 table._loggername = os.path.join(dbpath, 'sql.log') + 633 logfile = self.file_open(table._loggername, 'a') + 634 else: + 635 logfile = None + 636 if not table._dbt or not self.file_exists(table._dbt): + 637 if table._dbt: + 638 logfile.write('timestamp: %s\n' + 639 % datetime.datetime.today().isoformat()) + 640 logfile.write(query + '\n') + 641 if not fake_migrate: + 642 self.create_sequence_and_triggers(query,table) + 643 table._db.commit() + 644 if table._dbt: + 645 tfile = self.file_open(table._dbt, 'w') + 646 cPickle.dump(sql_fields, tfile) + 647 self.file_close(tfile) + 648 if fake_migrate: + 649 logfile.write('faked!\n') + 650 else: + 651 logfile.write('success!\n') + 652 else: + 653 tfile = self.file_open(table._dbt, 'r') + 654 try: + 655 sql_fields_old = cPickle.load(tfile) + 656 except EOFError: + 657 self.file_close(tfile) + 658 self.file_close(logfile) + 659 raise RuntimeError, 'File %s appears corrupted' % table._dbt + 660 self.file_close(tfile) + 661 if sql_fields != sql_fields_old: + 662 self.migrate_table(table, + 663 sql_fields, sql_fields_old, + 664 sql_fields_aux, logfile, + 665 fake_migrate=fake_migrate) + 666 self.file_close(logfile) + 667 return query +
    668 +
    669 - def migrate_table( + 670 self, + 671 table, + 672 sql_fields, + 673 sql_fields_old, + 674 sql_fields_aux, + 675 logfile, + 676 fake_migrate=False, + 677 ): +
    678 tablename = table._tablename + 679 def fix(item): + 680 k,v=item + 681 if not isinstance(v,dict): + 682 v=dict(type='unkown',sql=v) + 683 return k.lower(),v +
    684 ### make sure all field names are lower case to avoid conflicts + 685 sql_fields = dict(map(fix,sql_fields.items())) + 686 sql_fields_old = dict(map(fix,sql_fields_old.items())) + 687 sql_fields_aux = dict(map(fix,sql_fields_aux.items())) + 688 + 689 keys = sql_fields.keys() + 690 for key in sql_fields_old: + 691 if not key in keys: + 692 keys.append(key) + 693 if self.dbengine == 'mssql': + 694 new_add = '; ALTER TABLE %s ADD ' % tablename + 695 else: + 696 new_add = ', ADD ' + 697 + 698 metadata_change = False + 699 sql_fields_current = copy.copy(sql_fields_old) + 700 for key in keys: + 701 query = None + 702 if not key in sql_fields_old: + 703 sql_fields_current[key] = sql_fields[key] + 704 query = ['ALTER TABLE %s ADD %s %s;' % \ + 705 (tablename, key, + 706 sql_fields_aux[key]['sql'].replace(', ', new_add))] + 707 metadata_change = True + 708 elif self.dbengine == 'sqlite': + 709 if key in sql_fields: + 710 sql_fields_current[key] = sql_fields[key] + 711 metadata_change = True + 712 elif not key in sql_fields: + 713 del sql_fields_current[key] + 714 if not self.dbengine in ('firebird',): + 715 query = ['ALTER TABLE %s DROP COLUMN %s;' % (tablename, key)] + 716 else: + 717 query = ['ALTER TABLE %s DROP %s;' % (tablename, key)] + 718 metadata_change = True + 719 elif sql_fields[key]['sql'] != sql_fields_old[key]['sql'] \ + 720 and not isinstance(table[key].type, SQLCustomType) \ + 721 and not (table[key].type.startswith('reference') and \ + 722 sql_fields[key]['sql'].startswith('INT,') and \ + 723 sql_fields_old[key]['sql'].startswith('INT NOT NULL,')): + 724 sql_fields_current[key] = sql_fields[key] + 725 t = tablename + 726 tt = sql_fields_aux[key]['sql'].replace(', ', new_add) + 727 if not self.dbengine in ('firebird',): + 728 query = ['ALTER TABLE %s ADD %s__tmp %s;' % (t, key, tt), + 729 'UPDATE %s SET %s__tmp=%s;' % (t, key, key), + 730 'ALTER TABLE %s DROP COLUMN %s;' % (t, key), + 731 'ALTER TABLE %s ADD %s %s;' % (t, key, tt), + 732 'UPDATE %s SET %s=%s__tmp;' % (t, key, key), + 733 'ALTER TABLE %s DROP COLUMN %s__tmp;' % (t, key)] + 734 else: + 735 query = ['ALTER TABLE %s ADD %s__tmp %s;' % (t, key, tt), + 736 'UPDATE %s SET %s__tmp=%s;' % (t, key, key), + 737 'ALTER TABLE %s DROP %s;' % (t, key), + 738 'ALTER TABLE %s ADD %s %s;' % (t, key, tt), + 739 'UPDATE %s SET %s=%s__tmp;' % (t, key, key), + 740 'ALTER TABLE %s DROP %s__tmp;' % (t, key)] + 741 metadata_change = True + 742 elif sql_fields[key]['type'] != sql_fields_old[key]['type']: + 743 sql_fields_current[key] = sql_fields[key] + 744 metadata_change = True + 745 + 746 if query: + 747 logfile.write('timestamp: %s\n' + 748 % datetime.datetime.today().isoformat()) + 749 table._db['_lastsql'] = '\n'.join(query) + 750 for sub_query in query: + 751 logfile.write(sub_query + '\n') + 752 if not fake_migrate: + 753 self.execute(sub_query) + 754 # caveat. mysql, oracle and firebird do not allow multiple alter table + 755 # in one transaction so we must commit partial transactions and + 756 # update table._dbt after alter table. + 757 if table._db._adapter.commit_on_alter_table: + 758 table._db.commit() + 759 tfile = self.file_open(table._dbt, 'w') + 760 cPickle.dump(sql_fields_current, tfile) + 761 self.file_close(tfile) + 762 logfile.write('success!\n') + 763 else: + 764 logfile.write('faked!\n') + 765 elif metadata_change: + 766 tfile = self.file_open(table._dbt, 'w') + 767 cPickle.dump(sql_fields_current, tfile) + 768 self.file_close(tfile) + 769 + 770 if metadata_change and \ + 771 not (query and self.dbengine in ('mysql','oracle','firebird')): + 772 table._db.commit() + 773 tfile = self.file_open(table._dbt, 'w') + 774 cPickle.dump(sql_fields_current, tfile) + 775 self.file_close(tfile) + 776 +
    777 - def LOWER(self,first): +
    778 return 'LOWER(%s)' % self.expand(first) +
    779 +
    780 - def UPPER(self,first): +
    781 return 'UPPER(%s)' % self.expand(first) +
    782 +
    783 - def EXTRACT(self,first,what): +
    784 return "EXTRACT(%s FROM %s)" % (what, self.expand(first)) +
    785 +
    786 - def AGGREGATE(self,first,what): +
    787 return "%s(%s)" % (what,self.expand(first)) +
    788 +
    789 - def JOIN(self): +
    790 return 'JOIN' +
    791 +
    792 - def LEFT_JOIN(self): +
    793 return 'LEFT JOIN' +
    794 +
    795 - def RANDOM(self): +
    796 return 'Random()' +
    797 +
    798 - def NOT_NULL(self,default,field_type): +
    799 return 'NOT NULL DEFAULT %s' % self.represent(default,field_type) +
    800 +
    801 - def COALESCE_ZERO(self,first): +
    802 return 'COALESCE(%s,0)' % self.expand(first) +
    803 +
    804 - def ALLOW_NULL(self): +
    805 return '' +
    806 +
    807 - def SUBSTRING(self,field,parameters): +
    808 return 'SUBSTR(%s,%s,%s)' % (self.expand(field), parameters[0], parameters[1]) +
    809 +
    810 - def PRIMARY_KEY(self,key): +
    811 return 'PRIMARY KEY(%s)' % key +
    812 +
    813 - def _drop(self,table,mode): +
    814 return ['DROP TABLE %s;' % table] +
    815 +
    816 - def drop(self, table, mode=''): +
    817 if table._dbt: + 818 logfile = self.file_open(table._loggername, 'a') + 819 queries = self._drop(table, mode) + 820 for query in queries: + 821 if table._dbt: + 822 logfile.write(query + '\n') + 823 self.execute(query) + 824 table._db.commit() + 825 del table._db[table._tablename] + 826 del table._db.tables[table._db.tables.index(table._tablename)] + 827 table._db._update_referenced_by(table._tablename) + 828 if table._dbt: + 829 self.file_delete(table._dbt) + 830 logfile.write('success!\n') +
    831 +
    832 - def _insert(self,table,fields): +
    833 keys = ','.join(f.name for f,v in fields) + 834 values = ','.join(self.expand(v,f.type) for f,v in fields) + 835 return 'INSERT INTO %s(%s) VALUES (%s);' % (table, keys, values) +
    836 +
    837 - def insert(self,table,fields): +
    838 query = self._insert(table,fields) + 839 try: + 840 self.execute(query) + 841 except Exception, e: + 842 if isinstance(e,self.integrity_error_class()): + 843 return None + 844 raise e + 845 if hasattr(table,'_primarykey'): + 846 return dict([(k[0].name, k[1]) for k in fields \ + 847 if k[0].name in table._primarykey]) + 848 id = self.lastrowid(table) + 849 if not isinstance(id,int): + 850 return id + 851 rid = Reference(id) + 852 (rid._table, rid._record) = (table, None) + 853 return rid +
    854 +
    855 - def bulk_insert(self,table,items): +
    856 return [self.insert(table,item) for item in items] +
    857 +
    858 - def NOT(self,first): +
    859 return '(NOT %s)' % self.expand(first) +
    860 +
    861 - def AND(self,first,second): +
    862 return '(%s AND %s)' % (self.expand(first),self.expand(second)) +
    863 +
    864 - def OR(self,first,second): +
    865 return '(%s OR %s)' % (self.expand(first),self.expand(second)) +
    866 +
    867 - def BELONGS(self,first,second): +
    868 if isinstance(second,str): + 869 return '(%s IN (%s))' % (self.expand(first),second[:-1]) + 870 elif second==[] or second==(): + 871 return '(0)' + 872 items =','.join(self.expand(item,first.type) for item in second) + 873 return '(%s IN (%s))' % (self.expand(first),items) +
    874 +
    875 - def LIKE(self,first,second): +
    876 return '(%s LIKE %s)' % (self.expand(first),self.expand(second,'string')) +
    877 +
    878 - def STARTSWITH(self,first,second): +
    879 return '(%s LIKE %s)' % (self.expand(first),self.expand(second+'%','string')) +
    880 +
    881 - def ENDSWITH(self,first,second): +
    882 return '(%s LIKE %s)' % (self.expand(first),self.expand('%'+second,'string')) +
    883 +
    884 - def CONTAINS(self,first,second): +
    885 if first.type in ('string','text'): + 886 key = '%'+str(second).replace('%','%%')+'%' + 887 elif first.type.startswith('list:'): + 888 key = '%|'+str(second).replace('|','||').replace('%','%%')+'|%' + 889 return '(%s LIKE %s)' % (self.expand(first),self.expand(key,'string')) +
    890 +
    891 - def EQ(self,first,second=None): +
    892 if second is None: + 893 return '(%s IS NULL)' % self.expand(first) + 894 return '(%s = %s)' % (self.expand(first),self.expand(second,first.type)) +
    895 +
    896 - def NE(self,first,second=None): +
    897 if second is None: + 898 return '(%s IS NOT NULL)' % self.expand(first) + 899 return '(%s <> %s)' % (self.expand(first),self.expand(second,first.type)) +
    900 +
    901 - def LT(self,first,second=None): +
    902 return '(%s < %s)' % (self.expand(first),self.expand(second,first.type)) +
    903 +
    904 - def LE(self,first,second=None): +
    905 return '(%s <= %s)' % (self.expand(first),self.expand(second,first.type)) +
    906 +
    907 - def GT(self,first,second=None): +
    908 return '(%s > %s)' % (self.expand(first),self.expand(second,first.type)) +
    909 +
    910 - def GE(self,first,second=None): +
    911 return '(%s >= %s)' % (self.expand(first),self.expand(second,first.type)) +
    912 +
    913 - def ADD(self,first,second): +
    914 return '(%s + %s)' % (self.expand(first),self.expand(second,first.type)) +
    915 +
    916 - def SUB(self,first,second): +
    917 return '(%s - %s)' % (self.expand(first),self.expand(second,first.type)) +
    918 +
    919 - def MUL(self,first,second): +
    920 return '(%s * %s)' % (self.expand(first),self.expand(second,first.type)) +
    921 +
    922 - def DIV(self,first,second): +
    923 return '(%s / %s)' % (self.expand(first),self.expand(second,first.type)) +
    924 +
    925 - def MOD(self,first,second): +
    926 return '(%s %% %s)' % (self.expand(first),self.expand(second,first.type)) +
    927 +
    928 - def AS(self,first,second): +
    929 return '%s AS %s' % (self.expand(first),second) +
    930 +
    931 - def ON(self,first,second): +
    932 return '%s ON %s' % (self.expand(first),self.expand(second)) +
    933 +
    934 - def INVERT(self,first): +
    935 return '%s DESC' % self.expand(first) +
    936 +
    937 - def COMMA(self,first,second): +
    938 return '%s, %s' % (self.expand(first),self.expand(second)) +
    939 +
    940 - def expand(self,expression,field_type=None): +
    941 if isinstance(expression,Field): + 942 return str(expression) + 943 elif isinstance(expression, (Expression, Query)): + 944 if not expression.second is None: + 945 return expression.op(expression.first, expression.second) + 946 elif not expression.first is None: + 947 return expression.op(expression.first) + 948 else: + 949 return expression.op() + 950 elif field_type: + 951 return self.represent(expression,field_type) + 952 elif isinstance(expression,(list,tuple)): + 953 return ','.join([self.represent(item,field_type) for item in expression]) + 954 else: + 955 return str(expression) +
    956 +
    957 - def alias(self,table,alias): +
    958 """ + 959 given a table object, makes a new table object + 960 with alias name. + 961 """ + 962 other = copy.copy(table) + 963 other['_ot'] = other._tablename + 964 other['ALL'] = SQLALL(other) + 965 other['_tablename'] = alias + 966 for fieldname in other.fields: + 967 other[fieldname] = copy.copy(other[fieldname]) + 968 other[fieldname]._tablename = alias + 969 other[fieldname].tablename = alias + 970 other[fieldname].table = other + 971 table._db[alias] = other + 972 return other +
    973 +
    974 - def _truncate(self,table,mode = ''): +
    975 tablename = table._tablename + 976 return ['TRUNCATE TABLE %s %s;' % (tablename, mode or '')] +
    977 +
    978 - def truncate(self,table,mode= ' '): +
    979 # Prepare functions "write_to_logfile" and "close_logfile" + 980 if table._dbt: + 981 logfile = self.file_open(table._loggername, 'a') + 982 else: + 983 class Logfile(object): + 984 def write(self, value): + 985 pass +
    986 def close(self): + 987 pass + 988 logfile = Logfile() + 989 + 990 try: + 991 queries = table._db._adapter._truncate(table, mode) + 992 for query in queries: + 993 logfile.write(query + '\n') + 994 self.execute(query) + 995 table._db.commit() + 996 logfile.write('success!\n') + 997 finally: + 998 logfile.close() + 999 +
    1000 - def _update(self,tablename,query,fields): +
    1001 if query: +1002 sql_w = ' WHERE ' + self.expand(query) +1003 else: +1004 sql_w = '' +1005 sql_v = ','.join(['%s=%s' % (field.name, self.expand(value,field.type)) for (field,value) in fields]) +1006 return 'UPDATE %s SET %s%s;' % (tablename, sql_v, sql_w) +
    1007 +
    1008 - def update(self,tablename,query,fields): +
    1009 sql = self._update(tablename,query,fields) +1010 self.execute(sql) +1011 try: +1012 return self.cursor.rowcount +1013 except: +1014 return None +
    1015 +
    1016 - def _delete(self,tablename, query): +
    1017 if query: +1018 sql_w = ' WHERE ' + self.expand(query) +1019 else: +1020 sql_w = '' +1021 return 'DELETE FROM %s%s;' % (tablename, sql_w) +
    1022 +
    1023 - def delete(self,tablename,query): +
    1024 sql = self._delete(tablename,query) +1025 ### special code to handle CASCADE in SQLite +1026 db = self.db +1027 table = db[tablename] +1028 if self.dbengine=='sqlite' and table._referenced_by: +1029 deleted = [x[table._id.name] for x in db(query).select(table._id)] +1030 ### end special code to handle CASCADE in SQLite +1031 self.execute(sql) +1032 try: +1033 counter = self.cursor.rowcount +1034 except: +1035 counter = None +1036 ### special code to handle CASCADE in SQLite +1037 if self.dbengine=='sqlite' and counter: +1038 for tablename,fieldname in table._referenced_by: +1039 f = db[tablename][fieldname] +1040 if f.type=='reference '+table._tablename and f.ondelete=='CASCADE': +1041 db(db[tablename][fieldname].belongs(deleted)).delete() +1042 ### end special code to handle CASCADE in SQLite +1043 return counter +
    1044 +
    1045 - def get_table(self,query): +
    1046 tablenames = self.tables(query) +1047 if len(tablenames)==1: +1048 return tablenames[0] +1049 elif len(tablenames)<1: +1050 raise RuntimeError, "No table selected" +1051 else: +1052 raise RuntimeError, "Too many tables selected" +
    1053 +
    1054 - def _select(self, query, fields, attributes): +
    1055 for key in set(attributes.keys())-set(('orderby','groupby','limitby', +1056 'required','cache','left', +1057 'distinct','having', 'join')): +1058 raise SyntaxError, 'invalid select attribute: %s' % key +1059 # ## if not fields specified take them all from the requested tables +1060 new_fields = [] +1061 for item in fields: +1062 if isinstance(item,SQLALL): +1063 new_fields += item.table +1064 else: +1065 new_fields.append(item) +1066 fields = new_fields +1067 tablenames = self.tables(query) +1068 query = self.filter_tenant(query,tablenames) +1069 if not fields: +1070 for table in tablenames: +1071 for field in self.db[table]: +1072 fields.append(field) +1073 else: +1074 for field in fields: +1075 if isinstance(field,basestring) and table_field.match(field): +1076 tn,fn = field.split('.') +1077 field = self.db[tn][fn] +1078 for tablename in self.tables(field): +1079 if not tablename in tablenames: +1080 tablenames.append(tablename) +1081 if len(tablenames) < 1: +1082 raise SyntaxError, 'Set: no tables selected' +1083 sql_f = ', '.join(map(self.expand,fields)) +1084 self._colnames = [c.strip() for c in sql_f.split(', ')] +1085 if query: +1086 sql_w = ' WHERE ' + self.expand(query) +1087 else: +1088 sql_w = '' +1089 sql_o = '' +1090 sql_s = '' +1091 left = attributes.get('left', False) +1092 inner_join = attributes.get('join', False) +1093 distinct = attributes.get('distinct', False) +1094 groupby = attributes.get('groupby', False) +1095 orderby = attributes.get('orderby', False) +1096 having = attributes.get('having', False) +1097 limitby = attributes.get('limitby', False) +1098 if distinct is True: +1099 sql_s += 'DISTINCT' +1100 elif distinct: +1101 sql_s += 'DISTINCT ON (%s)' % distinct +1102 if inner_join: +1103 icommand = self.JOIN() +1104 if not isinstance(inner_join, (tuple, list)): +1105 inner_join = [inner_join] +1106 ijoint = [t._tablename for t in inner_join if not isinstance(t,Expression)] +1107 ijoinon = [t for t in inner_join if isinstance(t, Expression)] +1108 ijoinont = [t.first._tablename for t in ijoinon] +1109 iexcluded = [t for t in tablenames if not t in ijoint + ijoinont] +1110 if left: +1111 join = attributes['left'] +1112 command = self.LEFT_JOIN() +1113 if not isinstance(join, (tuple, list)): +1114 join = [join] +1115 joint = [t._tablename for t in join if not isinstance(t,Expression)] +1116 joinon = [t for t in join if isinstance(t, Expression)] +1117 #patch join+left patch (solves problem with ordering in left joins) +1118 tables_to_merge={} +1119 [tables_to_merge.update(dict.fromkeys(self.tables(t))) for t in joinon] +1120 joinont = [t.first._tablename for t in joinon] +1121 [tables_to_merge.pop(t) for t in joinont if t in tables_to_merge] +1122 important_tablenames = joint + joinont + tables_to_merge.keys() +1123 excluded = [t for t in tablenames if not t in important_tablenames ] +1124 def alias(t): +1125 return str(self.db[t]) +
    1126 if inner_join and not left: +1127 sql_t = ', '.join(alias(t) for t in iexcluded) +1128 for t in ijoinon: +1129 sql_t += ' %s %s' % (icommand, str(t)) +1130 elif not inner_join and left: +1131 sql_t = ', '.join([alias(t) for t in excluded + tables_to_merge.keys()]) +1132 if joint: +1133 sql_t += ' %s %s' % (command, ','.join([t for t in joint])) +1134 for t in joinon: +1135 sql_t += ' %s %s' % (command, str(t)) +1136 elif inner_join and left: +1137 sql_t = ','.join([alias(t) for t in excluded + \ +1138 tables_to_merge.keys() if t in iexcluded ]) +1139 for t in ijoinon: +1140 sql_t += ' %s %s' % (icommand, str(t)) +1141 if joint: +1142 sql_t += ' %s %s' % (command, ','.join([t for t in joint])) +1143 for t in joinon: +1144 sql_t += ' %s %s' % (command, str(t)) +1145 else: +1146 sql_t = ', '.join(alias(t) for t in tablenames) +1147 if groupby: +1148 if isinstance(groupby, (list, tuple)): +1149 groupby = xorify(groupby) +1150 sql_o += ' GROUP BY %s' % self.expand(groupby) +1151 if having: +1152 sql_o += ' HAVING %s' % attributes['having'] +1153 if orderby: +1154 if isinstance(orderby, (list, tuple)): +1155 orderby = xorify(orderby) +1156 if str(orderby) == '<random>': +1157 sql_o += ' ORDER BY %s' % self.RANDOM() +1158 else: +1159 sql_o += ' ORDER BY %s' % self.expand(orderby) +1160 if limitby: +1161 if not orderby and tablenames: +1162 sql_o += ' ORDER BY %s' % ', '.join(['%s.%s'%(t,x) for t in tablenames for x in ((hasattr(self.db[t],'_primarykey') and self.db[t]._primarykey) or [self.db[t]._id.name])]) +1163 # oracle does not support limitby +1164 return self.select_limitby(sql_s, sql_f, sql_t, sql_w, sql_o, limitby) +1165 +
    1166 - def select_limitby(self, sql_s, sql_f, sql_t, sql_w, sql_o, limitby): +
    1167 if limitby: +1168 (lmin, lmax) = limitby +1169 sql_o += ' LIMIT %i OFFSET %i' % (lmax - lmin, lmin) +1170 return 'SELECT %s %s FROM %s%s%s;' % (sql_s, sql_f, sql_t, sql_w, sql_o) +
    1171 +
    1172 - def select(self,query,fields,attributes): +
    1173 """ +1174 Always returns a Rows object, even if it may be empty +1175 """ +1176 def response(sql): +1177 self.execute(sql) +1178 return self.cursor.fetchall() +
    1179 sql = self._select(query,fields,attributes) +1180 if attributes.get('cache', None): +1181 (cache_model, time_expire) = attributes['cache'] +1182 del attributes['cache'] +1183 key = self.uri + '/' + sql +1184 key = (key<=200) and key or hashlib.md5(key).hexdigest() +1185 rows = cache_model(key, lambda: response(sql), time_expire) +1186 else: +1187 rows = response(sql) +1188 if isinstance(rows,tuple): +1189 rows = list(rows) +1190 limitby = attributes.get('limitby',None) or (0,) +1191 rows = self.rowslice(rows,limitby[0],None) +1192 return self.parse(rows,self._colnames) +1193 +
    1194 - def _count(self,query,distinct=None): +
    1195 tablenames = self.tables(query) +1196 if query: +1197 sql_w = ' WHERE ' + self.expand(query) +1198 else: +1199 sql_w = '' +1200 sql_t = ','.join(tablenames) +1201 if distinct: +1202 if isinstance(distinct,(list,tuple)): +1203 distinct = xorify(distinct) +1204 sql_d = self.expand(distinct) +1205 return 'SELECT count(DISTINCT %s) FROM %s%s' % (sql_d, sql_t, sql_w) +1206 return 'SELECT count(*) FROM %s%s' % (sql_t, sql_w) +
    1207 +
    1208 - def count(self,query,distinct=None): +
    1209 self.execute(self._count(query,distinct)) +1210 return self.cursor.fetchone()[0] +
    1211 +1212 +
    1213 - def tables(self,query): +
    1214 tables = set() +1215 if isinstance(query, Field): +1216 tables.add(query.tablename) +1217 elif isinstance(query, (Expression, Query)): +1218 if query.first!=None: +1219 tables = tables.union(self.tables(query.first)) +1220 if query.second!=None: +1221 tables = tables.union(self.tables(query.second)) +1222 return list(tables) +
    1223 +
    1224 - def commit(self): +
    1225 return self.connection.commit() +
    1226 +
    1227 - def rollback(self): +
    1228 return self.connection.rollback() +
    1229 +
    1230 - def close(self): +
    1231 return self.connection.close() +
    1232 +
    1233 - def distributed_transaction_begin(self,key): +
    1234 return +
    1235 +
    1236 - def prepare(self,key): +
    1237 self.connection.prepare() +
    1238 +
    1239 - def commit_prepared(self,key): +
    1240 self.connection.commit() +
    1241 +
    1242 - def rollback_prepared(self,key): +
    1243 self.connection.rollback() +
    1244 +
    1245 - def concat_add(self,table): +
    1246 return ', ADD ' +
    1247 +
    1248 - def constraint_name(self, table, fieldname): +
    1249 return '%s_%s__constraint' % (table,fieldname) +
    1250 +
    1251 - def create_sequence_and_triggers(self, query, table, **args): +
    1252 self.execute(query) +
    1253 +
    1254 - def log_execute(self,*a,**b): +
    1255 self.db._lastsql = a[0] +1256 t0 = time.time() +1257 ret = self.cursor.execute(*a,**b) +1258 self.db._timings.append((a[0],time.time()-t0)) +1259 return ret +
    1260 +
    1261 - def execute(self,*a,**b): +
    1262 return self.log_execute(*a, **b) +
    1263 +
    1264 - def represent(self, obj, fieldtype): +
    1265 if isinstance(obj,CALLABLETYPES): +1266 obj = obj() +1267 if isinstance(fieldtype, SQLCustomType): +1268 return fieldtype.encoder(obj) +1269 if isinstance(obj, (Expression, Field)): +1270 return str(obj) +1271 if fieldtype.startswith('list:'): +1272 if not obj: +1273 obj = [] +1274 if not isinstance(obj, (list, tuple)): +1275 obj = [obj] +1276 if isinstance(obj, (list, tuple)): +1277 obj = bar_encode(obj) +1278 if obj is None: +1279 return 'NULL' +1280 if obj == '' and not fieldtype[:2] in ['st', 'te', 'pa', 'up']: +1281 return 'NULL' +1282 r = self.represent_exceptions(obj,fieldtype) +1283 if r != None: +1284 return r +1285 if fieldtype == 'boolean': +1286 if obj and not str(obj)[:1].upper() in ['F', '0']: +1287 return "'T'" +1288 else: +1289 return "'F'" +1290 if fieldtype == 'id' or fieldtype == 'integer': +1291 return str(int(obj)) +1292 if fieldtype.startswith('decimal'): +1293 return str(obj) +1294 elif fieldtype.startswith('reference'): # reference +1295 if fieldtype.find('.')>0: +1296 return repr(obj) +1297 elif isinstance(obj, (Row, Reference)): +1298 return str(obj['id']) +1299 return str(int(obj)) +1300 elif fieldtype == 'double': +1301 return repr(float(obj)) +1302 if isinstance(obj, unicode): +1303 obj = obj.encode(self.db_codec) +1304 if fieldtype == 'blob': +1305 obj = base64.b64encode(str(obj)) +1306 elif fieldtype == 'date': +1307 if isinstance(obj, (datetime.date, datetime.datetime)): +1308 obj = obj.isoformat()[:10] +1309 else: +1310 obj = str(obj) +1311 elif fieldtype == 'datetime': +1312 if isinstance(obj, datetime.datetime): +1313 obj = obj.isoformat()[:19].replace('T',' ') +1314 elif isinstance(obj, datetime.date): +1315 obj = obj.isoformat()[:10]+' 00:00:00' +1316 else: +1317 obj = str(obj) +1318 elif fieldtype == 'time': +1319 if isinstance(obj, datetime.time): +1320 obj = obj.isoformat()[:10] +1321 else: +1322 obj = str(obj) +1323 if not isinstance(obj,str): +1324 obj = str(obj) +1325 try: +1326 obj.decode(self.db_codec) +1327 except: +1328 obj = obj.decode('latin1').encode(self.db_codec) +1329 return "'%s'" % obj.replace("'", "''") +
    1330 +
    1331 - def represent_exceptions(self, obj, fieldtype): +
    1332 return None +
    1333 +
    1334 - def lastrowid(self,table): +
    1335 return None +
    1336 +
    1337 - def integrity_error_class(self): +
    1338 return type(None) +
    1339 +
    1340 - def rowslice(self,rows,minimum=0,maximum=None): +
    1341 """ by default this function does nothing, overload when db does not do slicing """ +1342 return rows +
    1343 +
    1344 - def parse(self, rows, colnames, blob_decode=True): +
    1345 db = self.db +1346 virtualtables = [] +1347 new_rows = [] +1348 for (i,row) in enumerate(rows): +1349 new_row = Row() +1350 for j,colname in enumerate(colnames): +1351 value = row[j] +1352 if not table_field.match(colnames[j]): +1353 if not '_extra' in new_row: +1354 new_row['_extra'] = Row() +1355 new_row['_extra'][colnames[j]] = value +1356 select_as_parser = re.compile("\s+AS\s+(\S+)") +1357 new_column_name = select_as_parser.search(colnames[j]) +1358 if not new_column_name is None: +1359 column_name = new_column_name.groups(0) +1360 setattr(new_row,column_name[0],value) +1361 continue +1362 (tablename, fieldname) = colname.split('.') +1363 table = db[tablename] +1364 field = table[fieldname] +1365 field_type = field.type +1366 if field.type != 'blob' and isinstance(value, str): +1367 try: +1368 value = value.decode(db._db_codec) +1369 except Exception: +1370 pass +1371 if isinstance(value, unicode): +1372 value = value.encode('utf-8') +1373 if not tablename in new_row: +1374 colset = new_row[tablename] = Row() +1375 if tablename not in virtualtables: +1376 virtualtables.append(tablename) +1377 else: +1378 colset = new_row[tablename] +1379 +1380 if isinstance(field_type, SQLCustomType): +1381 colset[fieldname] = field_type.decoder(value) +1382 # field_type = field_type.type +1383 elif not isinstance(field_type, str) or value is None: +1384 colset[fieldname] = value +1385 elif isinstance(field_type, str) and \ +1386 field_type.startswith('reference'): +1387 referee = field_type[10:].strip() +1388 if not '.' in referee: +1389 colset[fieldname] = rid = Reference(value) +1390 (rid._table, rid._record) = (db[referee], None) +1391 else: ### reference not by id +1392 colset[fieldname] = value +1393 elif field_type == 'boolean': +1394 if value == True or str(value)[:1].lower() == 't': +1395 colset[fieldname] = True +1396 else: +1397 colset[fieldname] = False +1398 elif field_type == 'date' \ +1399 and (not isinstance(value, datetime.date)\ +1400 or isinstance(value, datetime.datetime)): +1401 (y, m, d) = map(int, str(value)[:10].strip().split('-')) +1402 colset[fieldname] = datetime.date(y, m, d) +1403 elif field_type == 'time' \ +1404 and not isinstance(value, datetime.time): +1405 time_items = map(int,str(value)[:8].strip().split(':')[:3]) +1406 if len(time_items) == 3: +1407 (h, mi, s) = time_items +1408 else: +1409 (h, mi, s) = time_items + [0] +1410 colset[fieldname] = datetime.time(h, mi, s) +1411 elif field_type == 'datetime'\ +1412 and not isinstance(value, datetime.datetime): +1413 (y, m, d) = map(int,str(value)[:10].strip().split('-')) +1414 time_items = map(int,str(value)[11:19].strip().split(':')[:3]) +1415 if len(time_items) == 3: +1416 (h, mi, s) = time_items +1417 else: +1418 (h, mi, s) = time_items + [0] +1419 colset[fieldname] = datetime.datetime(y, m, d, h, mi, s) +1420 elif field_type == 'blob' and blob_decode: +1421 colset[fieldname] = base64.b64decode(str(value)) +1422 elif field_type.startswith('decimal'): +1423 decimals = int(field_type[8:-1].split(',')[-1]) +1424 if self.dbengine == 'sqlite': +1425 value = ('%.' + str(decimals) + 'f') % value +1426 if not isinstance(value, decimal.Decimal): +1427 value = decimal.Decimal(str(value)) +1428 colset[fieldname] = value +1429 elif field_type.startswith('list:integer'): +1430 if not self.dbengine=='google:datastore': +1431 colset[fieldname] = bar_decode_integer(value) +1432 else: +1433 colset[fieldname] = value +1434 elif field_type.startswith('list:reference'): +1435 if not self.dbengine=='google:datastore': +1436 colset[fieldname] = bar_decode_integer(value) +1437 else: +1438 colset[fieldname] = value +1439 elif field_type.startswith('list:string'): +1440 if not self.dbengine=='google:datastore': +1441 colset[fieldname] = bar_decode_string(value) +1442 else: +1443 colset[fieldname] = value +1444 else: +1445 colset[fieldname] = value +1446 if field_type == 'id': +1447 id = colset[field.name] +1448 colset.update_record = lambda _ = (colset, table, id), **a: update_record(_, a) +1449 colset.delete_record = lambda t = table, i = id: t._db(t._id==i).delete() +1450 for (referee_table, referee_name) in \ +1451 table._referenced_by: +1452 s = db[referee_table][referee_name] +1453 if not referee_table in colset: +1454 # for backward compatibility +1455 colset[referee_table] = Set(db, s == id) +1456 ### add new feature? +1457 ### colset[referee_table+'_by_'+refree_name] = Set(db, s == id) +1458 colset['id'] = id +1459 new_rows.append(new_row) +1460 rowsobj = Rows(db, new_rows, colnames, rawrows=rows) +1461 for tablename in virtualtables: +1462 for item in db[tablename].virtualfields: +1463 try: +1464 rowsobj = rowsobj.setvirtualfields(**{tablename:item}) +1465 except KeyError: +1466 # to avoid breaking virtualfields when partial select +1467 pass +1468 return rowsobj +
    1469 +
    1470 - def filter_tenant(self,query,tablenames): +
    1471 fieldname = self.db._request_tenant +1472 for tablename in tablenames: +1473 table = self.db[tablename] +1474 if fieldname in table: +1475 default = table[fieldname].default +1476 if default!=None: +1477 query = query&(table[fieldname]==default) +1478 return query +
    1479 +1480 ################################################################################### +1481 # List of all the available adapters, they all extend BaseAdapter +1482 ################################################################################### +1483 +
    1484 -class SQLiteAdapter(BaseAdapter): +
    1485 +1486 driver = globals().get('sqlite3',None) +1487 +
    1488 - def EXTRACT(self,field,what): +
    1489 return "web2py_extract('%s',%s)" % (what,self.expand(field)) +
    1490 +1491 @staticmethod +
    1492 - def web2py_extract(lookup, s): +
    1493 table = { +1494 'year': (0, 4), +1495 'month': (5, 7), +1496 'day': (8, 10), +1497 'hour': (11, 13), +1498 'minute': (14, 16), +1499 'second': (17, 19), +1500 } +1501 try: +1502 (i, j) = table[lookup] +1503 return int(s[i:j]) +1504 except: +1505 return None +
    1506 +
    1507 - def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8', +1508 credential_decoder=lambda x:x, driver_args={}, +1509 adapter_args={}): +
    1510 self.db = db +1511 self.dbengine = "sqlite" +1512 self.uri = uri +1513 self.pool_size = pool_size +1514 self.folder = folder +1515 self.db_codec = db_codec +1516 self.find_or_make_work_folder() +1517 path_encoding = sys.getfilesystemencoding() or locale.getdefaultlocale()[1] or 'utf8' +1518 if uri.startswith('sqlite:memory'): +1519 dbpath = ':memory:' +1520 else: +1521 dbpath = uri.split('://')[1] +1522 if dbpath[0] != '/': +1523 dbpath = os.path.join(self.folder.decode(path_encoding).encode('utf8'),dbpath) +1524 if not 'check_same_thread' in driver_args: +1525 driver_args['check_same_thread'] = False +1526 def connect(dbpath=dbpath, driver_args=driver_args): +1527 return self.driver.Connection(dbpath, **driver_args) +
    1528 self.pool_connection(connect) +1529 self.cursor = self.connection.cursor() +1530 self.connection.create_function('web2py_extract', 2, SQLiteAdapter.web2py_extract) +
    1531 +
    1532 - def _truncate(self,table,mode = ''): +
    1533 tablename = table._tablename +1534 return ['DELETE FROM %s;' % tablename, +1535 "DELETE FROM sqlite_sequence WHERE name='%s';" % tablename] +
    1536 +
    1537 - def lastrowid(self,table): +
    1538 return self.cursor.lastrowid +
    1539 +1540 +
    1541 -class JDBCSQLiteAdapter(SQLiteAdapter): +
    1542 +1543 driver = globals().get('zxJDBC',None) +1544 +
    1545 - def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8', +1546 credential_decoder=lambda x:x, driver_args={}, +1547 adapter_args={}): +
    1548 self.db = db +1549 self.dbengine = "sqlite" +1550 self.uri = uri +1551 self.pool_size = pool_size +1552 self.folder = folder +1553 self.db_codec = db_codec +1554 self.find_or_make_work_folder() +1555 path_encoding = sys.getfilesystemencoding() or locale.getdefaultlocale()[1] or 'utf8' +1556 if uri.startswith('sqlite:memory'): +1557 dbpath = ':memory:' +1558 else: +1559 dbpath = uri.split('://')[1] +1560 if dbpath[0] != '/': +1561 dbpath = os.path.join(self.folder.decode(path_encoding).encode('utf8'),dbpath) +1562 def connect(dbpath=dbpath,driver_args=driver_args): +1563 return self.driver.connect(java.sql.DriverManager.getConnection('jdbc:sqlite:'+dbpath),**driver_args) +
    1564 self.pool_connection(connect) +1565 self.cursor = self.connection.cursor() +
    1566 # FIXME http://www.zentus.com/sqlitejdbc/custom_functions.html for UDFs +1567 # self.connection.create_function('web2py_extract', 2, SQLiteAdapter.web2py_extract) +1568 +
    1569 - def execute(self,a): +
    1570 return self.log_execute(a[:-1]) +
    1571 +1572 +
    1573 -class MySQLAdapter(BaseAdapter): +
    1574 +1575 driver = globals().get('pymysql',None) +1576 maxcharlength = 255 +1577 commit_on_alter_table = True +1578 support_distributed_transaction = True +1579 types = { +1580 'boolean': 'CHAR(1)', +1581 'string': 'VARCHAR(%(length)s)', +1582 'text': 'LONGTEXT', +1583 'password': 'VARCHAR(%(length)s)', +1584 'blob': 'LONGBLOB', +1585 'upload': 'VARCHAR(%(length)s)', +1586 'integer': 'INT', +1587 'double': 'DOUBLE', +1588 'decimal': 'NUMERIC(%(precision)s,%(scale)s)', +1589 'date': 'DATE', +1590 'time': 'TIME', +1591 'datetime': 'DATETIME', +1592 'id': 'INT AUTO_INCREMENT NOT NULL', +1593 'reference': 'INT, INDEX %(field_name)s__idx (%(field_name)s), FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s', +1594 'list:integer': 'LONGTEXT', +1595 'list:string': 'LONGTEXT', +1596 'list:reference': 'LONGTEXT', +1597 } +1598 +
    1599 - def RANDOM(self): +
    1600 return 'RAND()' +
    1601 +
    1602 - def SUBSTRING(self,field,parameters): +
    1603 return 'SUBSTRING(%s,%s,%s)' % (self.expand(field), parameters[0], parameters[1]) +
    1604 +
    1605 - def _drop(self,table,mode): +
    1606 # breaks db integrity but without this mysql does not drop table +1607 return ['SET FOREIGN_KEY_CHECKS=0;','DROP TABLE %s;' % table,'SET FOREIGN_KEY_CHECKS=1;'] +
    1608 +
    1609 - def distributed_transaction_begin(self,key): +
    1610 self.execute('XA START;') +
    1611 +
    1612 - def prepare(self,key): +
    1613 self.execute("XA END;") +1614 self.execute("XA PREPARE;") +
    1615 +
    1616 - def commit_prepared(self,ley): +
    1617 self.execute("XA COMMIT;") +
    1618 +
    1619 - def rollback_prepared(self,key): +
    1620 self.execute("XA ROLLBACK;") +
    1621 +
    1622 - def concat_add(self,table): +
    1623 return '; ALTER TABLE %s ADD ' % table +
    1624 +
    1625 - def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8', +1626 credential_decoder=lambda x:x, driver_args={}, +1627 adapter_args={}): +
    1628 self.db = db +1629 self.dbengine = "mysql" +1630 self.uri = uri +1631 self.pool_size = pool_size +1632 self.folder = folder +1633 self.db_codec = db_codec +1634 self.find_or_make_work_folder() +1635 uri = uri.split('://')[1] +1636 m = re.compile('^(?P<user>[^:@]+)(\:(?P<password>[^@]*))?@(?P<host>[^\:/]+)(\:(?P<port>[0-9]+))?/(?P<db>[^?]+)(\?set_encoding=(?P<charset>\w+))?$').match(uri) +1637 if not m: +1638 raise SyntaxError, \ +1639 "Invalid URI string in DAL: %s" % self.uri +1640 user = credential_decoder(m.group('user')) +1641 if not user: +1642 raise SyntaxError, 'User required' +1643 password = credential_decoder(m.group('password')) +1644 if not password: +1645 password = '' +1646 host = m.group('host') +1647 if not host: +1648 raise SyntaxError, 'Host name required' +1649 db = m.group('db') +1650 if not db: +1651 raise SyntaxError, 'Database name required' +1652 port = int(m.group('port') or '3306') +1653 charset = m.group('charset') or 'utf8' +1654 driver_args.update(dict(db=db, +1655 user=credential_decoder(user), +1656 passwd=credential_decoder(password), +1657 host=host, +1658 port=port, +1659 charset=charset)) +1660 def connect(driver_args=driver_args): +1661 return self.driver.connect(**driver_args) +
    1662 self.pool_connection(connect) +1663 self.cursor = self.connection.cursor() +1664 self.execute('SET FOREIGN_KEY_CHECKS=1;') +1665 self.execute("SET sql_mode='NO_BACKSLASH_ESCAPES';") +
    1666 +
    1667 - def lastrowid(self,table): +
    1668 self.execute('select last_insert_id();') +1669 return int(self.cursor.fetchone()[0]) +
    1670 +1671 +
    1672 -class PostgreSQLAdapter(BaseAdapter): +
    1673 +1674 driver = globals().get('psycopg2',None) +1675 +1676 support_distributed_transaction = True +1677 types = { +1678 'boolean': 'CHAR(1)', +1679 'string': 'VARCHAR(%(length)s)', +1680 'text': 'TEXT', +1681 'password': 'VARCHAR(%(length)s)', +1682 'blob': 'BYTEA', +1683 'upload': 'VARCHAR(%(length)s)', +1684 'integer': 'INTEGER', +1685 'double': 'FLOAT8', +1686 'decimal': 'NUMERIC(%(precision)s,%(scale)s)', +1687 'date': 'DATE', +1688 'time': 'TIME', +1689 'datetime': 'TIMESTAMP', +1690 'id': 'SERIAL PRIMARY KEY', +1691 'reference': 'INTEGER REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s', +1692 'list:integer': 'TEXT', +1693 'list:string': 'TEXT', +1694 'list:reference': 'TEXT', +1695 } +1696 +
    1697 - def sequence_name(self,table): +
    1698 return '%s_id_Seq' % table +
    1699 +
    1700 - def RANDOM(self): +
    1701 return 'RANDOM()' +
    1702 +
    1703 - def distributed_transaction_begin(self,key): +
    1704 return +
    1705 +
    1706 - def prepare(self,key): +
    1707 self.execute("PREPARE TRANSACTION '%s';" % key) +
    1708 +
    1709 - def commit_prepared(self,key): +
    1710 self.execute("COMMIT PREPARED '%s';" % key) +
    1711 +
    1712 - def rollback_prepared(self,key): +
    1713 self.execute("ROLLBACK PREPARED '%s';" % key) +
    1714 +
    1715 - def create_sequence_and_triggers(self, query, table, **args): +
    1716 # following lines should only be executed if table._sequence_name does not exist +1717 # self.execute('CREATE SEQUENCE %s;' % table._sequence_name) +1718 # self.execute("ALTER TABLE %s ALTER COLUMN %s SET DEFAULT NEXTVAL('%s');" \ +1719 # % (table._tablename, table._fieldname, table._sequence_name)) +1720 self.execute(query) +
    1721 +
    1722 - def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8', +1723 credential_decoder=lambda x:x, driver_args={}, +1724 adapter_args={}): +
    1725 self.db = db +1726 self.dbengine = "postgres" +1727 self.uri = uri +1728 self.pool_size = pool_size +1729 self.folder = folder +1730 self.db_codec = db_codec +1731 self.find_or_make_work_folder() +1732 uri = uri.split('://')[1] +1733 m = re.compile('^(?P<user>[^:@]+)(\:(?P<password>[^@]*))?@(?P<host>[^\:@/]+)(\:(?P<port>[0-9]+))?/(?P<db>[^\?]+)(\?sslmode=(?P<sslmode>.+))?$').match(uri) +1734 if not m: +1735 raise SyntaxError, "Invalid URI string in DAL" +1736 user = credential_decoder(m.group('user')) +1737 if not user: +1738 raise SyntaxError, 'User required' +1739 password = credential_decoder(m.group('password')) +1740 if not password: +1741 password = '' +1742 host = m.group('host') +1743 if not host: +1744 raise SyntaxError, 'Host name required' +1745 db = m.group('db') +1746 if not db: +1747 raise SyntaxError, 'Database name required' +1748 port = m.group('port') or '5432' +1749 sslmode = m.group('sslmode') +1750 if sslmode: +1751 msg = ("dbname='%s' user='%s' host='%s'" +1752 "port=%s password='%s' sslmode='%s'") \ +1753 % (db, user, host, port, password, sslmode) +1754 else: +1755 msg = ("dbname='%s' user='%s' host='%s'" +1756 "port=%s password='%s'") \ +1757 % (db, user, host, port, password) +1758 def connect(msg=msg,driver_args=driver_args): +1759 return self.driver.connect(msg,**driver_args) +
    1760 self.pool_connection(connect) +1761 self.connection.set_client_encoding('UTF8') +1762 self.cursor = self.connection.cursor() +1763 self.execute('BEGIN;') +1764 self.execute("SET CLIENT_ENCODING TO 'UNICODE';") +1765 self.execute("SET standard_conforming_strings=on;") +
    1766 +
    1767 - def lastrowid(self,table): +
    1768 self.execute("select currval('%s')" % table._sequence_name) +1769 return int(self.cursor.fetchone()[0]) +
    1770 +
    1771 - def LIKE(self,first,second): +
    1772 return '(%s ILIKE %s)' % (self.expand(first),self.expand(second,'string')) +
    1773 +
    1774 - def STARTSWITH(self,first,second): +
    1775 return '(%s ILIKE %s)' % (self.expand(first),self.expand(second+'%','string')) +
    1776 +
    1777 - def ENDSWITH(self,first,second): +
    1778 return '(%s ILIKE %s)' % (self.expand(first),self.expand('%'+second,'string')) +
    1779 +
    1780 - def CONTAINS(self,first,second): +
    1781 if first.type in ('string','text'): +1782 key = '%'+str(second).replace('%','%%')+'%' +1783 elif first.type.startswith('list:'): +1784 key = '%|'+str(second).replace('|','||').replace('%','%%')+'|%' +1785 return '(%s ILIKE %s)' % (self.expand(first),self.expand(key,'string')) +
    1786 +
    1787 -class JDBCPostgreSQLAdapter(PostgreSQLAdapter): +
    1788 +
    1789 - def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8', +1790 credential_decoder=lambda x:x, driver_args={}, +1791 adapter_args={}): +
    1792 self.db = db +1793 self.dbengine = "postgres" +1794 self.uri = uri +1795 self.pool_size = pool_size +1796 self.folder = folder +1797 self.db_codec = db_codec +1798 self.find_or_make_work_folder() +1799 uri = uri.split('://')[1] +1800 m = re.compile('^(?P<user>[^:@]+)(\:(?P<password>[^@]*))?@(?P<host>[^\:/]+)(\:(?P<port>[0-9]+))?/(?P<db>.+)$').match(uri) +1801 if not m: +1802 raise SyntaxError, "Invalid URI string in DAL" +1803 user = credential_decoder(m.group('user')) +1804 if not user: +1805 raise SyntaxError, 'User required' +1806 password = credential_decoder(m.group('password')) +1807 if not password: +1808 password = '' +1809 host = m.group('host') +1810 if not host: +1811 raise SyntaxError, 'Host name required' +1812 db = m.group('db') +1813 if not db: +1814 raise SyntaxError, 'Database name required' +1815 port = m.group('port') or '5432' +1816 msg = ('jdbc:postgresql://%s:%s/%s' % (host, port, db), user, password) +1817 def connect(msg=msg,driver_args=driver_args): +1818 return self.driver.connect(*msg,**driver_args) +
    1819 self.pool_connection(connect) +1820 self.connection.set_client_encoding('UTF8') +1821 self.cursor = self.connection.cursor() +1822 self.execute('BEGIN;') +1823 self.execute("SET CLIENT_ENCODING TO 'UNICODE';") +
    1824 +1825 +
    1826 -class OracleAdapter(BaseAdapter): +
    1827 +1828 driver = globals().get('cx_Oracle',None) +1829 +1830 commit_on_alter_table = False +1831 types = { +1832 'boolean': 'CHAR(1)', +1833 'string': 'VARCHAR2(%(length)s)', +1834 'text': 'CLOB', +1835 'password': 'VARCHAR2(%(length)s)', +1836 'blob': 'CLOB', +1837 'upload': 'VARCHAR2(%(length)s)', +1838 'integer': 'INT', +1839 'double': 'FLOAT', +1840 'decimal': 'NUMERIC(%(precision)s,%(scale)s)', +1841 'date': 'DATE', +1842 'time': 'CHAR(8)', +1843 'datetime': 'DATE', +1844 'id': 'NUMBER PRIMARY KEY', +1845 'reference': 'NUMBER, CONSTRAINT %(constraint_name)s FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s', +1846 'list:integer': 'CLOB', +1847 'list:string': 'CLOB', +1848 'list:reference': 'CLOB', +1849 } +1850 +
    1851 - def sequence_name(self,tablename): +
    1852 return '%s_sequence' % tablename +
    1853 +
    1854 - def trigger_name(self,tablename): +
    1855 return '%s_trigger' % tablename +
    1856 +
    1857 - def LEFT_JOIN(self): +
    1858 return 'LEFT OUTER JOIN' +
    1859 +
    1860 - def RANDOM(self): +
    1861 return 'dbms_random.value' +
    1862 +
    1863 - def NOT_NULL(self,default,field_type): +
    1864 return 'DEFAULT %s NOT NULL' % self.represent(default,field_type) +
    1865 +
    1866 - def _drop(self,table,mode): +
    1867 sequence_name = table._sequence_name +1868 return ['DROP TABLE %s %s;' % (table, mode), 'DROP SEQUENCE %s;' % sequence_name] +
    1869 +
    1870 - def select_limitby(self, sql_s, sql_f, sql_t, sql_w, sql_o, limitby): +
    1871 if limitby: +1872 (lmin, lmax) = limitby +1873 if len(sql_w) > 1: +1874 sql_w_row = sql_w + ' AND w_row > %i' % lmin +1875 else: +1876 sql_w_row = 'WHERE w_row > %i' % lmin +1877 return 'SELECT %s %s FROM (SELECT w_tmp.*, ROWNUM w_row FROM (SELECT %s FROM %s%s%s) w_tmp WHERE ROWNUM<=%i) %s %s %s;' % (sql_s, sql_f, sql_f, sql_t, sql_w, sql_o, lmax, sql_t, sql_w_row, sql_o) +1878 return 'SELECT %s %s FROM %s%s%s;' % (sql_s, sql_f, sql_t, sql_w, sql_o) +
    1879 +
    1880 - def constraint_name(self, tablename, fieldname): +
    1881 constraint_name = BaseAdapter.constraint_name(self, tablename, fieldname) +1882 if len(constraint_name)>30: +1883 constraint_name = '%s_%s__constraint' % (tablename[:10], fieldname[:7]) +1884 return constraint_name +
    1885 +
    1886 - def represent_exceptions(self, obj, fieldtype): +
    1887 if fieldtype == 'blob': +1888 obj = base64.b64encode(str(obj)) +1889 return ":CLOB('%s')" % obj +1890 elif fieldtype == 'date': +1891 if isinstance(obj, (datetime.date, datetime.datetime)): +1892 obj = obj.isoformat()[:10] +1893 else: +1894 obj = str(obj) +1895 return "to_date('%s','yyyy-mm-dd')" % obj +1896 elif fieldtype == 'datetime': +1897 if isinstance(obj, datetime.datetime): +1898 obj = obj.isoformat()[:19].replace('T',' ') +1899 elif isinstance(obj, datetime.date): +1900 obj = obj.isoformat()[:10]+' 00:00:00' +1901 else: +1902 obj = str(obj) +1903 return "to_date('%s','yyyy-mm-dd hh24:mi:ss')" % obj +1904 return None +
    1905 +
    1906 - def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8', +1907 credential_decoder=lambda x:x, driver_args={}, +1908 adapter_args={}): +
    1909 self.db = db +1910 self.dbengine = "oracle" +1911 self.uri = uri +1912 self.pool_size = pool_size +1913 self.folder = folder +1914 self.db_codec = db_codec +1915 self.find_or_make_work_folder() +1916 uri = uri.split('://')[1] +1917 if not 'threaded' in driver_args: +1918 driver_args['threaded']=True +1919 def connect(uri=uri,driver_args=driver_args): +1920 return self.driver.connect(uri,**driver_args) +
    1921 self.pool_connection(connect) +1922 self.cursor = self.connection.cursor() +1923 self.execute("ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS';") +1924 self.execute("ALTER SESSION SET NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS';") +
    1925 oracle_fix = re.compile("[^']*('[^']*'[^']*)*\:(?P<clob>CLOB\('([^']+|'')*'\))") +1926 +
    1927 - def execute(self, command): +
    1928 args = [] +1929 i = 1 +1930 while True: +1931 m = self.oracle_fix.match(command) +1932 if not m: +1933 break +1934 command = command[:m.start('clob')] + str(i) + command[m.end('clob'):] +1935 args.append(m.group('clob')[6:-2].replace("''", "'")) +1936 i += 1 +1937 return self.log_execute(command[:-1], args) +
    1938 +
    1939 - def create_sequence_and_triggers(self, query, table, **args): +
    1940 tablename = table._tablename +1941 sequence_name = table._sequence_name +1942 trigger_name = table._trigger_name +1943 self.execute(query) +1944 self.execute('CREATE SEQUENCE %s START WITH 1 INCREMENT BY 1 NOMAXVALUE;' % sequence_name) +1945 self.execute('CREATE OR REPLACE TRIGGER %s BEFORE INSERT ON %s FOR EACH ROW BEGIN SELECT %s.nextval INTO :NEW.id FROM DUAL; END;\n' % (trigger_name, tablename, sequence_name)) +
    1946 +
    1947 - def lastrowid(self,table): +
    1948 sequence_name = table._sequence_name +1949 self.execute('SELECT %s.currval FROM dual;' % sequence_name) +1950 return int(self.cursor.fetchone()[0]) +
    1951 +1952 +
    1953 -class MSSQLAdapter(BaseAdapter): +
    1954 +1955 driver = globals().get('pyodbc',None) +1956 +1957 types = { +1958 'boolean': 'BIT', +1959 'string': 'VARCHAR(%(length)s)', +1960 'text': 'TEXT', +1961 'password': 'VARCHAR(%(length)s)', +1962 'blob': 'IMAGE', +1963 'upload': 'VARCHAR(%(length)s)', +1964 'integer': 'INT', +1965 'double': 'FLOAT', +1966 'decimal': 'NUMERIC(%(precision)s,%(scale)s)', +1967 'date': 'DATETIME', +1968 'time': 'CHAR(8)', +1969 'datetime': 'DATETIME', +1970 'id': 'INT IDENTITY PRIMARY KEY', +1971 'reference': 'INT NULL, CONSTRAINT %(constraint_name)s FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s', +1972 'reference FK': ', CONSTRAINT FK_%(constraint_name)s FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s', +1973 'reference TFK': ' CONSTRAINT FK_%(foreign_table)s_PK FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_table)s (%(foreign_key)s) ON DELETE %(on_delete_action)s', +1974 'list:integer': 'TEXT', +1975 'list:string': 'TEXT', +1976 'list:reference': 'TEXT', +1977 } +1978 +
    1979 - def EXTRACT(self,field,what): +
    1980 return "DATEPART(%s,%s)" % (what, self.expand(field)) +
    1981 +
    1982 - def LEFT_JOIN(self): +
    1983 return 'LEFT OUTER JOIN' +
    1984 +
    1985 - def RANDOM(self): +
    1986 return 'NEWID()' +
    1987 +
    1988 - def ALLOW_NULL(self): +
    1989 return ' NULL' +
    1990 +
    1991 - def SUBSTRING(self,field,parameters): +
    1992 return 'SUBSTRING(%s,%s,%s)' % (self.expand(field), parameters[0], parameters[1]) +
    1993 +
    1994 - def PRIMARY_KEY(self,key): +
    1995 return 'PRIMARY KEY CLUSTERED (%s)' % key +
    1996 +
    1997 - def select_limitby(self, sql_s, sql_f, sql_t, sql_w, sql_o, limitby): +
    1998 if limitby: +1999 (lmin, lmax) = limitby +2000 sql_s += ' TOP %i' % lmax +2001 return 'SELECT %s %s FROM %s%s%s;' % (sql_s, sql_f, sql_t, sql_w, sql_o) +
    2002 +
    2003 - def represent_exceptions(self, obj, fieldtype): +
    2004 if fieldtype == 'boolean': +2005 if obj and not str(obj)[0].upper() == 'F': +2006 return '1' +2007 else: +2008 return '0' +2009 return None +
    2010 +
    2011 - def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8', +2012 credential_decoder=lambda x:x, driver_args={}, +2013 adapter_args={}, fake_connect=False): +
    2014 self.db = db +2015 self.dbengine = "mssql" +2016 self.uri = uri +2017 self.pool_size = pool_size +2018 self.folder = folder +2019 self.db_codec = db_codec +2020 self.find_or_make_work_folder() +2021 # ## read: http://bytes.com/groups/python/460325-cx_oracle-utf8 +2022 uri = uri.split('://')[1] +2023 if '@' not in uri: +2024 try: +2025 m = re.compile('^(?P<dsn>.+)$').match(uri) +2026 if not m: +2027 raise SyntaxError, \ +2028 'Parsing uri string(%s) has no result' % self.uri +2029 dsn = m.group('dsn') +2030 if not dsn: +2031 raise SyntaxError, 'DSN required' +2032 except SyntaxError, e: +2033 logger.error('NdGpatch error') +2034 raise e +2035 cnxn = 'DSN=%s' % dsn +2036 else: +2037 m = re.compile('^(?P<user>[^:@]+)(\:(?P<password>[^@]*))?@(?P<host>[^\:/]+)(\:(?P<port>[0-9]+))?/(?P<db>[^\?]+)(\?(?P<urlargs>.*))?$').match(uri) +2038 if not m: +2039 raise SyntaxError, \ +2040 "Invalid URI string in DAL: %s" % uri +2041 user = credential_decoder(m.group('user')) +2042 if not user: +2043 raise SyntaxError, 'User required' +2044 password = credential_decoder(m.group('password')) +2045 if not password: +2046 password = '' +2047 host = m.group('host') +2048 if not host: +2049 raise SyntaxError, 'Host name required' +2050 db = m.group('db') +2051 if not db: +2052 raise SyntaxError, 'Database name required' +2053 port = m.group('port') or '1433' +2054 # Parse the optional url name-value arg pairs after the '?' +2055 # (in the form of arg1=value1&arg2=value2&...) +2056 # Default values (drivers like FreeTDS insist on uppercase parameter keys) +2057 argsdict = { 'DRIVER':'{SQL Server}' } +2058 urlargs = m.group('urlargs') or '' +2059 argpattern = re.compile('(?P<argkey>[^=]+)=(?P<argvalue>[^&]*)') +2060 for argmatch in argpattern.finditer(urlargs): +2061 argsdict[str(argmatch.group('argkey')).upper()] = argmatch.group('argvalue') +2062 urlargs = ';'.join(['%s=%s' % (ak, av) for (ak, av) in argsdict.items()]) +2063 cnxn = 'SERVER=%s;PORT=%s;DATABASE=%s;UID=%s;PWD=%s;%s' \ +2064 % (host, port, db, user, password, urlargs) +2065 def connect(cnxn=cnxn,driver_args=driver_args): +2066 return self.driver.connect(cnxn,**driver_args) +
    2067 if not fake_connect: +2068 self.pool_connection(connect) +2069 self.cursor = self.connection.cursor() +
    2070 +
    2071 - def lastrowid(self,table): +
    2072 #self.execute('SELECT @@IDENTITY;') +2073 self.execute('SELECT SCOPE_IDENTITY();') +2074 return int(self.cursor.fetchone()[0]) +
    2075 +
    2076 - def integrity_error_class(self): +
    2077 return pyodbc.IntegrityError +
    2078 +
    2079 - def rowslice(self,rows,minimum=0,maximum=None): +
    2080 if maximum is None: +2081 return rows[minimum:] +2082 return rows[minimum:maximum] +
    2083 +2084 +
    2085 -class MSSQL2Adapter(MSSQLAdapter): +
    2086 types = { +2087 'boolean': 'CHAR(1)', +2088 'string': 'NVARCHAR(%(length)s)', +2089 'text': 'NTEXT', +2090 'password': 'NVARCHAR(%(length)s)', +2091 'blob': 'IMAGE', +2092 'upload': 'NVARCHAR(%(length)s)', +2093 'integer': 'INT', +2094 'double': 'FLOAT', +2095 'decimal': 'NUMERIC(%(precision)s,%(scale)s)', +2096 'date': 'DATETIME', +2097 'time': 'CHAR(8)', +2098 'datetime': 'DATETIME', +2099 'id': 'INT IDENTITY PRIMARY KEY', +2100 'reference': 'INT, CONSTRAINT %(constraint_name)s FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s', +2101 'reference FK': ', CONSTRAINT FK_%(constraint_name)s FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s', +2102 'reference TFK': ' CONSTRAINT FK_%(foreign_table)s_PK FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_table)s (%(foreign_key)s) ON DELETE %(on_delete_action)s', +2103 'list:integer': 'NTEXT', +2104 'list:string': 'NTEXT', +2105 'list:reference': 'NTEXT', +2106 } +2107 +
    2108 - def represent(self, obj, fieldtype): +
    2109 value = BaseAdapter.represent(self, obj, fieldtype) +2110 if (fieldtype == 'string' or fieldtype == 'text') and value[:1]=="'": +2111 value = 'N'+value +2112 return value +
    2113 +
    2114 - def execute(self,a): +
    2115 return self.log_execute(a.decode('utf8')) +
    2116 +2117 +
    2118 -class FireBirdAdapter(BaseAdapter): +
    2119 +2120 driver = globals().get('pyodbc',None) +2121 +2122 commit_on_alter_table = False +2123 support_distributed_transaction = True +2124 types = { +2125 'boolean': 'CHAR(1)', +2126 'string': 'VARCHAR(%(length)s)', +2127 'text': 'BLOB SUB_TYPE 1', +2128 'password': 'VARCHAR(%(length)s)', +2129 'blob': 'BLOB SUB_TYPE 0', +2130 'upload': 'VARCHAR(%(length)s)', +2131 'integer': 'INTEGER', +2132 'double': 'DOUBLE PRECISION', +2133 'decimal': 'DECIMAL(%(precision)s,%(scale)s)', +2134 'date': 'DATE', +2135 'time': 'TIME', +2136 'datetime': 'TIMESTAMP', +2137 'id': 'INTEGER PRIMARY KEY', +2138 'reference': 'INTEGER REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s', +2139 'list:integer': 'BLOB SUB_TYPE 1', +2140 'list:string': 'BLOB SUB_TYPE 1', +2141 'list:reference': 'BLOB SUB_TYPE 1', +2142 } +2143 +
    2144 - def sequence_name(self,tablename): +
    2145 return 'genid_%s' % tablename +
    2146 +
    2147 - def trigger_name(self,tablename): +
    2148 return 'trg_id_%s' % tablename +
    2149 +
    2150 - def RANDOM(self): +
    2151 return 'RAND()' +
    2152 +
    2153 - def NOT_NULL(self,default,field_type): +
    2154 return 'DEFAULT %s NOT NULL' % self.represent(default,field_type) +
    2155 +
    2156 - def SUBSTRING(self,field,parameters): +
    2157 return 'SUBSTRING(%s from %s for %s)' % (self.expand(field), parameters[0], parameters[1]) +
    2158 +
    2159 - def _drop(self,table,mode): +
    2160 sequence_name = table._sequence_name +2161 return ['DROP TABLE %s %s;' % (table, mode), 'DROP GENERATOR %s;' % sequence_name] +
    2162 +
    2163 - def select_limitby(self, sql_s, sql_f, sql_t, sql_w, sql_o, limitby): +
    2164 if limitby: +2165 (lmin, lmax) = limitby +2166 sql_s += ' FIRST %i SKIP %i' % (lmax - lmin, lmin) +2167 return 'SELECT %s %s FROM %s%s%s;' % (sql_s, sql_f, sql_t, sql_w, sql_o) +
    2168 +
    2169 - def _truncate(self,table,mode = ''): +
    2170 return ['DELETE FROM %s;' % table._tablename, +2171 'SET GENERATOR %s TO 0;' % table._sequence_name] +
    2172 +
    2173 - def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8', +2174 credential_decoder=lambda x:x, driver_args={}, +2175 adapter_args={}): +
    2176 self.db = db +2177 self.dbengine = "firebird" +2178 self.uri = uri +2179 self.pool_size = pool_size +2180 self.folder = folder +2181 self.db_codec = db_codec +2182 self.find_or_make_work_folder() +2183 uri = uri.split('://')[1] +2184 m = re.compile('^(?P<user>[^:@]+)(\:(?P<password>[^@]*))?@(?P<host>[^\:/]+)(\:(?P<port>[0-9]+))?/(?P<db>.+?)(\?set_encoding=(?P<charset>\w+))?$').match(uri) +2185 if not m: +2186 raise SyntaxError, "Invalid URI string in DAL: %s" % uri +2187 user = credential_decoder(m.group('user')) +2188 if not user: +2189 raise SyntaxError, 'User required' +2190 password = credential_decoder(m.group('password')) +2191 if not password: +2192 password = '' +2193 host = m.group('host') +2194 if not host: +2195 raise SyntaxError, 'Host name required' +2196 port = int(m.group('port') or 3050) +2197 db = m.group('db') +2198 if not db: +2199 raise SyntaxError, 'Database name required' +2200 charset = m.group('charset') or 'UTF8' +2201 driver_args.update(dict(dsn='%s/%s:%s' % (host,port,db), +2202 user = credential_decoder(user), +2203 password = credential_decoder(password), +2204 charset = charset)) +2205 if adapter_args.has_key('driver_name'): +2206 if adapter_args['driver_name'] == 'kinterbasdb': +2207 self.driver = kinterbasdb +2208 elif adapter_args['driver_name'] == 'firebirdsql': +2209 self.driver = firebirdsql +2210 else: +2211 self.driver = kinterbasdb +2212 def connect(driver_args=driver_args): +2213 return self.driver.connect(**driver_args) +
    2214 self.pool_connection(connect) +2215 self.cursor = self.connection.cursor() +
    2216 +
    2217 - def create_sequence_and_triggers(self, query, table, **args): +
    2218 tablename = table._tablename +2219 sequence_name = table._sequence_name +2220 trigger_name = table._trigger_name +2221 self.execute(query) +2222 self.execute('create generator %s;' % sequence_name) +2223 self.execute('set generator %s to 0;' % sequence_name) +2224 self.execute('create trigger %s for %s active before insert position 0 as\nbegin\nif(new.id is null) then\nbegin\nnew.id = gen_id(%s, 1);\nend\nend;' % (trigger_name, tablename, sequence_name)) +
    2225 +
    2226 - def lastrowid(self,table): +
    2227 sequence_name = table._sequence_name +2228 self.execute('SELECT gen_id(%s, 0) FROM rdb$database' % sequence_name) +2229 return int(self.cursor.fetchone()[0]) +
    2230 +2231 +
    2232 -class FireBirdEmbeddedAdapter(FireBirdAdapter): +
    2233 +
    2234 - def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8', +2235 credential_decoder=lambda x:x, driver_args={}, +2236 adapter_args={}): +
    2237 self.db = db +2238 self.dbengine = "firebird" +2239 self.uri = uri +2240 self.pool_size = pool_size +2241 self.folder = folder +2242 self.db_codec = db_codec +2243 self.find_or_make_work_folder() +2244 uri = uri.split('://')[1] +2245 m = re.compile('^(?P<user>[^:@]+)(\:(?P<password>[^@]*))?@(?P<path>[^\?]+)(\?set_encoding=(?P<charset>\w+))?$').match(uri) +2246 if not m: +2247 raise SyntaxError, \ +2248 "Invalid URI string in DAL: %s" % self.uri +2249 user = credential_decoder(m.group('user')) +2250 if not user: +2251 raise SyntaxError, 'User required' +2252 password = credential_decoder(m.group('password')) +2253 if not password: +2254 password = '' +2255 pathdb = m.group('path') +2256 if not pathdb: +2257 raise SyntaxError, 'Path required' +2258 charset = m.group('charset') +2259 if not charset: +2260 charset = 'UTF8' +2261 host = '' +2262 driver_args.update(dict(host=host, +2263 database=pathdb, +2264 user=credential_decoder(user), +2265 password=credential_decoder(password), +2266 charset=charset)) +2267 #def connect(driver_args=driver_args): +2268 # return kinterbasdb.connect(**driver_args) +2269 if adapter_args.has_key('driver_name'): +2270 if adapter_args['driver_name'] == 'kinterbasdb': +2271 self.driver = kinterbasdb +2272 elif adapter_args['driver_name'] == 'firebirdsql': +2273 self.driver = firebirdsql +2274 else: +2275 self.driver = kinterbasdb +2276 def connect(driver_args=driver_args): +2277 return self.driver.connect(**driver_args) +
    2278 self.pool_connection(connect) +2279 self.cursor = self.connection.cursor() +
    2280 +2281 +
    2282 -class InformixAdapter(BaseAdapter): +
    2283 +2284 driver = globals().get('informixdb',None) +2285 +2286 types = { +2287 'boolean': 'CHAR(1)', +2288 'string': 'VARCHAR(%(length)s)', +2289 'text': 'BLOB SUB_TYPE 1', +2290 'password': 'VARCHAR(%(length)s)', +2291 'blob': 'BLOB SUB_TYPE 0', +2292 'upload': 'VARCHAR(%(length)s)', +2293 'integer': 'INTEGER', +2294 'double': 'FLOAT', +2295 'decimal': 'NUMERIC(%(precision)s,%(scale)s)', +2296 'date': 'DATE', +2297 'time': 'CHAR(8)', +2298 'datetime': 'DATETIME', +2299 'id': 'SERIAL', +2300 'reference': 'INTEGER REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s', +2301 'reference FK': 'REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s CONSTRAINT FK_%(table_name)s_%(field_name)s', +2302 'reference TFK': 'FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_table)s (%(foreign_key)s) ON DELETE %(on_delete_action)s CONSTRAINT TFK_%(table_name)s_%(field_name)s', +2303 'list:integer': 'BLOB SUB_TYPE 1', +2304 'list:string': 'BLOB SUB_TYPE 1', +2305 'list:reference': 'BLOB SUB_TYPE 1', +2306 } +2307 +
    2308 - def RANDOM(self): +
    2309 return 'Random()' +
    2310 +
    2311 - def NOT_NULL(self,default,field_type): +
    2312 return 'DEFAULT %s NOT NULL' % self.represent(default,field_type) +
    2313 +
    2314 - def select_limitby(self, sql_s, sql_f, sql_t, sql_w, sql_o, limitby): +
    2315 if limitby: +2316 (lmin, lmax) = limitby +2317 fetch_amt = lmax - lmin +2318 dbms_version = int(self.connection.dbms_version.split('.')[0]) +2319 if lmin and (dbms_version >= 10): +2320 # Requires Informix 10.0+ +2321 sql_s += ' SKIP %d' % (lmin, ) +2322 if fetch_amt and (dbms_version >= 9): +2323 # Requires Informix 9.0+ +2324 sql_s += ' FIRST %d' % (fetch_amt, ) +2325 return 'SELECT %s %s FROM %s%s%s;' % (sql_s, sql_f, sql_t, sql_w, sql_o) +
    2326 +
    2327 - def represent_exceptions(self, obj, fieldtype): +
    2328 if fieldtype == 'date': +2329 if isinstance(obj, (datetime.date, datetime.datetime)): +2330 obj = obj.isoformat()[:10] +2331 else: +2332 obj = str(obj) +2333 return "to_date('%s','yyyy-mm-dd')" % obj +2334 elif fieldtype == 'datetime': +2335 if isinstance(obj, datetime.datetime): +2336 obj = obj.isoformat()[:19].replace('T',' ') +2337 elif isinstance(obj, datetime.date): +2338 obj = obj.isoformat()[:10]+' 00:00:00' +2339 else: +2340 obj = str(obj) +2341 return "to_date('%s','yyyy-mm-dd hh24:mi:ss')" % obj +2342 return None +
    2343 +
    2344 - def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8', +2345 credential_decoder=lambda x:x, driver_args={}, +2346 adapter_args={}): +
    2347 self.db = db +2348 self.dbengine = "informix" +2349 self.uri = uri +2350 self.pool_size = pool_size +2351 self.folder = folder +2352 self.db_codec = db_codec +2353 self.find_or_make_work_folder() +2354 uri = uri.split('://')[1] +2355 m = re.compile('^(?P<user>[^:@]+)(\:(?P<password>[^@]*))?@(?P<host>[^\:/]+)(\:(?P<port>[0-9]+))?/(?P<db>.+)$').match(uri) +2356 if not m: +2357 raise SyntaxError, \ +2358 "Invalid URI string in DAL: %s" % self.uri +2359 user = credential_decoder(m.group('user')) +2360 if not user: +2361 raise SyntaxError, 'User required' +2362 password = credential_decoder(m.group('password')) +2363 if not password: +2364 password = '' +2365 host = m.group('host') +2366 if not host: +2367 raise SyntaxError, 'Host name required' +2368 db = m.group('db') +2369 if not db: +2370 raise SyntaxError, 'Database name required' +2371 user = credential_decoder(user) +2372 password = credential_decoder(password) +2373 dsn = '%s@%s' % (db,host) +2374 driver_args.update(dict(user=user,password=password,autocommit=True)) +2375 def connect(dsn=dsn,driver_args=driver_args): +2376 return self.driver.connect(dsn,**driver_args) +
    2377 self.pool_connection(connect) +2378 self.cursor = self.connection.cursor() +
    2379 +
    2380 - def execute(self,command): +
    2381 if command[-1:]==';': +2382 command = command[:-1] +2383 return self.log_execute(command) +
    2384 +
    2385 - def lastrowid(self,table): +
    2386 return self.cursor.sqlerrd[1] +
    2387 +
    2388 - def integrity_error_class(self): +
    2389 return informixdb.IntegrityError +
    2390 +2391 +
    2392 -class DB2Adapter(BaseAdapter): +
    2393 +2394 driver = globals().get('pyodbc',None) +2395 +2396 types = { +2397 'boolean': 'CHAR(1)', +2398 'string': 'VARCHAR(%(length)s)', +2399 'text': 'CLOB', +2400 'password': 'VARCHAR(%(length)s)', +2401 'blob': 'BLOB', +2402 'upload': 'VARCHAR(%(length)s)', +2403 'integer': 'INT', +2404 'double': 'DOUBLE', +2405 'decimal': 'NUMERIC(%(precision)s,%(scale)s)', +2406 'date': 'DATE', +2407 'time': 'TIME', +2408 'datetime': 'TIMESTAMP', +2409 'id': 'INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL', +2410 'reference': 'INT, FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s', +2411 'reference FK': ', CONSTRAINT FK_%(constraint_name)s FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s', +2412 'reference TFK': ' CONSTRAINT FK_%(foreign_table)s_PK FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_table)s (%(foreign_key)s) ON DELETE %(on_delete_action)s', +2413 'list:integer': 'CLOB', +2414 'list:string': 'CLOB', +2415 'list:reference': 'CLOB', +2416 } +2417 +
    2418 - def LEFT_JOIN(self): +
    2419 return 'LEFT OUTER JOIN' +
    2420 +
    2421 - def RANDOM(self): +
    2422 return 'RAND()' +
    2423 +
    2424 - def select_limitby(self, sql_s, sql_f, sql_t, sql_w, sql_o, limitby): +
    2425 if limitby: +2426 (lmin, lmax) = limitby +2427 sql_o += ' FETCH FIRST %i ROWS ONLY' % lmax +2428 return 'SELECT %s %s FROM %s%s%s;' % (sql_s, sql_f, sql_t, sql_w, sql_o) +
    2429 +
    2430 - def represent_exceptions(self, obj, fieldtype): +
    2431 if fieldtype == 'blob': +2432 obj = base64.b64encode(str(obj)) +2433 return "BLOB('%s')" % obj +2434 elif fieldtype == 'datetime': +2435 if isinstance(obj, datetime.datetime): +2436 obj = obj.isoformat()[:19].replace('T','-').replace(':','.') +2437 elif isinstance(obj, datetime.date): +2438 obj = obj.isoformat()[:10]+'-00.00.00' +2439 return "'%s'" % obj +2440 return None +
    2441 +
    2442 - def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8', +2443 credential_decoder=lambda x:x, driver_args={}, +2444 adapter_args={}): +
    2445 self.db = db +2446 self.dbengine = "db2" +2447 self.uri = uri +2448 self.pool_size = pool_size +2449 self.folder = folder +2450 self.db_codec = db_codec +2451 self.find_or_make_work_folder() +2452 cnxn = uri.split('://', 1)[1] +2453 def connect(cnxn=cnxn,driver_args=driver_args): +2454 return self.driver.connect(cnxn,**driver_args) +
    2455 self.pool_connection(connect) +2456 self.cursor = self.connection.cursor() +
    2457 +
    2458 - def execute(self,command): +
    2459 if command[-1:]==';': +2460 command = command[:-1] +2461 return self.log_execute(command) +
    2462 +
    2463 - def lastrowid(self,table): +
    2464 self.execute('SELECT DISTINCT IDENTITY_VAL_LOCAL() FROM %s;' % table) +2465 return int(self.cursor.fetchone()[0]) +
    2466 +
    2467 - def rowslice(self,rows,minimum=0,maximum=None): +
    2468 if maximum is None: +2469 return rows[minimum:] +2470 return rows[minimum:maximum] +
    2471 +2472 +
    2473 -class TeradataAdapter(DB2Adapter): +
    2474 +2475 driver = globals().get('pyodbc',None) +2476 +2477 types = { +2478 'boolean': 'CHAR(1)', +2479 'string': 'VARCHAR(%(length)s)', +2480 'text': 'CLOB', +2481 'password': 'VARCHAR(%(length)s)', +2482 'blob': 'BLOB', +2483 'upload': 'VARCHAR(%(length)s)', +2484 'integer': 'INT', +2485 'double': 'DOUBLE', +2486 'decimal': 'NUMERIC(%(precision)s,%(scale)s)', +2487 'date': 'DATE', +2488 'time': 'TIME', +2489 'datetime': 'TIMESTAMP', +2490 'id': 'INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL', +2491 'reference': 'INT, FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s', +2492 'reference FK': ', CONSTRAINT FK_%(constraint_name)s FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s', +2493 'reference TFK': ' CONSTRAINT FK_%(foreign_table)s_PK FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_table)s (%(foreign_key)s) ON DELETE %(on_delete_action)s', +2494 'list:integer': 'CLOB', +2495 'list:string': 'CLOB', +2496 'list:reference': 'CLOB', +2497 } +2498 +2499 +
    2500 - def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8', +2501 credential_decoder=lambda x:x, driver_args={}, +2502 adapter_args={}): +
    2503 self.db = db +2504 self.dbengine = "teradata" +2505 self.uri = uri +2506 self.pool_size = pool_size +2507 self.folder = folder +2508 self.db_codec = db_codec +2509 self.find_or_make_work_folder() +2510 cnxn = uri.split('://', 1)[1] +2511 def connect(cnxn=cnxn,driver_args=driver_args): +2512 return self.driver.connect(cnxn,**driver_args) +
    2513 self.pool_connection(connect) +2514 self.cursor = self.connection.cursor() +
    2515 +2516 +2517 INGRES_SEQNAME='ii***lineitemsequence' # NOTE invalid database object name +2518 # (ANSI-SQL wants this form of name +2519 # to be a delimited identifier) +2520 +
    2521 -class IngresAdapter(BaseAdapter): +
    2522 +2523 driver = globals().get('ingresdbi',None) +2524 +2525 types = { +2526 'boolean': 'CHAR(1)', +2527 'string': 'VARCHAR(%(length)s)', +2528 'text': 'CLOB', +2529 'password': 'VARCHAR(%(length)s)', ## Not sure what this contains utf8 or nvarchar. Or even bytes? +2530 'blob': 'BLOB', +2531 'upload': 'VARCHAR(%(length)s)', ## FIXME utf8 or nvarchar... or blob? what is this type? +2532 'integer': 'INTEGER4', # or int8... +2533 'double': 'FLOAT8', +2534 'decimal': 'NUMERIC(%(precision)s,%(scale)s)', +2535 'date': 'ANSIDATE', +2536 'time': 'TIME WITHOUT TIME ZONE', +2537 'datetime': 'TIMESTAMP WITHOUT TIME ZONE', +2538 'id': 'integer4 not null unique with default next value for %s' % INGRES_SEQNAME, +2539 'reference': 'integer4, FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s', +2540 'reference FK': ', CONSTRAINT FK_%(constraint_name)s FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s', +2541 'reference TFK': ' CONSTRAINT FK_%(foreign_table)s_PK FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_table)s (%(foreign_key)s) ON DELETE %(on_delete_action)s', ## FIXME TODO +2542 'list:integer': 'CLOB', +2543 'list:string': 'CLOB', +2544 'list:reference': 'CLOB', +2545 } +2546 +
    2547 - def LEFT_JOIN(self): +
    2548 return 'LEFT OUTER JOIN' +
    2549 +
    2550 - def RANDOM(self): +
    2551 return 'RANDOM()' +
    2552 +
    2553 - def select_limitby(self, sql_s, sql_f, sql_t, sql_w, sql_o, limitby): +
    2554 if limitby: +2555 (lmin, lmax) = limitby +2556 fetch_amt = lmax - lmin +2557 if fetch_amt: +2558 sql_s += ' FIRST %d ' % (fetch_amt, ) +2559 if lmin: +2560 # Requires Ingres 9.2+ +2561 sql_o += ' OFFSET %d' % (lmin, ) +2562 return 'SELECT %s %s FROM %s%s%s;' % (sql_s, sql_f, sql_t, sql_w, sql_o) +
    2563 +
    2564 - def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8', +2565 credential_decoder=lambda x:x, driver_args={}, +2566 adapter_args={}): +
    2567 self.db = db +2568 self.dbengine = "ingres" +2569 self.uri = uri +2570 self.pool_size = pool_size +2571 self.folder = folder +2572 self.db_codec = db_codec +2573 self.find_or_make_work_folder() +2574 connstr = self._uri.split(':', 1)[1] +2575 # Simple URI processing +2576 connstr = connstr.lstrip() +2577 while connstr.startswith('/'): +2578 connstr = connstr[1:] +2579 database_name=connstr # Assume only (local) dbname is passed in +2580 vnode = '(local)' +2581 servertype = 'ingres' +2582 trace = (0, None) # No tracing +2583 driver_args.update(dict(database=database_name, +2584 vnode=vnode, +2585 servertype=servertype, +2586 trace=trace)) +2587 def connect(driver_args=driver_args): +2588 return self.driver.connect(**driver_args) +
    2589 self.pool_connection(connect) +2590 self.cursor = self.connection.cursor() +
    2591 +
    2592 - def create_sequence_and_triggers(self, query, table, **args): +
    2593 # post create table auto inc code (if needed) +2594 # modify table to btree for performance.... +2595 # Older Ingres releases could use rule/trigger like Oracle above. +2596 if hasattr(table,'_primarykey'): +2597 modify_tbl_sql = 'modify %s to btree unique on %s' % \ +2598 (table._tablename, +2599 ', '.join(["'%s'" % x for x in table.primarykey])) +2600 self.execute(modify_tbl_sql) +2601 else: +2602 tmp_seqname='%s_iisq' % table._tablename +2603 query=query.replace(INGRES_SEQNAME, tmp_seqname) +2604 self.execute('create sequence %s' % tmp_seqname) +2605 self.execute(query) +2606 self.execute('modify %s to btree unique on %s' % (table._tablename, 'id')) +
    2607 +2608 +
    2609 - def lastrowid(self,table): +
    2610 tmp_seqname='%s_iisq' % table +2611 self.execute('select current value for %s' % tmp_seqname) +2612 return int(self.cursor.fetchone()[0]) # don't really need int type cast here... +
    2613 +
    2614 - def integrity_error_class(self): +
    2615 return ingresdbi.IntegrityError +
    2616 +2617 +
    2618 -class IngresUnicodeAdapter(IngresAdapter): +
    2619 types = { +2620 'boolean': 'CHAR(1)', +2621 'string': 'NVARCHAR(%(length)s)', +2622 'text': 'NCLOB', +2623 'password': 'NVARCHAR(%(length)s)', ## Not sure what this contains utf8 or nvarchar. Or even bytes? +2624 'blob': 'BLOB', +2625 'upload': 'VARCHAR(%(length)s)', ## FIXME utf8 or nvarchar... or blob? what is this type? +2626 'integer': 'INTEGER4', # or int8... +2627 'double': 'FLOAT8', +2628 'decimal': 'NUMERIC(%(precision)s,%(scale)s)', +2629 'date': 'ANSIDATE', +2630 'time': 'TIME WITHOUT TIME ZONE', +2631 'datetime': 'TIMESTAMP WITHOUT TIME ZONE', +2632 'id': 'integer4 not null unique with default next value for %s'% INGRES_SEQNAME, +2633 'reference': 'integer4, FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s', +2634 'reference FK': ', CONSTRAINT FK_%(constraint_name)s FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s', +2635 'reference TFK': ' CONSTRAINT FK_%(foreign_table)s_PK FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_table)s (%(foreign_key)s) ON DELETE %(on_delete_action)s', ## FIXME TODO +2636 'list:integer': 'NCLOB', +2637 'list:string': 'NCLOB', +2638 'list:reference': 'NCLOB', +2639 } +
    2640 +
    2641 -class SAPDBAdapter(BaseAdapter): +
    2642 +2643 driver = globals().get('sapdb',None) +2644 support_distributed_transaction = False +2645 types = { +2646 'boolean': 'CHAR(1)', +2647 'string': 'VARCHAR(%(length)s)', +2648 'text': 'LONG', +2649 'password': 'VARCHAR(%(length)s)', +2650 'blob': 'LONG', +2651 'upload': 'VARCHAR(%(length)s)', +2652 'integer': 'INT', +2653 'double': 'FLOAT', +2654 'decimal': 'FIXED(%(precision)s,%(scale)s)', +2655 'date': 'DATE', +2656 'time': 'TIME', +2657 'datetime': 'TIMESTAMP', +2658 'id': 'INT PRIMARY KEY', +2659 'reference': 'INT, FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s', +2660 'list:integer': 'LONG', +2661 'list:string': 'LONG', +2662 'list:reference': 'LONG', +2663 } +2664 +
    2665 - def sequence_name(self,table): +
    2666 return '%s_id_Seq' % table +
    2667 +
    2668 - def select_limitby(self, sql_s, sql_f, sql_t, sql_w, sql_o, limitby): +
    2669 if limitby: +2670 (lmin, lmax) = limitby +2671 if len(sql_w) > 1: +2672 sql_w_row = sql_w + ' AND w_row > %i' % lmin +2673 else: +2674 sql_w_row = 'WHERE w_row > %i' % lmin +2675 return '%s %s FROM (SELECT w_tmp.*, ROWNO w_row FROM (SELECT %s FROM %s%s%s) w_tmp WHERE ROWNO=%i) %s %s %s;' % (sql_s, sql_f, sql_f, sql_t, sql_w, sql_o, lmax, sql_t, sql_w_row, sql_o) +2676 return 'SELECT %s %s FROM %s%s%s;' % (sql_s, sql_f, sql_t, sql_w, sql_o) +
    2677 +
    2678 - def create_sequence_and_triggers(self, query, table, **args): +
    2679 # following lines should only be executed if table._sequence_name does not exist +2680 self.execute('CREATE SEQUENCE %s;' % table._sequence_name) +2681 self.execute("ALTER TABLE %s ALTER COLUMN %s SET DEFAULT NEXTVAL('%s');" \ +2682 % (table._tablename, table._id.name, table._sequence_name)) +2683 self.execute(query) +
    2684 +
    2685 - def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8', +2686 credential_decoder=lambda x:x, driver_args={}, +2687 adapter_args={}): +
    2688 self.db = db +2689 self.dbengine = "sapdb" +2690 self.uri = uri +2691 self.pool_size = pool_size +2692 self.folder = folder +2693 self.db_codec = db_codec +2694 self.find_or_make_work_folder() +2695 uri = uri.split('://')[1] +2696 m = re.compile('^(?P<user>[^:@]+)(\:(?P<password>[^@]*))?@(?P<host>[^\:@/]+)(\:(?P<port>[0-9]+))?/(?P<db>[^\?]+)(\?sslmode=(?P<sslmode>.+))?$').match(uri) +2697 if not m: +2698 raise SyntaxError, "Invalid URI string in DAL" +2699 user = credential_decoder(m.group('user')) +2700 if not user: +2701 raise SyntaxError, 'User required' +2702 password = credential_decoder(m.group('password')) +2703 if not password: +2704 password = '' +2705 host = m.group('host') +2706 if not host: +2707 raise SyntaxError, 'Host name required' +2708 db = m.group('db') +2709 if not db: +2710 raise SyntaxError, 'Database name required' +2711 def connect(user=user,password=password,database=db, +2712 host=host,driver_args=driver_args): +2713 return self.driver.Connection(user,password,database, +2714 host,**driver_args) +
    2715 self.pool_connection(connect) +2716 # self.connection.set_client_encoding('UTF8') +2717 self.cursor = self.connection.cursor() +
    2718 +
    2719 - def lastrowid(self,table): +
    2720 self.execute("select %s.NEXTVAL from dual" % table._sequence_name) +2721 return int(self.cursor.fetchone()[0]) +
    2722 +
    2723 -class CubridAdapter(MySQLAdapter): +
    2724 +2725 driver = globals().get('cubriddb',None) +2726 +
    2727 - def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8', +2728 credential_decoder=lambda x:x, driver_args={}, +2729 adapter_args={}): +
    2730 self.db = db +2731 self.dbengine = "cubrid" +2732 self.uri = uri +2733 self.pool_size = pool_size +2734 self.folder = folder +2735 self.db_codec = db_codec +2736 self.find_or_make_work_folder() +2737 uri = uri.split('://')[1] +2738 m = re.compile('^(?P<user>[^:@]+)(\:(?P<password>[^@]*))?@(?P<host>[^\:/]+)(\:(?P<port>[0-9]+))?/(?P<db>[^?]+)(\?set_encoding=(?P<charset>\w+))?$').match(uri) +2739 if not m: +2740 raise SyntaxError, \ +2741 "Invalid URI string in DAL: %s" % self.uri +2742 user = credential_decoder(m.group('user')) +2743 if not user: +2744 raise SyntaxError, 'User required' +2745 password = credential_decoder(m.group('password')) +2746 if not password: +2747 password = '' +2748 host = m.group('host') +2749 if not host: +2750 raise SyntaxError, 'Host name required' +2751 db = m.group('db') +2752 if not db: +2753 raise SyntaxError, 'Database name required' +2754 port = int(m.group('port') or '30000') +2755 charset = m.group('charset') or 'utf8' +2756 user=credential_decoder(user), +2757 passwd=credential_decoder(password), +2758 def connect(host,port,db,user,passwd,driver_args=driver_args): +2759 return self.driver.connect(host,port,db,user,passwd,**driver_args) +
    2760 self.pool_connection(connect) +2761 self.cursor = self.connection.cursor() +2762 self.execute('SET FOREIGN_KEY_CHECKS=1;') +2763 self.execute("SET sql_mode='NO_BACKSLASH_ESCAPES';") +
    2764 +2765 +2766 ######## GAE MySQL ########## +2767 +
    2768 -class DatabaseStoredFile: +
    2769 +2770 web2py_filesystem = False +2771 +
    2772 - def __init__(self,db,filename,mode): +
    2773 if db._adapter.dbengine != 'mysql': +2774 raise RuntimeError, "only MySQL can store metadata .table files in database for now" +2775 self.db = db +2776 self.filename = filename +2777 self.mode = mode +2778 if not self.web2py_filesystem: +2779 self.db.executesql("CREATE TABLE IF NOT EXISTS web2py_filesystem (path VARCHAR(512), content LONGTEXT, PRIMARY KEY(path) ) ENGINE=InnoDB;") +2780 DatabaseStoredFile.web2py_filesystem = True +2781 self.p=0 +2782 self.data = '' +2783 if mode in ('r','rw','a'): +2784 query = "SELECT content FROM web2py_filesystem WHERE path='%s'" % filename +2785 rows = self.db.executesql(query) +2786 if rows: +2787 self.data = rows[0][0] +2788 elif os.path.exists(filename): +2789 datafile = open(filename, 'r') +2790 try: +2791 self.data = datafile.read() +2792 finally: +2793 datafile.close() +2794 elif mode in ('r','rw'): +2795 raise RuntimeError, "File %s does not exist" % filename +
    2796 +
    2797 - def read(self, bytes): +
    2798 data = self.data[self.p:self.p+bytes] +2799 self.p += len(data) +2800 return data +
    2801 +
    2802 - def readline(self): +
    2803 i = self.data.find('\n',self.p)+1 +2804 if i>0: +2805 data, self.p = self.data[self.p:i], i +2806 else: +2807 data, self.p = self.data[self.p:], len(self.data) +2808 return data +
    2809 +
    2810 - def write(self,data): +
    2811 self.data += data +
    2812 +
    2813 - def close(self): +
    2814 self.db.executesql("DELETE FROM web2py_filesystem WHERE path='%s'" % self.filename) +2815 query = "INSERT INTO web2py_filesystem(path,content) VALUES ('%s','%s')" % \ +2816 (self.filename, self.data.replace("'","''")) +2817 self.db.executesql(query) +2818 self.db.commit() +
    2819 +2820 @staticmethod +
    2821 - def exists(db,filename): +
    2822 if os.path.exists(filename): +2823 return True +2824 query = "SELECT path FROM web2py_filesystem WHERE path='%s'" % filename +2825 if db.executesql(query): +2826 return True +2827 return False +
    2828 +2829 +
    2831 +
    2832 - def file_exists(self, filename): +
    2833 return DatabaseStoredFile.exists(self.db,filename) +
    2834 +
    2835 - def file_open(self, filename, mode='rb', lock=True): +
    2836 return DatabaseStoredFile(self.db,filename,mode) +
    2837 +
    2838 - def file_close(self, fileobj, unlock=True): +
    2839 fileobj.close() +
    2840 +
    2841 - def file_delete(self,filename): +
    2842 query = "DELETE FROM web2py_filesystem WHERE path='%s'" % filename +2843 self.db.executesql(query) +2844 self.db.commit() +
    2845 +
    2846 -class GoogleSQLAdapter(UseDatabaseStoredFile,MySQLAdapter): +
    2847 +
    2848 - def __init__(self, db, uri='google:sql://realm:domain/database', pool_size=0, +2849 folder=None, db_codec='UTF-8', check_reserved=None, +2850 migrate=True, fake_migrate=False, +2851 credential_decoder = lambda x:x, driver_args={}, +2852 adapter_args={}): +
    2853 +2854 self.db = db +2855 self.dbengine = "mysql" +2856 self.uri = uri +2857 self.pool_size = pool_size +2858 self.folder = folder +2859 self.db_codec = db_codec +2860 self.folder = folder or '$HOME/'+thread.folder.split('/applications/',1)[1] +2861 +2862 m = re.compile('^(?P<instance>.*)/(?P<db>.*)$').match(self.uri[len('google:sql://'):]) +2863 if not m: +2864 raise SyntaxError, "Invalid URI string in SQLDB: %s" % self._uri +2865 instance = credential_decoder(m.group('instance')) +2866 db = credential_decoder(m.group('db')) +2867 driver_args['instance'] = instance +2868 if not migrate: +2869 driver_args['database'] = db +2870 def connect(driver_args=driver_args): +2871 return rdbms.connect(**driver_args) +
    2872 self.pool_connection(connect) +2873 self.cursor = self.connection.cursor() +2874 if migrate: +2875 # self.execute('DROP DATABASE %s' % db) +2876 self.execute('CREATE DATABASE IF NOT EXISTS %s' % db) +2877 self.execute('USE %s' % db) +2878 self.execute("SET FOREIGN_KEY_CHECKS=1;") +2879 self.execute("SET sql_mode='NO_BACKSLASH_ESCAPES';") +
    2880 +
    2881 -class NoSQLAdapter(BaseAdapter): +
    2882 +2883 @staticmethod +
    2884 - def to_unicode(obj): +
    2885 if isinstance(obj, str): +2886 return obj.decode('utf8') +2887 elif not isinstance(obj, unicode): +2888 return unicode(obj) +2889 return obj +
    2890 +
    2891 - def represent(self, obj, fieldtype): +
    2892 if isinstance(obj,CALLABLETYPES): +2893 obj = obj() +2894 if isinstance(fieldtype, SQLCustomType): +2895 return fieldtype.encoder(obj) +2896 if isinstance(obj, (Expression, Field)): +2897 raise SyntaxError, "non supported on GAE" +2898 if self.dbengine=='google:datastore' in globals(): +2899 if isinstance(fieldtype, gae.Property): +2900 return obj +2901 if fieldtype.startswith('list:'): +2902 if not obj: +2903 obj = [] +2904 if not isinstance(obj, (list, tuple)): +2905 obj = [obj] +2906 if obj == '' and not fieldtype[:2] in ['st','te','pa','up']: +2907 return None +2908 if obj != None: +2909 if isinstance(obj, list) and not fieldtype.startswith('list'): +2910 obj = [self.represent(o, fieldtype) for o in obj] +2911 elif fieldtype in ('integer','id'): +2912 obj = long(obj) +2913 elif fieldtype == 'double': +2914 obj = float(obj) +2915 elif fieldtype.startswith('reference'): +2916 if isinstance(obj, (Row, Reference)): +2917 obj = obj['id'] +2918 obj = long(obj) +2919 elif fieldtype == 'boolean': +2920 if obj and not str(obj)[0].upper() == 'F': +2921 obj = True +2922 else: +2923 obj = False +2924 elif fieldtype == 'date': +2925 if not isinstance(obj, datetime.date): +2926 (y, m, d) = map(int,str(obj).strip().split('-')) +2927 obj = datetime.date(y, m, d) +2928 elif isinstance(obj,datetime.datetime): +2929 (y, m, d) = (obj.year, obj.month, obj.day) +2930 obj = datetime.date(y, m, d) +2931 elif fieldtype == 'time': +2932 if not isinstance(obj, datetime.time): +2933 time_items = map(int,str(obj).strip().split(':')[:3]) +2934 if len(time_items) == 3: +2935 (h, mi, s) = time_items +2936 else: +2937 (h, mi, s) = time_items + [0] +2938 obj = datetime.time(h, mi, s) +2939 elif fieldtype == 'datetime': +2940 if not isinstance(obj, datetime.datetime): +2941 (y, m, d) = map(int,str(obj)[:10].strip().split('-')) +2942 time_items = map(int,str(obj)[11:].strip().split(':')[:3]) +2943 while len(time_items)<3: +2944 time_items.append(0) +2945 (h, mi, s) = time_items +2946 obj = datetime.datetime(y, m, d, h, mi, s) +2947 elif fieldtype == 'blob': +2948 pass +2949 elif fieldtype.startswith('list:string'): +2950 return map(self.to_unicode,obj) +2951 elif fieldtype.startswith('list:'): +2952 return map(int,obj) +2953 else: +2954 obj = self.to_unicode(obj) +2955 return obj +
    2956 +
    2957 - def _insert(self,table,fields): +
    2958 return 'insert %s in %s' % (fields, table) +
    2959 +
    2960 - def _count(self,query,distinct=None): +
    2961 return 'count %s' % repr(query) +
    2962 +
    2963 - def _select(self,query,fields,attributes): +
    2964 return 'select %s where %s' % (repr(fields), repr(query)) +
    2965 +
    2966 - def _delete(self,tablename, query): +
    2967 return 'delete %s where %s' % (repr(tablename),repr(query)) +
    2968 +
    2969 - def _update(self,tablename,query,fields): +
    2970 return 'update %s (%s) where %s' % (repr(tablename), +2971 repr(fields),repr(query)) +
    2972 +
    2973 - def commit(self): +
    2974 """ +2975 remember: no transactions on many NoSQL +2976 """ +2977 pass +
    2978 +
    2979 - def rollback(self): +
    2980 """ +2981 remember: no transactions on many NoSQL +2982 """ +2983 pass +
    2984 +
    2985 - def close(self): +
    2986 """ +2987 remember: no transactions on many NoSQL +2988 """ +2989 pass +
    2990 +2991 +2992 # these functions should never be called! +
    2993 - def OR(self,first,second): raise SyntaxError, "Not supported" +
    2994 - def AND(self,first,second): raise SyntaxError, "Not supported" +
    2995 - def AS(self,first,second): raise SyntaxError, "Not supported" +
    2996 - def ON(self,first,second): raise SyntaxError, "Not supported" +
    2997 - def STARTSWITH(self,first,second=None): raise SyntaxError, "Not supported" +
    2998 - def ENDSWITH(self,first,second=None): raise SyntaxError, "Not supported" +
    2999 - def ADD(self,first,second): raise SyntaxError, "Not supported" +
    3000 - def SUB(self,first,second): raise SyntaxError, "Not supported" +
    3001 - def MUL(self,first,second): raise SyntaxError, "Not supported" +
    3002 - def DIV(self,first,second): raise SyntaxError, "Not supported" +
    3003 - def LOWER(self,first): raise SyntaxError, "Not supported" +
    3004 - def UPPER(self,first): raise SyntaxError, "Not supported" +
    3005 - def EXTRACT(self,first,what): raise SyntaxError, "Not supported" +
    3006 - def AGGREGATE(self,first,what): raise SyntaxError, "Not supported" +
    3007 - def LEFT_JOIN(self): raise SyntaxError, "Not supported" +
    3008 - def RANDOM(self): raise SyntaxError, "Not supported" +
    3009 - def SUBSTRING(self,field,parameters): raise SyntaxError, "Not supported" +
    3010 - def PRIMARY_KEY(self,key): raise SyntaxError, "Not supported" +
    3011 - def LIKE(self,first,second): raise SyntaxError, "Not supported" +
    3012 - def drop(self,table,mode): raise SyntaxError, "Not supported" +
    3013 - def alias(self,table,alias): raise SyntaxError, "Not supported" +
    3014 - def migrate_table(self,*a,**b): raise SyntaxError, "Not supported" +
    3015 - def distributed_transaction_begin(self,key): raise SyntaxError, "Not supported" +
    3016 - def prepare(self,key): raise SyntaxError, "Not supported" +
    3017 - def commit_prepared(self,key): raise SyntaxError, "Not supported" +
    3018 - def rollback_prepared(self,key): raise SyntaxError, "Not supported" +
    3019 - def concat_add(self,table): raise SyntaxError, "Not supported" +
    3020 - def constraint_name(self, table, fieldname): raise SyntaxError, "Not supported" +
    3021 - def create_sequence_and_triggers(self, query, table, **args): pass +
    3022 - def log_execute(self,*a,**b): raise SyntaxError, "Not supported" +
    3023 - def execute(self,*a,**b): raise SyntaxError, "Not supported" +
    3024 - def represent_exceptions(self, obj, fieldtype): raise SyntaxError, "Not supported" +
    3025 - def lastrowid(self,table): raise SyntaxError, "Not supported" +
    3026 - def integrity_error_class(self): raise SyntaxError, "Not supported" +
    3027 - def rowslice(self,rows,minimum=0,maximum=None): raise SyntaxError, "Not supported" +
    3028 +3029 +
    3030 -class GAEF(object): +
    3031 - def __init__(self,name,op,value,apply): +
    3032 self.name=name=='id' and '__key__' or name +3033 self.op=op +3034 self.value=value +3035 self.apply=apply +
    3036 - def __repr__(self): +
    3037 return '(%s %s %s:%s)' % (self.name, self.op, repr(self.value), type(self.value)) +
    3038 +
    3039 -class GoogleDatastoreAdapter(NoSQLAdapter): +
    3040 uploads_in_blob = True +3041 types = {} +3042 +
    3043 - def file_exists(self, filename): pass +
    3044 - def file_open(self, filename, mode='rb', lock=True): pass +
    3045 - def file_close(self, fileobj, unlock=True): pass +
    3046 +
    3047 - def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8', +3048 credential_decoder=lambda x:x, driver_args={}, +3049 adapter_args={}): +
    3050 self.types.update({ +3051 'boolean': gae.BooleanProperty, +3052 'string': (lambda: gae.StringProperty(multiline=True)), +3053 'text': gae.TextProperty, +3054 'password': gae.StringProperty, +3055 'blob': gae.BlobProperty, +3056 'upload': gae.StringProperty, +3057 'integer': gae.IntegerProperty, +3058 'double': gae.FloatProperty, +3059 'decimal': GAEDecimalProperty, +3060 'date': gae.DateProperty, +3061 'time': gae.TimeProperty, +3062 'datetime': gae.DateTimeProperty, +3063 'id': None, +3064 'reference': gae.IntegerProperty, +3065 'list:string': (lambda: gae.StringListProperty(default=None)), +3066 'list:integer': (lambda: gae.ListProperty(int,default=None)), +3067 'list:reference': (lambda: gae.ListProperty(int,default=None)), +3068 }) +3069 self.db = db +3070 self.uri = uri +3071 self.dbengine = 'google:datastore' +3072 self.folder = folder +3073 db['_lastsql'] = '' +3074 self.db_codec = 'UTF-8' +3075 self.pool_size = 0 +3076 match = re.compile('.*://(?P<namespace>.+)').match(uri) +3077 if match: +3078 namespace_manager.set_namespace(match.group('namespace')) +
    3079 +
    3080 - def create_table(self,table,migrate=True,fake_migrate=False, polymodel=None): +
    3081 myfields = {} +3082 for k in table.fields: +3083 if isinstance(polymodel,Table) and k in polymodel.fields(): +3084 continue +3085 field = table[k] +3086 attr = {} +3087 if isinstance(field.type, SQLCustomType): +3088 ftype = self.types[field.type.native or field.type.type](**attr) +3089 elif isinstance(field.type, gae.Property): +3090 ftype = field.type +3091 elif field.type.startswith('id'): +3092 continue +3093 elif field.type.startswith('decimal'): +3094 precision, scale = field.type[7:].strip('()').split(',') +3095 precision = int(precision) +3096 scale = int(scale) +3097 ftype = GAEDecimalProperty(precision, scale, **attr) +3098 elif field.type.startswith('reference'): +3099 if field.notnull: +3100 attr = dict(required=True) +3101 referenced = field.type[10:].strip() +3102 ftype = self.types[field.type[:9]](table._db[referenced]) +3103 elif field.type.startswith('list:reference'): +3104 if field.notnull: +3105 attr = dict(required=True) +3106 referenced = field.type[15:].strip() +3107 ftype = self.types[field.type[:14]](**attr) +3108 elif field.type.startswith('list:'): +3109 ftype = self.types[field.type](**attr) +3110 elif not field.type in self.types\ +3111 or not self.types[field.type]: +3112 raise SyntaxError, 'Field: unknown field type: %s' % field.type +3113 else: +3114 ftype = self.types[field.type](**attr) +3115 myfields[field.name] = ftype +3116 if not polymodel: +3117 table._tableobj = classobj(table._tablename, (gae.Model, ), myfields) +3118 elif polymodel==True: +3119 table._tableobj = classobj(table._tablename, (PolyModel, ), myfields) +3120 elif isinstance(polymodel,Table): +3121 table._tableobj = classobj(table._tablename, (polymodel._tableobj, ), myfields) +3122 else: +3123 raise SyntaxError, "polymodel must be None, True, a table or a tablename" +3124 return None +
    3125 +
    3126 - def expand(self,expression,field_type=None): +
    3127 if isinstance(expression,Field): +3128 if expression.type in ('text','blob'): +3129 raise SyntaxError, 'AppEngine does not index by: %s' % expression.type +3130 return expression.name +3131 elif isinstance(expression, (Expression, Query)): +3132 if not expression.second is None: +3133 return expression.op(expression.first, expression.second) +3134 elif not expression.first is None: +3135 return expression.op(expression.first) +3136 else: +3137 return expression.op() +3138 elif field_type: +3139 return self.represent(expression,field_type) +3140 elif isinstance(expression,(list,tuple)): +3141 return ','.join([self.represent(item,field_type) for item in expression]) +3142 else: +3143 return str(expression) +
    3144 +3145 ### TODO from gql.py Expression +
    3146 - def AND(self,first,second): +
    3147 a = self.expand(first) +3148 b = self.expand(second) +3149 if b[0].name=='__key__' and a[0].name!='__key__': +3150 return b+a +3151 return a+b +
    3152 +
    3153 - def EQ(self,first,second=None): +
    3154 if isinstance(second, Key): +3155 return [GAEF(first.name,'=',second,lambda a,b:a==b)] +3156 return [GAEF(first.name,'=',self.represent(second,first.type),lambda a,b:a==b)] +
    3157 +
    3158 - def NE(self,first,second=None): +
    3159 if first.type != 'id': +3160 return [GAEF(first.name,'!=',self.represent(second,first.type),lambda a,b:a!=b)] +3161 else: +3162 second = Key.from_path(first._tablename, long(second)) +3163 return [GAEF(first.name,'!=',second,lambda a,b:a!=b)] +
    3164 +
    3165 - def LT(self,first,second=None): +
    3166 if first.type != 'id': +3167 return [GAEF(first.name,'<',self.represent(second,first.type),lambda a,b:a<b)] +3168 else: +3169 second = Key.from_path(first._tablename, long(second)) +3170 return [GAEF(first.name,'<',second,lambda a,b:a<b)] +
    3171 +
    3172 - def LE(self,first,second=None): +
    3173 if first.type != 'id': +3174 return [GAEF(first.name,'<=',self.represent(second,first.type),lambda a,b:a<=b)] +3175 else: +3176 second = Key.from_path(first._tablename, long(second)) +3177 return [GAEF(first.name,'<=',second,lambda a,b:a<=b)] +
    3178 +
    3179 - def GT(self,first,second=None): +
    3180 if first.type != 'id' or second==0 or second == '0': +3181 return [GAEF(first.name,'>',self.represent(second,first.type),lambda a,b:a>b)] +3182 else: +3183 second = Key.from_path(first._tablename, long(second)) +3184 return [GAEF(first.name,'>',second,lambda a,b:a>b)] +
    3185 +
    3186 - def GE(self,first,second=None): +
    3187 if first.type != 'id': +3188 return [GAEF(first.name,'>=',self.represent(second,first.type),lambda a,b:a>=b)] +3189 else: +3190 second = Key.from_path(first._tablename, long(second)) +3191 return [GAEF(first.name,'>=',second,lambda a,b:a>=b)] +
    3192 +
    3193 - def INVERT(self,first): +
    3194 return '-%s' % first.name +
    3195 +
    3196 - def COMMA(self,first,second): +
    3197 return '%s, %s' % (self.expand(first),self.expand(second)) +
    3198 +
    3199 - def BELONGS(self,first,second=None): +
    3200 if not isinstance(second,(list, tuple)): +3201 raise SyntaxError, "Not supported" +3202 if first.type != 'id': +3203 return [GAEF(first.name,'in',self.represent(second,first.type),lambda a,b:a in b)] +3204 else: +3205 second = [Key.from_path(first._tablename, i) for i in second] +3206 return [GAEF(first.name,'in',second,lambda a,b:a in b)] +
    3207 +
    3208 - def CONTAINS(self,first,second): +
    3209 if not first.type.startswith('list:'): +3210 raise SyntaxError, "Not supported" +3211 return [GAEF(first.name,'=',self.expand(second,first.type[5:]),lambda a,b:a in b)] +
    3212 +
    3213 - def NOT(self,first): +
    3214 nops = { self.EQ: self.NE, +3215 self.NE: self.EQ, +3216 self.LT: self.GE, +3217 self.GT: self.LE, +3218 self.LE: self.GT, +3219 self.GE: self.LT} +3220 if not isinstance(first,Query): +3221 raise SyntaxError, "Not suported" +3222 nop = nops.get(first.op,None) +3223 if not nop: +3224 raise SyntaxError, "Not suported %s" % first.op.__name__ +3225 first.op = nop +3226 return self.expand(first) +
    3227 +
    3228 - def truncate(self,table,mode): +
    3229 self.db(table._id > 0).delete() +
    3230 +
    3231 - def select_raw(self,query,fields=[],attributes={}): +
    3232 new_fields = [] +3233 for item in fields: +3234 if isinstance(item,SQLALL): +3235 new_fields += item.table +3236 else: +3237 new_fields.append(item) +3238 fields = new_fields +3239 if query: +3240 tablename = self.get_table(query) +3241 elif fields: +3242 tablename = fields[0].tablename +3243 query = fields[0].table._id>0 +3244 else: +3245 raise SyntaxError, "Unable to determine a tablename" +3246 query = self.filter_tenant(query,[tablename]) +3247 tableobj = self.db[tablename]._tableobj +3248 items = tableobj.all() +3249 filters = self.expand(query) +3250 for filter in filters: +3251 if filter.name=='__key__' and filter.op=='>' and filter.value==0: +3252 continue +3253 elif filter.name=='__key__' and filter.op=='=': +3254 if filter.value==0: +3255 items = [] +3256 elif isinstance(filter.value, Key): +3257 item = tableobj.get(filter.value) +3258 items = (item and [item]) or [] +3259 else: +3260 item = tableobj.get_by_id(filter.value) +3261 items = (item and [item]) or [] +3262 elif isinstance(items,list): # i.e. there is a single record! +3263 items = [i for i in items if filter.apply(getattr(item,filter.name), +3264 filter.value)] +3265 else: +3266 if filter.name=='__key__': items.order('__key__') +3267 items = items.filter('%s %s' % (filter.name,filter.op),filter.value) +3268 if not isinstance(items,list): +3269 if attributes.get('left', None): +3270 raise SyntaxError, 'Set: no left join in appengine' +3271 if attributes.get('groupby', None): +3272 raise SyntaxError, 'Set: no groupby in appengine' +3273 orderby = attributes.get('orderby', False) +3274 if orderby: +3275 ### THIS REALLY NEEDS IMPROVEMENT !!! +3276 if isinstance(orderby, (list, tuple)): +3277 orderby = xorify(orderby) +3278 if isinstance(orderby,Expression): +3279 orderby = self.expand(orderby) +3280 orders = orderby.split(', ') +3281 for order in orders: +3282 order={'-id':'-__key__','id':'__key__'}.get(order,order) +3283 items = items.order(order) +3284 if attributes.get('limitby', None): +3285 (lmin, lmax) = attributes['limitby'] +3286 (limit, offset) = (lmax - lmin, lmin) +3287 items = items.fetch(limit, offset=offset) +3288 fields = self.db[tablename].fields +3289 return (items, tablename, fields) +
    3290 +
    3291 - def select(self,query,fields,attributes): +
    3292 (items, tablename, fields) = self.select_raw(query,fields,attributes) +3293 # self.db['_lastsql'] = self._select(query,fields,attributes) +3294 rows = [ +3295 [t=='id' and int(item.key().id()) or getattr(item, t) for t in fields] +3296 for item in items] +3297 colnames = ['%s.%s' % (tablename, t) for t in fields] +3298 return self.parse(rows, colnames, False) +
    3299 +3300 +
    3301 - def count(self,query,distinct=None): +
    3302 if distinct: +3303 raise RuntimeError, "COUNT DISTINCT not supported" +3304 (items, tablename, fields) = self.select_raw(query) +3305 # self.db['_lastsql'] = self._count(query) +3306 try: +3307 return len(items) +3308 except TypeError: +3309 return items.count(limit=None) +
    3310 +
    3311 - def delete(self,tablename, query): +
    3312 """ +3313 This function was changed on 2010-05-04 because according to +3314 http://code.google.com/p/googleappengine/issues/detail?id=3119 +3315 GAE no longer support deleting more than 1000 records. +3316 """ +3317 # self.db['_lastsql'] = self._delete(tablename,query) +3318 (items, tablename, fields) = self.select_raw(query) +3319 # items can be one item or a query +3320 if not isinstance(items,list): +3321 counter = items.count(limit=None) +3322 leftitems = items.fetch(1000) +3323 while len(leftitems): +3324 gae.delete(leftitems) +3325 leftitems = items.fetch(1000) +3326 else: +3327 counter = len(items) +3328 gae.delete(items) +3329 return counter +
    3330 +
    3331 - def update(self,tablename,query,update_fields): +
    3332 # self.db['_lastsql'] = self._update(tablename,query,update_fields) +3333 (items, tablename, fields) = self.select_raw(query) +3334 counter = 0 +3335 for item in items: +3336 for field, value in update_fields: +3337 setattr(item, field.name, self.represent(value,field.type)) +3338 item.put() +3339 counter += 1 +3340 logger.info(str(counter)) +3341 return counter +
    3342 +
    3343 - def insert(self,table,fields): +
    3344 dfields=dict((f.name,self.represent(v,f.type)) for f,v in fields) +3345 # table._db['_lastsql'] = self._insert(table,fields) +3346 tmp = table._tableobj(**dfields) +3347 tmp.put() +3348 rid = Reference(tmp.key().id()) +3349 (rid._table, rid._record) = (table, None) +3350 return rid +
    3351 +
    3352 - def bulk_insert(self,table,items): +
    3353 parsed_items = [] +3354 for item in items: +3355 dfields=dict((f.name,self.represent(v,f.type)) for f,v in item) +3356 parsed_items.append(table._tableobj(**dfields)) +3357 gae.put(parsed_items) +3358 return True +
    3359 +
    3360 -def uuid2int(uuidv): +
    3361 return uuid.UUID(uuidv).int +
    3362 +
    3363 -def int2uuid(n): +
    3364 return str(uuid.UUID(int=n)) +
    3365 +
    3366 -class CouchDBAdapter(NoSQLAdapter): +
    3367 uploads_in_blob = True +3368 types = { +3369 'boolean': bool, +3370 'string': str, +3371 'text': str, +3372 'password': str, +3373 'blob': str, +3374 'upload': str, +3375 'integer': long, +3376 'double': float, +3377 'date': datetime.date, +3378 'time': datetime.time, +3379 'datetime': datetime.datetime, +3380 'id': long, +3381 'reference': long, +3382 'list:string': list, +3383 'list:integer': list, +3384 'list:reference': list, +3385 } +3386 +
    3387 - def file_exists(self, filename): pass +
    3388 - def file_open(self, filename, mode='rb', lock=True): pass +
    3389 - def file_close(self, fileobj, unlock=True): pass +
    3390 +
    3391 - def expand(self,expression,field_type=None): +
    3392 if isinstance(expression,Field): +3393 if expression.type=='id': +3394 return "%s._id" % expression.tablename +3395 return BaseAdapter.expand(self,expression,field_type) +
    3396 +
    3397 - def AND(self,first,second): +
    3398 return '(%s && %s)' % (self.expand(first),self.expand(second)) +
    3399 +
    3400 - def OR(self,first,second): +
    3401 return '(%s || %s)' % (self.expand(first),self.expand(second)) +
    3402 +
    3403 - def EQ(self,first,second): +
    3404 if second is None: +3405 return '(%s == null)' % self.expand(first) +3406 return '(%s == %s)' % (self.expand(first),self.expand(second,first.type)) +
    3407 +
    3408 - def NE(self,first,second): +
    3409 if second is None: +3410 return '(%s != null)' % self.expand(first) +3411 return '(%s != %s)' % (self.expand(first),self.expand(second,first.type)) +
    3412 +
    3413 - def COMMA(self,first,second): +
    3414 return '%s + %s' % (self.expand(first),self.expand(second)) +
    3415 +
    3416 - def represent(self, obj, fieldtype): +
    3417 value = NoSQLAdapter.represent(self, obj, fieldtype) +3418 if fieldtype=='id': +3419 return repr(str(int(value))) +3420 return repr(not isinstance(value,unicode) and value or value.encode('utf8')) +
    3421 +
    3422 - def __init__(self,db,uri='couchdb://127.0.0.1:5984', +3423 pool_size=0,folder=None,db_codec ='UTF-8', +3424 credential_decoder=lambda x:x, driver_args={}, +3425 adapter_args={}): +
    3426 self.db = db +3427 self.uri = uri +3428 self.dbengine = 'couchdb' +3429 self.folder = folder +3430 db['_lastsql'] = '' +3431 self.db_codec = 'UTF-8' +3432 self.pool_size = pool_size +3433 +3434 url='http://'+uri[10:] +3435 def connect(url=url,driver_args=driver_args): +3436 return couchdb.Server(url,**driver_args) +
    3437 self.pool_connection(connect) +
    3438 +
    3439 - def create_table(self, table, migrate=True, fake_migrate=False, polymodel=None): +
    3440 if migrate: +3441 try: +3442 self.connection.create(table._tablename) +3443 except: +3444 pass +
    3445 +
    3446 - def insert(self,table,fields): +
    3447 id = uuid2int(web2py_uuid()) +3448 ctable = self.connection[table._tablename] +3449 values = dict((k.name,NoSQLAdapter.represent(self,v,k.type)) for k,v in fields) +3450 values['_id'] = str(id) +3451 ctable.save(values) +3452 return id +
    3453 +
    3454 - def _select(self,query,fields,attributes): +
    3455 if not isinstance(query,Query): +3456 raise SyntaxError, "Not Supported" +3457 for key in set(attributes.keys())-set(('orderby','groupby','limitby', +3458 'required','cache','left', +3459 'distinct','having')): +3460 raise SyntaxError, 'invalid select attribute: %s' % key +3461 new_fields=[] +3462 for item in fields: +3463 if isinstance(item,SQLALL): +3464 new_fields += item.table +3465 else: +3466 new_fields.append(item) +3467 def uid(fd): +3468 return fd=='id' and '_id' or fd +
    3469 def get(row,fd): +3470 return fd=='id' and int(row['_id']) or row.get(fd,None) +3471 fields = new_fields +3472 tablename = self.get_table(query) +3473 fieldnames = [f.name for f in (fields or self.db[tablename])] +3474 colnames = ['%s.%s' % (tablename,k) for k in fieldnames] +3475 fields = ','.join(['%s.%s' % (tablename,uid(f)) for f in fieldnames]) +3476 fn="function(%(t)s){if(%(query)s)emit(%(order)s,[%(fields)s]);}" %\ +3477 dict(t=tablename, +3478 query=self.expand(query), +3479 order='%s._id' % tablename, +3480 fields=fields) +3481 return fn, colnames +3482 +
    3483 - def select(self,query,fields,attributes): +
    3484 if not isinstance(query,Query): +3485 raise SyntaxError, "Not Supported" +3486 fn, colnames = self._select(query,fields,attributes) +3487 tablename = colnames[0].split('.')[0] +3488 ctable = self.connection[tablename] +3489 rows = [cols['value'] for cols in ctable.query(fn)] +3490 return self.parse(rows, colnames, False) +
    3491 +
    3492 - def delete(self,tablename,query): +
    3493 if not isinstance(query,Query): +3494 raise SyntaxError, "Not Supported" +3495 if query.first.type=='id' and query.op==self.EQ: +3496 id = query.second +3497 tablename = query.first.tablename +3498 assert(tablename == query.first.tablename) +3499 ctable = self.connection[tablename] +3500 try: +3501 del ctable[str(id)] +3502 return 1 +3503 except couchdb.http.ResourceNotFound: +3504 return 0 +3505 else: +3506 tablename = self.get_table(query) +3507 rows = self.select(query,[self.db[tablename]._id],{}) +3508 ctable = self.connection[tablename] +3509 for row in rows: +3510 del ctable[str(row.id)] +3511 return len(rows) +
    3512 +
    3513 - def update(self,tablename,query,fields): +
    3514 if not isinstance(query,Query): +3515 raise SyntaxError, "Not Supported" +3516 if query.first.type=='id' and query.op==self.EQ: +3517 id = query.second +3518 tablename = query.first.tablename +3519 ctable = self.connection[tablename] +3520 try: +3521 doc = ctable[str(id)] +3522 for key,value in fields: +3523 doc[key.name] = NoSQLAdapter.represent(self,value,self.db[tablename][key.name].type) +3524 ctable.save(doc) +3525 return 1 +3526 except couchdb.http.ResourceNotFound: +3527 return 0 +3528 else: +3529 tablename = self.get_table(query) +3530 rows = self.select(query,[self.db[tablename]._id],{}) +3531 ctable = self.connection[tablename] +3532 table = self.db[tablename] +3533 for row in rows: +3534 doc = ctable[str(row.id)] +3535 for key,value in fields: +3536 doc[key.name] = NoSQLAdapter.represent(self,value,table[key.name].type) +3537 ctable.save(doc) +3538 return len(rows) +
    3539 +
    3540 - def count(self,query,distinct=None): +
    3541 if distinct: +3542 raise RuntimeError, "COUNT DISTINCT not supported" +3543 if not isinstance(query,Query): +3544 raise SyntaxError, "Not Supported" +3545 tablename = self.get_table(query) +3546 rows = self.select(query,[self.db[tablename]._id],{}) +3547 return len(rows) +
    3548 +
    3549 -def cleanup(text): +
    3550 """ +3551 validates that the given text is clean: only contains [0-9a-zA-Z_] +3552 """ +3553 +3554 if re.compile('[^0-9a-zA-Z_]').findall(text): +3555 raise SyntaxError, \ +3556 'only [0-9a-zA-Z_] allowed in table and field names, received %s' \ +3557 % text +3558 return text +
    3559 +3560 +
    3561 -class MongoDBAdapter(NoSQLAdapter): +
    3562 uploads_in_blob = True +3563 types = { +3564 'boolean': bool, +3565 'string': str, +3566 'text': str, +3567 'password': str, +3568 'blob': str, +3569 'upload': str, +3570 'integer': long, +3571 'double': float, +3572 'date': datetime.date, +3573 'time': datetime.time, +3574 'datetime': datetime.datetime, +3575 'id': long, +3576 'reference': long, +3577 'list:string': list, +3578 'list:integer': list, +3579 'list:reference': list, +3580 } +3581 +
    3582 - def __init__(self,db,uri='mongodb://127.0.0.1:5984/db', +3583 pool_size=0,folder=None,db_codec ='UTF-8', +3584 credential_decoder=lambda x:x, driver_args={}, +3585 adapter_args={}): +
    3586 self.db = db +3587 self.uri = uri +3588 self.dbengine = 'mongodb' +3589 self.folder = folder +3590 db['_lastsql'] = '' +3591 self.db_codec = 'UTF-8' +3592 self.pool_size = pool_size +3593 +3594 m = re.compile('^(?P<host>[^\:/]+)(\:(?P<port>[0-9]+))?/(?P<db>.+)$').match(self._uri[10:]) +3595 if not m: +3596 raise SyntaxError, "Invalid URI string in DAL: %s" % self._uri +3597 host = m.group('host') +3598 if not host: +3599 raise SyntaxError, 'mongodb: host name required' +3600 dbname = m.group('db') +3601 if not dbname: +3602 raise SyntaxError, 'mongodb: db name required' +3603 port = m.group('port') or 27017 +3604 driver_args.update(dict(host=host,port=port)) +3605 def connect(dbname=dbname,driver_args=driver_args): +3606 return pymongo.Connection(**driver_args)[dbname] +
    3607 self.pool_connection(connect) +
    3608 +
    3609 - def insert(self,table,fields): +
    3610 ctable = self.connection[table._tablename] +3611 values = dict((k,self.represent(v,table[k].type)) for k,v in fields) +3612 ctable.insert(values) +3613 return uuid2int(id) +
    3614 +3615 +
    3616 - def count(self,query): +
    3617 raise RuntimeError, "Not implemented" +
    3618 +
    3619 - def select(self,query,fields,attributes): +
    3620 raise RuntimeError, "Not implemented" +
    3621 +
    3622 - def delete(self,tablename, query): +
    3623 raise RuntimeError, "Not implemented" +
    3624 +
    3625 - def update(self,tablename,query,fields): +
    3626 raise RuntimeError, "Not implemented" +
    3627 +3628 +3629 ######################################################################## +3630 # end of adapters +3631 ######################################################################## +3632 +3633 ADAPTERS = { +3634 'sqlite': SQLiteAdapter, +3635 'sqlite:memory': SQLiteAdapter, +3636 'mysql': MySQLAdapter, +3637 'postgres': PostgreSQLAdapter, +3638 'oracle': OracleAdapter, +3639 'mssql': MSSQLAdapter, +3640 'mssql2': MSSQL2Adapter, +3641 'db2': DB2Adapter, +3642 'teradata': TeradataAdapter, +3643 'informix': InformixAdapter, +3644 'firebird': FireBirdAdapter, +3645 'firebird_embedded': FireBirdAdapter, +3646 'ingres': IngresAdapter, +3647 'ingresu': IngresUnicodeAdapter, +3648 'sapdb': SAPDBAdapter, +3649 'cubrid': CubridAdapter, +3650 'jdbc:sqlite': JDBCSQLiteAdapter, +3651 'jdbc:sqlite:memory': JDBCSQLiteAdapter, +3652 'jdbc:postgres': JDBCPostgreSQLAdapter, +3653 'gae': GoogleDatastoreAdapter, # discouraged, for backward compatibility +3654 'google:datastore': GoogleDatastoreAdapter, +3655 'google:sql': GoogleSQLAdapter, +3656 'couchdb': CouchDBAdapter, +3657 'mongodb': MongoDBAdapter, +3658 } +3659 +3660 +
    3661 -def sqlhtml_validators(field): +
    3662 """ +3663 Field type validation, using web2py's validators mechanism. +3664 +3665 makes sure the content of a field is in line with the declared +3666 fieldtype +3667 """ +3668 if not have_validators: +3669 return [] +3670 field_type, field_length = field.type, field.length +3671 if isinstance(field_type, SQLCustomType): +3672 if hasattr(field_type, 'validator'): +3673 return field_type.validator +3674 else: +3675 field_type = field_type.type +3676 elif not isinstance(field_type,str): +3677 return [] +3678 requires=[] +3679 def ff(r,id): +3680 row=r(id) +3681 if not row: +3682 return id +3683 elif hasattr(r, '_format') and isinstance(r._format,str): +3684 return r._format % row +3685 elif hasattr(r, '_format') and callable(r._format): +3686 return r._format(row) +3687 else: +3688 return id +
    3689 if field_type == 'string': +3690 requires.append(validators.IS_LENGTH(field_length)) +3691 elif field_type == 'text': +3692 requires.append(validators.IS_LENGTH(field_length)) +3693 elif field_type == 'password': +3694 requires.append(validators.IS_LENGTH(field_length)) +3695 elif field_type == 'double': +3696 requires.append(validators.IS_FLOAT_IN_RANGE(-1e100, 1e100)) +3697 elif field_type == 'integer': +3698 requires.append(validators.IS_INT_IN_RANGE(-1e100, 1e100)) +3699 elif field_type.startswith('decimal'): +3700 requires.append(validators.IS_DECIMAL_IN_RANGE(-10**10, 10**10)) +3701 elif field_type == 'date': +3702 requires.append(validators.IS_DATE()) +3703 elif field_type == 'time': +3704 requires.append(validators.IS_TIME()) +3705 elif field_type == 'datetime': +3706 requires.append(validators.IS_DATETIME()) +3707 elif field.db and field_type.startswith('reference') and \ +3708 field_type.find('.') < 0 and \ +3709 field_type[10:] in field.db.tables: +3710 referenced = field.db[field_type[10:]] +3711 def repr_ref(id, r=referenced, f=ff): return f(r, id) +3712 field.represent = field.represent or repr_ref +3713 if hasattr(referenced, '_format') and referenced._format: +3714 requires = validators.IS_IN_DB(field.db,referenced._id, +3715 referenced._format) +3716 if field.unique: +3717 requires._and = validators.IS_NOT_IN_DB(field.db,field) +3718 if field.tablename == field_type[10:]: +3719 return validators.IS_EMPTY_OR(requires) +3720 return requires +3721 elif field.db and field_type.startswith('list:reference') and \ +3722 field_type.find('.') < 0 and \ +3723 field_type[15:] in field.db.tables: +3724 referenced = field.db[field_type[15:]] +3725 def list_ref_repr(ids, r=referenced, f=ff): +3726 if not ids: +3727 return None +3728 refs = r._db(r._id.belongs(ids)).select(r._id) +3729 return (refs and ', '.join(str(f(r,ref.id)) for ref in refs) or '') +3730 field.represent = field.represent or list_ref_repr +3731 if hasattr(referenced, '_format') and referenced._format: +3732 requires = validators.IS_IN_DB(field.db,referenced._id, +3733 referenced._format,multiple=True) +3734 else: +3735 requires = validators.IS_IN_DB(field.db,referenced._id, +3736 multiple=True) +3737 if field.unique: +3738 requires._and = validators.IS_NOT_IN_DB(field.db,field) +3739 return requires +3740 elif field_type.startswith('list:'): +3741 def repr_list(values): return', '.join(str(v) for v in (values or [])) +3742 field.represent = field.represent or repr_list +3743 if field.unique: +3744 requires.insert(0,validators.IS_NOT_IN_DB(field.db,field)) +3745 sff = ['in', 'do', 'da', 'ti', 'de', 'bo'] +3746 if field.notnull and not field_type[:2] in sff: +3747 requires.insert(0, validators.IS_NOT_EMPTY()) +3748 elif not field.notnull and field_type[:2] in sff and requires: +3749 requires[-1] = validators.IS_EMPTY_OR(requires[-1]) +3750 return requires +3751 +3752 +
    3753 -def bar_escape(item): +
    3754 return str(item).replace('|', '||') +
    3755 +
    3756 -def bar_encode(items): +
    3757 return '|%s|' % '|'.join(bar_escape(item) for item in items if str(item).strip()) +
    3758 +
    3759 -def bar_decode_integer(value): +
    3760 return [int(x) for x in value.split('|') if x.strip()] +
    3761 +
    3762 -def bar_decode_string(value): +
    3763 return [x.replace('||', '|') for x in string_unpack.split(value[1:-1]) if x.strip()] +
    3764 +3765 +
    3766 -class Row(dict): +
    3767 +3768 """ +3769 a dictionary that lets you do d['a'] as well as d.a +3770 this is only used to store a Row +3771 """ +3772 +
    3773 - def __getitem__(self, key): +
    3774 key=str(key) +3775 if key in self.get('_extra',{}): +3776 return self._extra[key] +3777 return dict.__getitem__(self, key) +
    3778 +
    3779 - def __call__(self,key): +
    3780 return self.__getitem__(key) +
    3781 +
    3782 - def __setitem__(self, key, value): +
    3783 dict.__setitem__(self, str(key), value) +
    3784 +
    3785 - def __getattr__(self, key): +
    3786 return self[key] +
    3787 +
    3788 - def __setattr__(self, key, value): +
    3789 self[key] = value +
    3790 +
    3791 - def __repr__(self): +
    3792 return '<Row ' + dict.__repr__(self) + '>' +
    3793 +
    3794 - def __int__(self): +
    3795 return dict.__getitem__(self,'id') +
    3796 +
    3797 - def __eq__(self,other): +
    3798 try: +3799 return self.as_dict() == other.as_dict() +3800 except AttributeError: +3801 return False +
    3802 +
    3803 - def __ne__(self,other): +
    3804 return not (self == other) +
    3805 +
    3806 - def __copy__(self): +
    3807 return Row(dict(self)) +
    3808 +
    3809 - def as_dict(self,datetime_to_str=False): +
    3810 SERIALIZABLE_TYPES = (str,unicode,int,long,float,bool,list) +3811 d = dict(self) +3812 for k in copy.copy(d.keys()): +3813 v=d[k] +3814 if d[k] is None: +3815 continue +3816 elif isinstance(v,Row): +3817 d[k]=v.as_dict() +3818 elif isinstance(v,Reference): +3819 d[k]=int(v) +3820 elif isinstance(v,decimal.Decimal): +3821 d[k]=float(v) +3822 elif isinstance(v, (datetime.date, datetime.datetime, datetime.time)): +3823 if datetime_to_str: +3824 d[k] = v.isoformat().replace('T',' ')[:19] +3825 elif not isinstance(v,SERIALIZABLE_TYPES): +3826 del d[k] +3827 return d +
    3828 +3829 +
    3830 -def Row_unpickler(data): +
    3831 return Row(cPickle.loads(data)) +
    3832 +
    3833 -def Row_pickler(data): +
    3834 return Row_unpickler, (cPickle.dumps(data.as_dict(datetime_to_str=False)),) +
    3835 +3836 copy_reg.pickle(Row, Row_pickler, Row_unpickler) +3837 +3838 +3839 ################################################################################ +3840 # Everything below should be independent on the specifics of the +3841 # database and should for RDBMs and some NoSQL databases +3842 ################################################################################ +3843 +
    3844 -class SQLCallableList(list): +
    3845 - def __call__(self): +
    3846 return copy.copy(self) +
    3847 +3848 +
    3849 -class DAL(dict): +
    3850 +3851 """ +3852 an instance of this class represents a database connection +3853 +3854 Example:: +3855 +3856 db = DAL('sqlite://test.db') +3857 db.define_table('tablename', Field('fieldname1'), +3858 Field('fieldname2')) +3859 """ +3860 +3861 @staticmethod +
    3862 - def set_folder(folder): +
    3863 """ +3864 # ## this allows gluon to set a folder for this thread +3865 # ## <<<<<<<<< Should go away as new DAL replaces old sql.py +3866 """ +3867 BaseAdapter.set_folder(folder) +
    3868 +3869 @staticmethod +
    3870 - def distributed_transaction_begin(*instances): +
    3871 if not instances: +3872 return +3873 thread_key = '%s.%s' % (socket.gethostname(), threading.currentThread()) +3874 keys = ['%s.%i' % (thread_key, i) for (i,db) in instances] +3875 instances = enumerate(instances) +3876 for (i, db) in instances: +3877 if not db._adapter.support_distributed_transaction(): +3878 raise SyntaxError, \ +3879 'distributed transaction not suported by %s' % db._dbname +3880 for (i, db) in instances: +3881 db._adapter.distributed_transaction_begin(keys[i]) +
    3882 +3883 @staticmethod +
    3884 - def distributed_transaction_commit(*instances): +
    3885 if not instances: +3886 return +3887 instances = enumerate(instances) +3888 thread_key = '%s.%s' % (socket.gethostname(), threading.currentThread()) +3889 keys = ['%s.%i' % (thread_key, i) for (i,db) in instances] +3890 for (i, db) in instances: +3891 if not db._adapter.support_distributed_transaction(): +3892 raise SyntaxError, \ +3893 'distributed transaction not suported by %s' % db._dbanme +3894 try: +3895 for (i, db) in instances: +3896 db._adapter.prepare(keys[i]) +3897 except: +3898 for (i, db) in instances: +3899 db._adapter.rollback_prepared(keys[i]) +3900 raise RuntimeError, 'failure to commit distributed transaction' +3901 else: +3902 for (i, db) in instances: +3903 db._adapter.commit_prepared(keys[i]) +3904 return +
    3905 +3906 +
    3907 - def __init__(self, uri='sqlite://dummy.db', pool_size=0, folder=None, +3908 db_codec='UTF-8', check_reserved=None, +3909 migrate=True, fake_migrate=False, +3910 migrate_enabled=True, fake_migrate_all=False, +3911 decode_credentials=False, driver_args=None, +3912 adapter_args={}, attempts=5, auto_import=False): +
    3913 """ +3914 Creates a new Database Abstraction Layer instance. +3915 +3916 Keyword arguments: +3917 +3918 :uri: string that contains information for connecting to a database. +3919 (default: 'sqlite://dummy.db') +3920 :pool_size: How many open connections to make to the database object. +3921 :folder: <please update me> +3922 :db_codec: string encoding of the database (default: 'UTF-8') +3923 :check_reserved: list of adapters to check tablenames and column names +3924 against sql reserved keywords. (Default None) +3925 +3926 * 'common' List of sql keywords that are common to all database types +3927 such as "SELECT, INSERT". (recommended) +3928 * 'all' Checks against all known SQL keywords. (not recommended) +3929 <adaptername> Checks against the specific adapters list of keywords +3930 (recommended) +3931 * '<adaptername>_nonreserved' Checks against the specific adapters +3932 list of nonreserved keywords. (if available) +3933 :migrate (defaults to True) sets default migrate behavior for all tables +3934 :fake_migrate (defaults to False) sets default fake_migrate behavior for all tables +3935 :migrate_enabled (defaults to True). If set to False disables ALL migrations +3936 :fake_migrate_all (defaults to False). If sets to True fake migrates ALL tables +3937 :attempts (defaults to 5). Number of times to attempt connecting +3938 """ +3939 if not decode_credentials: +3940 credential_decoder = lambda cred: cred +3941 else: +3942 credential_decoder = lambda cred: urllib.unquote(cred) +3943 if folder: +3944 self.set_folder(folder) +3945 self._uri = uri +3946 self._pool_size = pool_size +3947 self._db_codec = db_codec +3948 self._lastsql = '' +3949 self._timings = [] +3950 self._pending_references = {} +3951 self._request_tenant = 'request_tenant' +3952 self._common_fields = [] +3953 if not str(attempts).isdigit() or attempts < 0: +3954 attempts = 5 +3955 if uri: +3956 uris = isinstance(uri,(list,tuple)) and uri or [uri] +3957 error = '' +3958 connected = False +3959 for k in range(attempts): +3960 for uri in uris: +3961 try: +3962 if is_jdbc and not uri.startswith('jdbc:'): +3963 uri = 'jdbc:'+uri +3964 self._dbname = regex_dbname.match(uri).group() +3965 if not self._dbname in ADAPTERS: +3966 raise SyntaxError, "Error in URI '%s' or database not supported" % self._dbname +3967 # notice that driver args or {} else driver_args defaults to {} global, not correct +3968 args = (self,uri,pool_size,folder,db_codec,credential_decoder,driver_args or {}, adapter_args) +3969 self._adapter = ADAPTERS[self._dbname](*args) +3970 connected = True +3971 break +3972 except SyntaxError: +3973 raise +3974 except Exception, error: +3975 sys.stderr.write('DEBUG_c: Exception %r' % ((Exception, error,),)) +3976 if connected: +3977 break +3978 else: +3979 time.sleep(1) +3980 if not connected: +3981 raise RuntimeError, "Failure to connect, tried %d times:\n%s" % (attempts, error) +3982 else: +3983 args = (self,'None',0,folder,db_codec) +3984 self._adapter = BaseAdapter(*args) +3985 migrate = fake_migrate = False +3986 adapter = self._adapter +3987 self._uri_hash = hashlib.md5(adapter.uri).hexdigest() +3988 self.tables = SQLCallableList() +3989 self.check_reserved = check_reserved +3990 if self.check_reserved: +3991 from reserved_sql_keywords import ADAPTERS as RSK +3992 self.RSK = RSK +3993 self._migrate = migrate +3994 self._fake_migrate = fake_migrate +3995 self._migrate_enabled = migrate_enabled +3996 self._fake_migrate_all = fake_migrate_all +3997 if auto_import: +3998 self.import_table_definitions(adapter.folder) +
    3999 +
    4000 - def import_table_definitions(self,path,migrate=False,fake_migrate=False): +
    4001 pattern = os.path.join(path,self._uri_hash+'_*.table') +4002 for filename in glob.glob(pattern): +4003 tfile = self._adapter.file_open(filename, 'r') +4004 try: +4005 sql_fields = cPickle.load(tfile) +4006 name = filename[len(pattern)-7:-6] +4007 mf = [(value['sortable'],Field(key,type=value['type'])) \ +4008 for key, value in sql_fields.items()] +4009 mf.sort(lambda a,b: cmp(a[0],b[0])) +4010 self.define_table(name,*[item[1] for item in mf], +4011 **dict(migrate=migrate,fake_migrate=fake_migrate)) +4012 finally: +4013 self._adapter.file_close(tfile) +
    4014 +
    4015 - def check_reserved_keyword(self, name): +
    4016 """ +4017 Validates ``name`` against SQL keywords +4018 Uses self.check_reserve which is a list of +4019 operators to use. +4020 self.check_reserved +4021 ['common', 'postgres', 'mysql'] +4022 self.check_reserved +4023 ['all'] +4024 """ +4025 for backend in self.check_reserved: +4026 if name.upper() in self.RSK[backend]: +4027 raise SyntaxError, 'invalid table/column name "%s" is a "%s" reserved SQL keyword' % (name, backend.upper()) +
    4028 +
    4029 - def __contains__(self, tablename): +
    4030 if self.has_key(tablename): +4031 return True +4032 else: +4033 return False +
    4034 +
    4035 - def parse_as_rest(self,patterns,args,vars,query=None,nested_select=True): +
    4036 """ +4037 EXAMPLE: +4038 +4039 db.define_table('person',Field('name'),Field('info')) +4040 db.define_table('pet',Field('person',db.person),Field('name'),Field('info')) +4041 +4042 @request.restful() +4043 def index(): +4044 def GET(*kargs,**kvars): +4045 patterns = [ +4046 "/persons[person]", +4047 "/{person.name.startswith}", +4048 "/{person.name}/:field", +4049 "/{person.name}/pets[pet.person]", +4050 "/{person.name}/pet[pet.person]/{pet.name}", +4051 "/{person.name}/pet[pet.person]/{pet.name}/:field" +4052 ] +4053 parser = db.parse_as_rest(patterns,kargs,kvars) +4054 if parser.status == 200: +4055 return dict(content=parser.response) +4056 else: +4057 raise HTTP(parser.status,parser.error) +4058 def POST(table_name,**kvars): +4059 if table_name == 'person': +4060 return db.person.validate_and_insert(**kvars) +4061 elif table_name == 'pet': +4062 return db.pet.validate_and_insert(**kvars) +4063 else: +4064 raise HTTP(400) +4065 return locals() +4066 """ +4067 +4068 db = self +4069 re1 = re.compile('^{[^\.]+\.[^\.]+(\.(lt|gt|le|ge|eq|ne|contains|startswith|year|month|day|hour|minute|second))?(\.not)?}$') +4070 re2 = re.compile('^.+\[.+\]$') +4071 +4072 def auto_table(table,base='',depth=0): +4073 patterns = [] +4074 for field in db[table].fields: +4075 if base: +4076 tag = '%s/%s' % (base,field.replace('_','-')) +4077 else: +4078 tag = '/%s/%s' % (table.replace('_','-'),field.replace('_','-')) +4079 f = db[table][field] +4080 if not f.readable: continue +4081 if f.type=='id' or 'slug' in field or f.type.startswith('reference'): +4082 tag += '/{%s.%s}' % (table,field) +4083 patterns.append(tag) +4084 patterns.append(tag+'/:field') +4085 elif f.type.startswith('boolean'): +4086 tag += '/{%s.%s}' % (table,field) +4087 patterns.append(tag) +4088 patterns.append(tag+'/:field') +4089 elif f.type.startswith('double') or f.type.startswith('integer'): +4090 tag += '/{%s.%s.ge}/{%s.%s.lt}' % (table,field,table,field) +4091 patterns.append(tag) +4092 patterns.append(tag+'/:field') +4093 elif f.type.startswith('list:'): +4094 tag += '/{%s.%s.contains}' % (table,field) +4095 patterns.append(tag) +4096 patterns.append(tag+'/:field') +4097 elif f.type in ('date','datetime'): +4098 tag+= '/{%s.%s.year}' % (table,field) +4099 patterns.append(tag) +4100 patterns.append(tag+'/:field') +4101 tag+='/{%s.%s.month}' % (table,field) +4102 patterns.append(tag) +4103 patterns.append(tag+'/:field') +4104 tag+='/{%s.%s.day}' % (table,field) +4105 patterns.append(tag) +4106 patterns.append(tag+'/:field') +4107 if f.type in ('datetime','time'): +4108 tag+= '/{%s.%s.hour}' % (table,field) +4109 patterns.append(tag) +4110 patterns.append(tag+'/:field') +4111 tag+='/{%s.%s.minute}' % (table,field) +4112 patterns.append(tag) +4113 patterns.append(tag+'/:field') +4114 tag+='/{%s.%s.second}' % (table,field) +4115 patterns.append(tag) +4116 patterns.append(tag+'/:field') +4117 if depth>0: +4118 for rtable,rfield in db[table]._referenced_by: +4119 tag+='/%s[%s.%s]' % (rtable,rtable,rfield) +4120 patterns.append(tag) +4121 patterns += auto_table(rtable,base=tag,depth=depth-1) +4122 return patterns +
    4123 +4124 if patterns=='auto': +4125 patterns=[] +4126 for table in db.tables: +4127 if not table.startswith('auth_'): +4128 patterns += auto_table(table,base='',depth=1) +4129 else: +4130 i = 0 +4131 while i<len(patterns): +4132 pattern = patterns[i] +4133 tokens = pattern.split('/') +4134 if tokens[-1].startswith(':auto') and re2.match(tokens[-1]): +4135 new_patterns = auto_table(tokens[-1][tokens[-1].find('[')+1:-1],'/'.join(tokens[:-1])) +4136 patterns = patterns[:i]+new_patterns+patterns[i+1:] +4137 i += len(new_patterns) +4138 else: +4139 i += 1 +4140 if '/'.join(args) == 'patterns': +4141 return Row({'status':200,'pattern':'list', +4142 'error':None,'response':patterns}) +4143 for pattern in patterns: +4144 otable=table=None +4145 dbset=db(query) +4146 i=0 +4147 tags = pattern[1:].split('/') +4148 # print pattern +4149 if len(tags)!=len(args): +4150 continue +4151 for tag in tags: +4152 # print i, tag, args[i] +4153 if re1.match(tag): +4154 # print 're1:'+tag +4155 tokens = tag[1:-1].split('.') +4156 table, field = tokens[0], tokens[1] +4157 if not otable or table == otable: +4158 if len(tokens)==2 or tokens[2]=='eq': +4159 query = db[table][field]==args[i] +4160 elif tokens[2]=='ne': +4161 query = db[table][field]!=args[i] +4162 elif tokens[2]=='lt': +4163 query = db[table][field]<args[i] +4164 elif tokens[2]=='gt': +4165 query = db[table][field]>args[i] +4166 elif tokens[2]=='ge': +4167 query = db[table][field]>=args[i] +4168 elif tokens[2]=='le': +4169 query = db[table][field]<=args[i] +4170 elif tokens[2]=='year': +4171 query = db[table][field].year()==args[i] +4172 elif tokens[2]=='month': +4173 query = db[table][field].month()==args[i] +4174 elif tokens[2]=='day': +4175 query = db[table][field].day()==args[i] +4176 elif tokens[2]=='hour': +4177 query = db[table][field].hour()==args[i] +4178 elif tokens[2]=='minute': +4179 query = db[table][field].minutes()==args[i] +4180 elif tokens[2]=='second': +4181 query = db[table][field].seconds()==args[i] +4182 elif tokens[2]=='startswith': +4183 query = db[table][field].startswith(args[i]) +4184 elif tokens[2]=='contains': +4185 query = db[table][field].contains(args[i]) +4186 else: +4187 raise RuntimeError, "invalid pattern: %s" % pattern +4188 if len(tokens)==4 and tokens[3]=='not': +4189 query = ~query +4190 elif len(tokens)>=4: +4191 raise RuntimeError, "invalid pattern: %s" % pattern +4192 dbset=dbset(query) +4193 else: +4194 raise RuntimeError, "missing relation in pattern: %s" % pattern +4195 elif otable and re2.match(tag) and args[i]==tag[:tag.find('[')]: +4196 # print 're2:'+tag +4197 ref = tag[tag.find('[')+1:-1] +4198 if '.' in ref: +4199 table,field = ref.split('.') +4200 # print table,field +4201 if nested_select: +4202 try: +4203 dbset=db(db[table][field].belongs(dbset._select(db[otable]._id))) +4204 except ValueError: +4205 return Row({'status':400,'pattern':pattern, +4206 'error':'invalid path','response':None}) +4207 else: +4208 items = [item.id for item in dbset.select(db[otable]._id)] +4209 dbset=db(db[table][field].belongs(items)) +4210 else: +4211 dbset=dbset(db[ref]) +4212 elif tag==':field' and table: +4213 # # print 're3:'+tag +4214 field = args[i] +4215 if not field in db[table]: break +4216 try: +4217 item = dbset.select(db[table][field],limitby=(0,1)).first() +4218 except ValueError: +4219 return Row({'status':400,'pattern':pattern, +4220 'error':'invalid path','response':None}) +4221 if not item: +4222 return Row({'status':404,'pattern':pattern, +4223 'error':'record not found','response':None}) +4224 else: +4225 return Row({'status':200,'response':item[field], +4226 'pattern':pattern}) +4227 elif tag != args[i]: +4228 break +4229 otable = table +4230 i += 1 +4231 if i==len(tags) and table: +4232 otable,ofield = vars.get('order','%s.%s' % (table,field)).split('.',1) +4233 try: +4234 if otable[:1]=='~': orderby = ~db[otable[1:]][ofield] +4235 else: orderby = db[otable][ofield] +4236 except KeyError: +4237 return Row({'status':400,'error':'invalid orderby','response':None}) +4238 fields = [field for field in db[table] if field.readable] +4239 count = dbset.count() +4240 try: +4241 limits = (int(vars.get('min',0)),int(vars.get('max',1000))) +4242 if limits[0]<0 or limits[1]<limits[0]: raise ValueError +4243 except ValueError: +4244 Row({'status':400,'error':'invalid limits','response':None}) +4245 if count > limits[1]-limits[0]: +4246 Row({'status':400,'error':'too many records','response':None}) +4247 try: +4248 response = dbset.select(limitby=limits,orderby=orderby,*fields) +4249 except ValueError: +4250 return Row({'status':400,'pattern':pattern, +4251 'error':'invalid path','response':None}) +4252 return Row({'status':200,'response':response,'pattern':pattern}) +4253 return Row({'status':400,'error':'no matching pattern','response':None}) +
    4254 +4255 +
    4256 - def define_table( +4257 self, +4258 tablename, +4259 *fields, +4260 **args +4261 ): +
    4262 +4263 for key in args: +4264 if key not in [ +4265 'migrate', +4266 'primarykey', +4267 'fake_migrate', +4268 'format', +4269 'trigger_name', +4270 'sequence_name', +4271 'polymodel']: +4272 raise SyntaxError, 'invalid table "%s" attribute: %s' % (tablename, key) +4273 migrate = self._migrate_enabled and args.get('migrate',self._migrate) +4274 fake_migrate = self._fake_migrate_all or args.get('fake_migrate',self._fake_migrate) +4275 format = args.get('format',None) +4276 trigger_name = args.get('trigger_name', None) +4277 sequence_name = args.get('sequence_name', None) +4278 primarykey=args.get('primarykey',None) +4279 polymodel=args.get('polymodel',None) +4280 if not isinstance(tablename,str): +4281 raise SyntaxError, "missing table name" +4282 tablename = cleanup(tablename) +4283 lowertablename = tablename.lower() +4284 +4285 if tablename.startswith('_') or hasattr(self,lowertablename) or \ +4286 regex_python_keywords.match(tablename): +4287 raise SyntaxError, 'invalid table name: %s' % tablename +4288 elif lowertablename in self.tables: +4289 raise SyntaxError, 'table already defined: %s' % tablename +4290 elif self.check_reserved: +4291 self.check_reserved_keyword(tablename) +4292 +4293 if self._common_fields: +4294 fields = [f for f in fields] + [f for f in self._common_fields] +4295 +4296 t = self[tablename] = Table(self, tablename, *fields, +4297 **dict(primarykey=primarykey, +4298 trigger_name=trigger_name, +4299 sequence_name=sequence_name)) +4300 # db magic +4301 if self._uri in (None,'None'): +4302 return t +4303 +4304 t._create_references() +4305 +4306 if migrate or self._adapter.dbengine=='google:datastore': +4307 try: +4308 sql_locker.acquire() +4309 self._adapter.create_table(t,migrate=migrate, +4310 fake_migrate=fake_migrate, +4311 polymodel=polymodel) +4312 finally: +4313 sql_locker.release() +4314 else: +4315 t._dbt = None +4316 self.tables.append(tablename) +4317 t._format = format +4318 return t +
    4319 +
    4320 - def __iter__(self): +
    4321 for tablename in self.tables: +4322 yield self[tablename] +
    4323 +
    4324 - def __getitem__(self, key): +
    4325 return dict.__getitem__(self, str(key)) +
    4326 +
    4327 - def __setitem__(self, key, value): +
    4328 dict.__setitem__(self, str(key), value) +
    4329 +
    4330 - def __getattr__(self, key): +
    4331 return self[key] +
    4332 +
    4333 - def __setattr__(self, key, value): +
    4334 if key[:1]!='_' and key in self: +4335 raise SyntaxError, \ +4336 'Object %s exists and cannot be redefined' % key +4337 self[key] = value +
    4338 +
    4339 - def __repr__(self): +
    4340 return '<DAL ' + dict.__repr__(self) + '>' +
    4341 +
    4342 - def __call__(self, query=None): +
    4343 if isinstance(query,Table): +4344 query = query._id>0 +4345 elif isinstance(query,Field): +4346 query = query!=None +4347 return Set(self, query) +
    4348 +
    4349 - def commit(self): +
    4350 self._adapter.commit() +
    4351 +
    4352 - def rollback(self): +
    4353 self._adapter.rollback() +
    4354 +
    4355 - def executesql(self, query, placeholders=None, as_dict=False): +
    4356 """ +4357 placeholders is optional and will always be None when using DAL +4358 if using raw SQL with placeholders, placeholders may be +4359 a sequence of values to be substituted in +4360 or, *if supported by the DB driver*, a dictionary with keys +4361 matching named placeholders in your SQL. +4362 +4363 Added 2009-12-05 "as_dict" optional argument. Will always be +4364 None when using DAL. If using raw SQL can be set to True +4365 and the results cursor returned by the DB driver will be +4366 converted to a sequence of dictionaries keyed with the db +4367 field names. Tested with SQLite but should work with any database +4368 since the cursor.description used to get field names is part of the +4369 Python dbi 2.0 specs. Results returned with as_dict = True are +4370 the same as those returned when applying .to_list() to a DAL query. +4371 +4372 [{field1: value1, field2: value2}, {field1: value1b, field2: value2b}] +4373 +4374 --bmeredyk +4375 """ +4376 if placeholders: +4377 self._adapter.execute(query, placeholders) +4378 else: +4379 self._adapter.execute(query) +4380 if as_dict: +4381 if not hasattr(self._adapter.cursor,'description'): +4382 raise RuntimeError, "database does not support executesql(...,as_dict=True)" +4383 # Non-DAL legacy db query, converts cursor results to dict. +4384 # sequence of 7-item sequences. each sequence tells about a column. +4385 # first item is always the field name according to Python Database API specs +4386 columns = self._adapter.cursor.description +4387 # reduce the column info down to just the field names +4388 fields = [f[0] for f in columns] +4389 # will hold our finished resultset in a list +4390 data = self._adapter.cursor.fetchall() +4391 # convert the list for each row into a dictionary so it's +4392 # easier to work with. row['field_name'] rather than row[0] +4393 return [dict(zip(fields,row)) for row in data] +4394 # see if any results returned from database +4395 try: +4396 return self._adapter.cursor.fetchall() +4397 except: +4398 return None +
    4399 +
    4400 - def _update_referenced_by(self, other): +
    4401 for tablename in self.tables: +4402 by = self[tablename]._referenced_by +4403 by[:] = [item for item in by if not item[0] == other] +
    4404 +
    4405 - def export_to_csv_file(self, ofile, *args, **kwargs): +
    4406 for table in self.tables: +4407 ofile.write('TABLE %s\r\n' % table) +4408 self(self[table]._id > 0).select().export_to_csv_file(ofile, *args, **kwargs) +4409 ofile.write('\r\n\r\n') +4410 ofile.write('END') +
    4411 +
    4412 - def import_from_csv_file(self, ifile, id_map={}, null='<NULL>', +4413 unique='uuid', *args, **kwargs): +
    4414 for line in ifile: +4415 line = line.strip() +4416 if not line: +4417 continue +4418 elif line == 'END': +4419 return +4420 elif not line.startswith('TABLE ') or not line[6:] in self.tables: +4421 raise SyntaxError, 'invalid file format' +4422 else: +4423 tablename = line[6:] +4424 self[tablename].import_from_csv_file(ifile, id_map, null, +4425 unique, *args, **kwargs) +
    4426 +4427 +
    4428 -class SQLALL(object): +
    4429 """ +4430 Helper class providing a comma-separated string having all the field names +4431 (prefixed by table name and '.') +4432 +4433 normally only called from within gluon.sql +4434 """ +4435 +
    4436 - def __init__(self, table): +
    4437 self.table = table +
    4438 +
    4439 - def __str__(self): +
    4440 return ', '.join([str(field) for field in self.table]) +
    4441 +4442 +
    4443 -class Reference(int): +
    4444 +
    4445 - def __allocate(self): +
    4446 if not self._record: +4447 self._record = self._table[int(self)] +4448 if not self._record: +4449 raise RuntimeError, "Using a recursive select but encountered a broken reference: %s %d"%(self._table, int(self)) +
    4450 +
    4451 - def __getattr__(self, key): +
    4452 if key == 'id': +4453 return int(self) +4454 self.__allocate() +4455 return self._record.get(key, None) +
    4456 +
    4457 - def __setattr__(self, key, value): +
    4458 if key.startswith('_'): +4459 int.__setattr__(self, key, value) +4460 return +4461 self.__allocate() +4462 self._record[key] = value +
    4463 +
    4464 - def __getitem__(self, key): +
    4465 if key == 'id': +4466 return int(self) +4467 self.__allocate() +4468 return self._record.get(key, None) +
    4469 +
    4470 - def __setitem__(self,key,value): +
    4471 self.__allocate() +4472 self._record[key] = value +
    4473 +4474 +
    4475 -def Reference_unpickler(data): +
    4476 return marshal.loads(data) +
    4477 +
    4478 -def Reference_pickler(data): +
    4479 try: +4480 marshal_dump = marshal.dumps(int(data)) +4481 except AttributeError: +4482 marshal_dump = 'i%s' % struct.pack('<i', int(data)) +4483 return (Reference_unpickler, (marshal_dump,)) +
    4484 +4485 copy_reg.pickle(Reference, Reference_pickler, Reference_unpickler) +4486 +4487 +
    4488 -class Table(dict): +
    4489 +4490 """ +4491 an instance of this class represents a database table +4492 +4493 Example:: +4494 +4495 db = DAL(...) +4496 db.define_table('users', Field('name')) +4497 db.users.insert(name='me') # print db.users._insert(...) to see SQL +4498 db.users.drop() +4499 """ +4500 +
    4501 - def __init__( +4502 self, +4503 db, +4504 tablename, +4505 *fields, +4506 **args +4507 ): +
    4508 """ +4509 Initializes the table and performs checking on the provided fields. +4510 +4511 Each table will have automatically an 'id'. +4512 +4513 If a field is of type Table, the fields (excluding 'id') from that table +4514 will be used instead. +4515 +4516 :raises SyntaxError: when a supplied field is of incorrect type. +4517 """ +4518 self._tablename = tablename +4519 self._sequence_name = args.get('sequence_name',None) or \ +4520 db and db._adapter.sequence_name(tablename) +4521 self._trigger_name = args.get('trigger_name',None) or \ +4522 db and db._adapter.trigger_name(tablename) +4523 +4524 primarykey = args.get('primarykey', None) +4525 fieldnames,newfields=set(),[] +4526 if primarykey: +4527 if not isinstance(primarykey,list): +4528 raise SyntaxError, \ +4529 "primarykey must be a list of fields from table '%s'" \ +4530 % tablename +4531 self._primarykey = primarykey +4532 elif not [f for f in fields if isinstance(f,Field) and f.type=='id']: +4533 field = Field('id', 'id') +4534 newfields.append(field) +4535 fieldnames.add('id') +4536 self._id = field +4537 for field in fields: +4538 if not isinstance(field, (Field, Table)): +4539 raise SyntaxError, \ +4540 'define_table argument is not a Field or Table: %s' % field +4541 elif isinstance(field, Field) and not field.name in fieldnames: +4542 if hasattr(field, '_db'): +4543 field = copy.copy(field) +4544 newfields.append(field) +4545 fieldnames.add(field.name) +4546 if field.type=='id': +4547 self._id = field +4548 elif isinstance(field, Table): +4549 table = field +4550 for field in table: +4551 if not field.name in fieldnames and not field.type=='id': +4552 newfields.append(copy.copy(field)) +4553 fieldnames.add(field.name) +4554 else: +4555 # let's ignore new fields with duplicated names!!! +4556 pass +4557 fields = newfields +4558 self._db = db +4559 tablename = tablename +4560 self.fields = SQLCallableList() +4561 self.virtualfields = [] +4562 fields = list(fields) +4563 +4564 if db and self._db._adapter.uploads_in_blob==True: +4565 for field in fields: +4566 if isinstance(field, Field) and field.type == 'upload'\ +4567 and field.uploadfield is True: +4568 tmp = field.uploadfield = '%s_blob' % field.name +4569 fields.append(self._db.Field(tmp, 'blob', default='')) +4570 +4571 lower_fieldnames = set() +4572 reserved = dir(Table) + ['fields'] +4573 for field in fields: +4574 if db and db.check_reserved: +4575 db.check_reserved_keyword(field.name) +4576 elif field.name in reserved: +4577 raise SyntaxError, "field name %s not allowed" % field.name +4578 +4579 if field.name.lower() in lower_fieldnames: +4580 raise SyntaxError, "duplicate field %s in table %s" \ +4581 % (field.name, tablename) +4582 else: +4583 lower_fieldnames.add(field.name.lower()) +4584 +4585 self.fields.append(field.name) +4586 self[field.name] = field +4587 if field.type == 'id': +4588 self['id'] = field +4589 field.tablename = field._tablename = tablename +4590 field.table = field._table = self +4591 field.db = field._db = self._db +4592 if self._db and field.type!='text' and \ +4593 self._db._adapter.maxcharlength < field.length: +4594 field.length = self._db._adapter.maxcharlength +4595 if field.requires == DEFAULT: +4596 field.requires = sqlhtml_validators(field) +4597 self.ALL = SQLALL(self) +4598 +4599 if hasattr(self,'_primarykey'): +4600 for k in self._primarykey: +4601 if k not in self.fields: +4602 raise SyntaxError, \ +4603 "primarykey must be a list of fields from table '%s " % tablename +4604 else: +4605 self[k].notnull = True +
    4606 +
    4607 - def _validate(self,**vars): +
    4608 errors = Row() +4609 for key,value in vars.items(): +4610 value,error = self[key].validate(value) +4611 if error: +4612 errors[key] = error +4613 return errors +
    4614 +
    4615 - def _create_references(self): +
    4616 pr = self._db._pending_references +4617 self._referenced_by = [] +4618 for fieldname in self.fields: +4619 field=self[fieldname] +4620 if isinstance(field.type,str) and field.type[:10] == 'reference ': +4621 ref = field.type[10:].strip() +4622 if not ref.split(): +4623 raise SyntaxError, 'Table: reference to nothing: %s' %ref +4624 refs = ref.split('.') +4625 rtablename = refs[0] +4626 if not rtablename in self._db: +4627 pr[rtablename] = pr.get(rtablename,[]) + [field] +4628 continue +4629 rtable = self._db[rtablename] +4630 if len(refs)==2: +4631 rfieldname = refs[1] +4632 if not hasattr(rtable,'_primarykey'): +4633 raise SyntaxError,\ +4634 'keyed tables can only reference other keyed tables (for now)' +4635 if rfieldname not in rtable.fields: +4636 raise SyntaxError,\ +4637 "invalid field '%s' for referenced table '%s' in table '%s'" \ +4638 % (rfieldname, rtablename, self._tablename) +4639 rtable._referenced_by.append((self._tablename, field.name)) +4640 for referee in pr.get(self._tablename,[]): +4641 self._referenced_by.append((referee._tablename,referee.name)) +
    4642 +
    4643 - def _filter_fields(self, record, id=False): +
    4644 return dict([(k, v) for (k, v) in record.items() if k +4645 in self.fields and (self[k].type!='id' or id)]) +
    4646 +
    4647 - def _build_query(self,key): +
    4648 """ for keyed table only """ +4649 query = None +4650 for k,v in key.iteritems(): +4651 if k in self._primarykey: +4652 if query: +4653 query = query & (self[k] == v) +4654 else: +4655 query = (self[k] == v) +4656 else: +4657 raise SyntaxError, \ +4658 'Field %s is not part of the primary key of %s' % \ +4659 (k,self._tablename) +4660 return query +
    4661 +
    4662 - def __getitem__(self, key): +
    4663 if not key: +4664 return None +4665 elif isinstance(key, dict): +4666 """ for keyed table """ +4667 query = self._build_query(key) +4668 rows = self._db(query).select() +4669 if rows: +4670 return rows[0] +4671 return None +4672 elif str(key).isdigit(): +4673 return self._db(self._id == key).select(limitby=(0,1)).first() +4674 elif key: +4675 return dict.__getitem__(self, str(key)) +
    4676 +
    4677 - def __call__(self, key=DEFAULT, **kwargs): +
    4678 if key!=DEFAULT: +4679 if isinstance(key, Query): +4680 record = self._db(key).select(limitby=(0,1)).first() +4681 elif not str(key).isdigit(): +4682 record = None +4683 else: +4684 record = self._db(self._id == key).select(limitby=(0,1)).first() +4685 if record: +4686 for k,v in kwargs.items(): +4687 if record[k]!=v: return None +4688 return record +4689 elif kwargs: +4690 query = reduce(lambda a,b:a&b,[self[k]==v for k,v in kwargs.items()]) +4691 return self._db(query).select(limitby=(0,1)).first() +4692 else: +4693 return None +
    4694 +
    4695 - def __setitem__(self, key, value): +
    4696 if isinstance(key, dict) and isinstance(value, dict): +4697 """ option for keyed table """ +4698 if set(key.keys()) == set(self._primarykey): +4699 value = self._filter_fields(value) +4700 kv = {} +4701 kv.update(value) +4702 kv.update(key) +4703 if not self.insert(**kv): +4704 query = self._build_query(key) +4705 self._db(query).update(**self._filter_fields(value)) +4706 else: +4707 raise SyntaxError,\ +4708 'key must have all fields from primary key: %s'%\ +4709 (self._primarykey) +4710 elif str(key).isdigit(): +4711 if key == 0: +4712 self.insert(**self._filter_fields(value)) +4713 elif not self._db(self._id == key)\ +4714 .update(**self._filter_fields(value)): +4715 raise SyntaxError, 'No such record: %s' % key +4716 else: +4717 if isinstance(key, dict): +4718 raise SyntaxError,\ +4719 'value must be a dictionary: %s' % value +4720 dict.__setitem__(self, str(key), value) +
    4721 +
    4722 - def __delitem__(self, key): +
    4723 if isinstance(key, dict): +4724 query = self._build_query(key) +4725 if not self._db(query).delete(): +4726 raise SyntaxError, 'No such record: %s' % key +4727 elif not str(key).isdigit() or not self._db(self._id == key).delete(): +4728 raise SyntaxError, 'No such record: %s' % key +
    4729 +
    4730 - def __getattr__(self, key): +
    4731 return self[key] +
    4732 +
    4733 - def __setattr__(self, key, value): +
    4734 if key in self: +4735 raise SyntaxError, 'Object exists and cannot be redefined: %s' % key +4736 self[key] = value +
    4737 +
    4738 - def __iter__(self): +
    4739 for fieldname in self.fields: +4740 yield self[fieldname] +
    4741 +
    4742 - def __repr__(self): +
    4743 return '<Table ' + dict.__repr__(self) + '>' +
    4744 +
    4745 - def __str__(self): +
    4746 if self.get('_ot', None): +4747 return '%s AS %s' % (self._ot, self._tablename) +4748 return self._tablename +
    4749 +
    4750 - def _drop(self, mode = ''): +
    4751 return self._db._adapter._drop(self, mode) +
    4752 +
    4753 - def drop(self, mode = ''): +
    4754 return self._db._adapter.drop(self,mode) +
    4755 +
    4756 - def _listify(self,fields,update=False): +
    4757 new_fields = [] +4758 new_fields_names = [] +4759 for name in fields: +4760 if not name in self.fields: +4761 if name != 'id': +4762 raise SyntaxError, 'Field %s does not belong to the table' % name +4763 else: +4764 new_fields.append((self[name],fields[name])) +4765 new_fields_names.append(name) +4766 for ofield in self: +4767 if not ofield.name in new_fields_names: +4768 if not update and ofield.default!=None: +4769 new_fields.append((ofield,ofield.default)) +4770 elif update and ofield.update!=None: +4771 new_fields.append((ofield,ofield.update)) +4772 for ofield in self: +4773 if not ofield.name in new_fields_names and ofield.compute: +4774 try: +4775 new_fields.append((ofield,ofield.compute(Row(fields)))) +4776 except KeyError: +4777 pass +4778 if not update and ofield.required and not ofield.name in new_fields_names: +4779 raise SyntaxError,'Table: missing required field: %s' % ofield.name +4780 return new_fields +
    4781 +
    4782 - def _insert(self, **fields): +
    4783 return self._db._adapter._insert(self,self._listify(fields)) +
    4784 +
    4785 - def insert(self, **fields): +
    4786 return self._db._adapter.insert(self,self._listify(fields)) +
    4787 +
    4788 - def validate_and_insert(self,**fields): +
    4789 response = Row() +4790 response.errors = self._validate(**fields) +4791 if not response.errors: +4792 response.id = self.insert(**fields) +4793 else: +4794 response.id = None +4795 return response +
    4796 +
    4797 - def update_or_insert(self, key=DEFAULT, **values): +
    4798 if key==DEFAULT: +4799 record = self(**values) +4800 else: +4801 record = self(key) +4802 if record: +4803 record.update_record(**values) +4804 newid = None +4805 else: +4806 newid = self.insert(**values) +4807 return newid +
    4808 +
    4809 - def bulk_insert(self, items): +
    4810 """ +4811 here items is a list of dictionaries +4812 """ +4813 items = [self._listify(item) for item in items] +4814 return self._db._adapter.bulk_insert(self,items) +
    4815 +
    4816 - def _truncate(self, mode = None): +
    4817 return self._db._adapter._truncate(self, mode) +
    4818 +
    4819 - def truncate(self, mode = None): +
    4820 return self._db._adapter.truncate(self, mode) +
    4821 +
    4822 - def import_from_csv_file( +4823 self, +4824 csvfile, +4825 id_map=None, +4826 null='<NULL>', +4827 unique='uuid', +4828 *args, **kwargs +4829 ): +
    4830 """ +4831 import records from csv file. Column headers must have same names as +4832 table fields. field 'id' is ignored. If column names read 'table.file' +4833 the 'table.' prefix is ignored. +4834 'unique' argument is a field which must be unique +4835 (typically a uuid field) +4836 """ +4837 +4838 delimiter = kwargs.get('delimiter', ',') +4839 quotechar = kwargs.get('quotechar', '"') +4840 quoting = kwargs.get('quoting', csv.QUOTE_MINIMAL) +4841 +4842 reader = csv.reader(csvfile, delimiter=delimiter, quotechar=quotechar, quoting=quoting) +4843 colnames = None +4844 if isinstance(id_map, dict): +4845 if not self._tablename in id_map: +4846 id_map[self._tablename] = {} +4847 id_map_self = id_map[self._tablename] +4848 +4849 def fix(field, value, id_map): +4850 if value == null: +4851 value = None +4852 elif field.type=='blob': +4853 value = base64.b64decode(value) +4854 elif field.type=='double': +4855 if not value.strip(): +4856 value = None +4857 else: +4858 value = float(value) +4859 elif field.type=='integer': +4860 if not value.strip(): +4861 value = None +4862 else: +4863 value = int(value) +4864 elif field.type.startswith('list:string'): +4865 value = bar_decode_string(value) +4866 elif field.type.startswith('list:reference'): +4867 ref_table = field.type[10:].strip() +4868 value = [id_map[ref_table][int(v)] \ +4869 for v in bar_decode_string(value)] +4870 elif field.type.startswith('list:'): +4871 value = bar_decode_integer(value) +4872 elif id_map and field.type.startswith('reference'): +4873 try: +4874 value = id_map[field.type[9:].strip()][value] +4875 except KeyError: +4876 pass +4877 return (field.name, value) +
    4878 +4879 def is_id(colname): +4880 if colname in self: +4881 return self[colname].type == 'id' +4882 else: +4883 return False +
    4884 +4885 for line in reader: +4886 if not line: +4887 break +4888 if not colnames: +4889 colnames = [x.split('.',1)[-1] for x in line][:len(line)] +4890 cols, cid = [], [] +4891 for i,colname in enumerate(colnames): +4892 if is_id(colname): +4893 cid = i +4894 else: +4895 cols.append(i) +4896 if colname == unique: +4897 unique_idx = i +4898 else: +4899 items = [fix(self[colnames[i]], line[i], id_map) \ +4900 for i in cols if colnames[i] in self.fields] +4901 # Validation. Check for duplicate of 'unique' &, +4902 # if present, update instead of insert. +4903 if not unique or unique not in colnames: +4904 new_id = self.insert(**dict(items)) +4905 else: +4906 unique_value = line[unique_idx] +4907 query = self._db[self][unique] == unique_value +4908 record = self._db(query).select().first() +4909 if record: +4910 record.update_record(**dict(items)) +4911 new_id = record[self._id.name] +4912 else: +4913 new_id = self.insert(**dict(items)) +4914 if id_map and cid != []: +4915 id_map_self[line[cid]] = new_id +4916 +
    4917 - def with_alias(self, alias): +
    4918 return self._db._adapter.alias(self,alias) +
    4919 +
    4920 - def on(self, query): +
    4921 return Expression(self._db,self._db._adapter.ON,self,query) +
    4922 +4923 +4924 +
    4925 -class Expression(object): +
    4926 +
    4927 - def __init__( +4928 self, +4929 db, +4930 op, +4931 first=None, +4932 second=None, +4933 type=None, +4934 ): +
    4935 +4936 self.db = db +4937 self.op = op +4938 self.first = first +4939 self.second = second +4940 ### self._tablename = first._tablename ## CHECK +4941 if not type and first and hasattr(first,'type'): +4942 self.type = first.type +4943 else: +4944 self.type = type +
    4945 +
    4946 - def sum(self): +
    4947 return Expression(self.db, self.db._adapter.AGGREGATE, self, 'SUM', self.type) +
    4948 +
    4949 - def max(self): +
    4950 return Expression(self.db, self.db._adapter.AGGREGATE, self, 'MAX', self.type) +
    4951 +
    4952 - def min(self): +
    4953 return Expression(self.db, self.db._adapter.AGGREGATE, self, 'MIN', self.type) +
    4954 +
    4955 - def len(self): +
    4956 return Expression(self.db, self.db._adapter.AGGREGATE, self, 'LENGTH', 'integer') +
    4957 +
    4958 - def lower(self): +
    4959 return Expression(self.db, self.db._adapter.LOWER, self, None, self.type) +
    4960 +
    4961 - def upper(self): +
    4962 return Expression(self.db, self.db._adapter.UPPER, self, None, self.type) +
    4963 +
    4964 - def year(self): +
    4965 return Expression(self.db, self.db._adapter.EXTRACT, self, 'year', 'integer') +
    4966 +
    4967 - def month(self): +
    4968 return Expression(self.db, self.db._adapter.EXTRACT, self, 'month', 'integer') +
    4969 +
    4970 - def day(self): +
    4971 return Expression(self.db, self.db._adapter.EXTRACT, self, 'day', 'integer') +
    4972 +
    4973 - def hour(self): +
    4974 return Expression(self.db, self.db._adapter.EXTRACT, self, 'hour', 'integer') +
    4975 +
    4976 - def minutes(self): +
    4977 return Expression(self.db, self.db._adapter.EXTRACT, self, 'minute', 'integer') +
    4978 +
    4979 - def coalesce_zero(self): +
    4980 return Expression(self.db, self.db._adapter.COALESCE_ZERO, self, None, self.type) +
    4981 +
    4982 - def seconds(self): +
    4983 return Expression(self.db, self.db._adapter.EXTRACT, self, 'second', 'integer') +
    4984 +
    4985 - def __getslice__(self, start, stop): +
    4986 if start < 0: +4987 pos0 = '(%s - %d)' % (self.len(), abs(start) - 1) +4988 else: +4989 pos0 = start + 1 +4990 +4991 if stop < 0: +4992 length = '(%s - %d - %s)' % (self.len(), abs(stop) - 1, pos0) +4993 elif stop == sys.maxint: +4994 length = self.len() +4995 else: +4996 length = '(%s - %s)' % (stop + 1, pos0) +4997 return Expression(self.db,self.db._adapter.SUBSTRING, +4998 self, (pos0, length), self.type) +
    4999 +
    5000 - def __getitem__(self, i): +
    5001 return self[i:i + 1] +
    5002 +
    5003 - def __str__(self): +
    5004 return self.db._adapter.expand(self,self.type) +
    5005 +
    5006 - def __or__(self, other): # for use in sortby +
    5007 return Expression(self.db,self.db._adapter.COMMA,self,other,self.type) +
    5008 +
    5009 - def __invert__(self): +
    5010 if hasattr(self,'_op') and self.op == self.db._adapter.INVERT: +5011 return self.first +5012 return Expression(self.db,self.db._adapter.INVERT,self,type=self.type) +
    5013 +
    5014 - def __add__(self, other): +
    5015 return Expression(self.db,self.db._adapter.ADD,self,other,self.type) +
    5016 +
    5017 - def __sub__(self, other): +
    5018 if self.type == 'integer': +5019 result_type = 'integer' +5020 elif self.type in ['date','time','datetime','double']: +5021 result_type = 'double' +5022 else: +5023 raise SyntaxError, "subtraction operation not supported for type" +5024 return Expression(self.db,self.db._adapter.SUB,self,other, +5025 result_type) +
    5026 - def __mul__(self, other): +
    5027 return Expression(self.db,self.db._adapter.MUL,self,other,self.type) +
    5028 +
    5029 - def __div__(self, other): +
    5030 return Expression(self.db,self.db._adapter.DIV,self,other,self.type) +
    5031 +
    5032 - def __mod__(self, other): +
    5033 return Expression(self.db,self.db._adapter.MOD,self,other,self.type) +
    5034 +
    5035 - def __eq__(self, value): +
    5036 return Query(self.db, self.db._adapter.EQ, self, value) +
    5037 +
    5038 - def __ne__(self, value): +
    5039 return Query(self.db, self.db._adapter.NE, self, value) +
    5040 +
    5041 - def __lt__(self, value): +
    5042 return Query(self.db, self.db._adapter.LT, self, value) +
    5043 +
    5044 - def __le__(self, value): +
    5045 return Query(self.db, self.db._adapter.LE, self, value) +
    5046 +
    5047 - def __gt__(self, value): +
    5048 return Query(self.db, self.db._adapter.GT, self, value) +
    5049 +
    5050 - def __ge__(self, value): +
    5051 return Query(self.db, self.db._adapter.GE, self, value) +
    5052 +
    5053 - def like(self, value): +
    5054 return Query(self.db, self.db._adapter.LIKE, self, value) +
    5055 +
    5056 - def belongs(self, value): +
    5057 return Query(self.db, self.db._adapter.BELONGS, self, value) +
    5058 +
    5059 - def startswith(self, value): +
    5060 if not self.type in ('string', 'text'): +5061 raise SyntaxError, "startswith used with incompatible field type" +5062 return Query(self.db, self.db._adapter.STARTSWITH, self, value) +
    5063 +
    5064 - def endswith(self, value): +
    5065 if not self.type in ('string', 'text'): +5066 raise SyntaxError, "endswith used with incompatible field type" +5067 return Query(self.db, self.db._adapter.ENDSWITH, self, value) +
    5068 +
    5069 - def contains(self, value): +
    5070 if not self.type in ('string', 'text') and not self.type.startswith('list:'): +5071 raise SyntaxError, "contains used with incompatible field type" +5072 return Query(self.db, self.db._adapter.CONTAINS, self, value) +
    5073 +
    5074 - def with_alias(self,alias): +
    5075 return Expression(self.db,self.db._adapter.AS,self,alias,self.type) +
    5076 +5077 # for use in both Query and sortby +5078 +5079 +
    5080 -class SQLCustomType(object): +
    5081 """ +5082 allows defining of custom SQL types +5083 +5084 Example:: +5085 +5086 decimal = SQLCustomType( +5087 type ='double', +5088 native ='integer', +5089 encoder =(lambda x: int(float(x) * 100)), +5090 decoder = (lambda x: Decimal("0.00") + Decimal(str(float(x)/100)) ) +5091 ) +5092 +5093 db.define_table( +5094 'example', +5095 Field('value', type=decimal) +5096 ) +5097 +5098 :param type: the web2py type (default = 'string') +5099 :param native: the backend type +5100 :param encoder: how to encode the value to store it in the backend +5101 :param decoder: how to decode the value retrieved from the backend +5102 :param validator: what validators to use ( default = None, will use the +5103 default validator for type) +5104 """ +5105 +
    5106 - def __init__( +5107 self, +5108 type='string', +5109 native=None, +5110 encoder=None, +5111 decoder=None, +5112 validator=None, +5113 _class=None, +5114 ): +
    5115 +5116 self.type = type +5117 self.native = native +5118 self.encoder = encoder or (lambda x: x) +5119 self.decoder = decoder or (lambda x: x) +5120 self.validator = validator +5121 self._class = _class or type +
    5122 +
    5123 - def startswith(self, dummy=None): +
    5124 return False +
    5125 +
    5126 - def __getslice__(self, a=0, b=100): +
    5127 return None +
    5128 +
    5129 - def __getitem__(self, i): +
    5130 return None +
    5131 +
    5132 - def __str__(self): +
    5133 return self._class +
    5134 +5135 +
    5136 -class Field(Expression): +
    5137 +5138 """ +5139 an instance of this class represents a database field +5140 +5141 example:: +5142 +5143 a = Field(name, 'string', length=32, default=None, required=False, +5144 requires=IS_NOT_EMPTY(), ondelete='CASCADE', +5145 notnull=False, unique=False, +5146 uploadfield=True, widget=None, label=None, comment=None, +5147 uploadfield=True, # True means store on disk, +5148 # 'a_field_name' means store in this field in db +5149 # False means file content will be discarded. +5150 writable=True, readable=True, update=None, authorize=None, +5151 autodelete=False, represent=None, uploadfolder=None, +5152 uploadseparate=False # upload to separate directories by uuid_keys +5153 # first 2 character and tablename.fieldname +5154 # False - old behavior +5155 # True - put uploaded file in +5156 # <uploaddir>/<tablename>.<fieldname>/uuid_key[:2] +5157 # directory) +5158 +5159 to be used as argument of DAL.define_table +5160 +5161 allowed field types: +5162 string, boolean, integer, double, text, blob, +5163 date, time, datetime, upload, password +5164 +5165 strings must have a length of Adapter.maxcharlength by default (512 or 255 for mysql) +5166 fields should have a default or they will be required in SQLFORMs +5167 the requires argument is used to validate the field input in SQLFORMs +5168 +5169 """ +5170 +
    5171 - def __init__( +5172 self, +5173 fieldname, +5174 type='string', +5175 length=None, +5176 default=DEFAULT, +5177 required=False, +5178 requires=DEFAULT, +5179 ondelete='CASCADE', +5180 notnull=False, +5181 unique=False, +5182 uploadfield=True, +5183 widget=None, +5184 label=None, +5185 comment=None, +5186 writable=True, +5187 readable=True, +5188 update=None, +5189 authorize=None, +5190 autodelete=False, +5191 represent=None, +5192 uploadfolder=None, +5193 uploadseparate=False, +5194 compute=None, +5195 custom_store=None, +5196 custom_retrieve=None, +5197 custom_delete=None, +5198 ): +
    5199 self.db = None +5200 self.op = None +5201 self.first = None +5202 self.second = None +5203 if not isinstance(fieldname,str): +5204 raise SyntaxError, "missing field name" +5205 if fieldname.startswith(':'): +5206 fieldname,readable,writable=fieldname[1:],False,False +5207 elif fieldname.startswith('.'): +5208 fieldname,readable,writable=fieldname[1:],False,False +5209 if '=' in fieldname: +5210 fieldname,default = fieldname.split('=',1) +5211 self.name = fieldname = cleanup(fieldname) +5212 if hasattr(Table,fieldname) or fieldname[0] == '_' or \ +5213 regex_python_keywords.match(fieldname): +5214 raise SyntaxError, 'Field: invalid field name: %s' % fieldname +5215 if isinstance(type, Table): +5216 type = 'reference ' + type._tablename +5217 self.type = type # 'string', 'integer' +5218 self.length = (length is None) and MAXCHARLENGTH or length +5219 if default==DEFAULT: +5220 self.default = update or None +5221 else: +5222 self.default = default +5223 self.required = required # is this field required +5224 self.ondelete = ondelete.upper() # this is for reference fields only +5225 self.notnull = notnull +5226 self.unique = unique +5227 self.uploadfield = uploadfield +5228 self.uploadfolder = uploadfolder +5229 self.uploadseparate = uploadseparate +5230 self.widget = widget +5231 self.label = label or ' '.join(item.capitalize() for item in fieldname.split('_')) +5232 self.comment = comment +5233 self.writable = writable +5234 self.readable = readable +5235 self.update = update +5236 self.authorize = authorize +5237 self.autodelete = autodelete +5238 if not represent and type in ('list:integer','list:string'): +5239 represent=lambda x: ', '.join(str(y) for y in x or []) +5240 self.represent = represent +5241 self.compute = compute +5242 self.isattachment = True +5243 self.custom_store = custom_store +5244 self.custom_retrieve = custom_retrieve +5245 self.custom_delete = custom_delete +5246 if self.label is None: +5247 self.label = ' '.join([x.capitalize() for x in +5248 fieldname.split('_')]) +5249 if requires is None: +5250 self.requires = [] +5251 else: +5252 self.requires = requires +
    5253 +
    5254 - def store(self, file, filename=None, path=None): +
    5255 if self.custom_store: +5256 return self.custom_store(file,filename,path) +5257 if not filename: +5258 filename = file.name +5259 filename = os.path.basename(filename.replace('/', os.sep)\ +5260 .replace('\\', os.sep)) +5261 m = re.compile('\.(?P<e>\w{1,5})$').search(filename) +5262 extension = m and m.group('e') or 'txt' +5263 uuid_key = web2py_uuid().replace('-', '')[-16:] +5264 encoded_filename = base64.b16encode(filename).lower() +5265 newfilename = '%s.%s.%s.%s' % \ +5266 (self._tablename, self.name, uuid_key, encoded_filename) +5267 newfilename = newfilename[:200] + '.' + extension +5268 if isinstance(self.uploadfield,Field): +5269 blob_uploadfield_name = self.uploadfield.uploadfield +5270 keys={self.uploadfield.name: newfilename, +5271 blob_uploadfield_name: file.read()} +5272 self.uploadfield.table.insert(**keys) +5273 elif self.uploadfield == True: +5274 if path: +5275 pass +5276 elif self.uploadfolder: +5277 path = self.uploadfolder +5278 elif self.db._adapter.folder: +5279 path = os.path.join(self.db._adapter.folder, '..', 'uploads') +5280 else: +5281 raise RuntimeError, "you must specify a Field(...,uploadfolder=...)" +5282 if self.uploadseparate: +5283 path = os.path.join(path,"%s.%s" % (self._tablename, self.name),uuid_key[:2]) +5284 if not os.path.exists(path): +5285 os.makedirs(path) +5286 pathfilename = os.path.join(path, newfilename) +5287 dest_file = open(pathfilename, 'wb') +5288 try: +5289 shutil.copyfileobj(file, dest_file) +5290 finally: +5291 dest_file.close() +5292 return newfilename +
    5293 +
    5294 - def retrieve(self, name, path=None): +
    5295 if self.custom_retrieve: +5296 return self.custom_retrieve(name, path) +5297 import http +5298 if self.authorize or isinstance(self.uploadfield, str): +5299 row = self.db(self == name).select().first() +5300 if not row: +5301 raise http.HTTP(404) +5302 if self.authorize and not self.authorize(row): +5303 raise http.HTTP(403) +5304 try: +5305 m = regex_content.match(name) +5306 if not m or not self.isattachment: +5307 raise TypeError, 'Can\'t retrieve %s' % name +5308 filename = base64.b16decode(m.group('name'), True) +5309 filename = regex_cleanup_fn.sub('_', filename) +5310 except (TypeError, AttributeError): +5311 filename = name +5312 if isinstance(self.uploadfield, str): # ## if file is in DB +5313 return (filename, cStringIO.StringIO(row[self.uploadfield] or '')) +5314 elif isinstance(self.uploadfield,Field): +5315 blob_uploadfield_name = self.uploadfield.uploadfield +5316 query = self.uploadfield == name +5317 data = self.uploadfield.table(query)[blob_uploadfield_name] +5318 return (filename, cStringIO.StringIO(data)) +5319 else: +5320 # ## if file is on filesystem +5321 if path: +5322 pass +5323 elif self.uploadfolder: +5324 path = self.uploadfolder +5325 else: +5326 path = os.path.join(self.db._adapter.folder, '..', 'uploads') +5327 if self.uploadseparate: +5328 t = m.group('table') +5329 f = m.group('field') +5330 u = m.group('uuidkey') +5331 path = os.path.join(path,"%s.%s" % (t,f),u[:2]) +5332 return (filename, open(os.path.join(path, name), 'rb')) +
    5333 +
    5334 - def formatter(self, value): +
    5335 if value is None or not self.requires: +5336 return value +5337 if not isinstance(self.requires, (list, tuple)): +5338 requires = [self.requires] +5339 elif isinstance(self.requires, tuple): +5340 requires = list(self.requires) +5341 else: +5342 requires = copy.copy(self.requires) +5343 requires.reverse() +5344 for item in requires: +5345 if hasattr(item, 'formatter'): +5346 value = item.formatter(value) +5347 return value +
    5348 +
    5349 - def validate(self, value): +
    5350 if not self.requires: +5351 return (value, None) +5352 requires = self.requires +5353 if not isinstance(requires, (list, tuple)): +5354 requires = [requires] +5355 for validator in requires: +5356 (value, error) = validator(value) +5357 if error: +5358 return (value, error) +5359 return (value, None) +
    5360 +
    5361 - def count(self): +
    5362 return Expression(self.db, self.db._adapter.AGGREGATE, self, 'COUNT', 'integer') +
    5363 +
    5364 - def __nonzero__(self): +
    5365 return True +
    5366 +
    5367 - def __str__(self): +
    5368 try: +5369 return '%s.%s' % (self.tablename, self.name) +5370 except: +5371 return '<no table>.%s' % self.name +
    5372 +5373 +
    5374 -class Query(object): +
    5375 +5376 """ +5377 a query object necessary to define a set. +5378 it can be stored or can be passed to DAL.__call__() to obtain a Set +5379 +5380 Example:: +5381 +5382 query = db.users.name=='Max' +5383 set = db(query) +5384 records = set.select() +5385 +5386 """ +5387 +
    5388 - def __init__( +5389 self, +5390 db, +5391 op, +5392 first=None, +5393 second=None, +5394 ): +
    5395 self.db = db +5396 self.op = op +5397 self.first = first +5398 self.second = second +
    5399 +
    5400 - def __str__(self): +
    5401 return self.db._adapter.expand(self) +
    5402 +
    5403 - def __and__(self, other): +
    5404 return Query(self.db,self.db._adapter.AND,self,other) +
    5405 +
    5406 - def __or__(self, other): +
    5407 return Query(self.db,self.db._adapter.OR,self,other) +
    5408 +
    5409 - def __invert__(self): +
    5410 if self.op==self.db._adapter.NOT: +5411 return self.first +5412 return Query(self.db,self.db._adapter.NOT,self) +
    5413 +5414 +5415 regex_quotes = re.compile("'[^']*'") +5416 +5417 +
    5418 -def xorify(orderby): +
    5419 if not orderby: +5420 return None +5421 orderby2 = orderby[0] +5422 for item in orderby[1:]: +5423 orderby2 = orderby2 | item +5424 return orderby2 +
    5425 +5426 +
    5427 -class Set(object): +
    5428 +5429 """ +5430 a Set represents a set of records in the database, +5431 the records are identified by the query=Query(...) object. +5432 normally the Set is generated by DAL.__call__(Query(...)) +5433 +5434 given a set, for example +5435 set = db(db.users.name=='Max') +5436 you can: +5437 set.update(db.users.name='Massimo') +5438 set.delete() # all elements in the set +5439 set.select(orderby=db.users.id, groupby=db.users.name, limitby=(0,10)) +5440 and take subsets: +5441 subset = set(db.users.id<5) +5442 """ +5443 +
    5444 - def __init__(self, db, query): +
    5445 self.db = db +5446 self._db = db # for backward compatibility +5447 self.query = query +
    5448 +
    5449 - def __call__(self, query): +
    5450 if isinstance(query,Table): +5451 query = query._id>0 +5452 elif isinstance(query,Field): +5453 query = query!=None +5454 if self.query: +5455 return Set(self.db, self.query & query) +5456 else: +5457 return Set(self.db, query) +
    5458 +
    5459 - def _count(self,distinct=None): +
    5460 return self.db._adapter._count(self.query,distinct) +
    5461 +
    5462 - def _select(self, *fields, **attributes): +
    5463 return self.db._adapter._select(self.query,fields,attributes) +
    5464 +
    5465 - def _delete(self): +
    5466 tablename=self.db._adapter.get_table(self.query) +5467 return self.db._adapter._delete(tablename,self.query) +
    5468 +
    5469 - def _update(self, **update_fields): +
    5470 tablename = self.db._adapter.get_table(self.query) +5471 fields = self.db[tablename]._listify(update_fields,update=True) +5472 return self.db._adapter._update(tablename,self.query,fields) +
    5473 +
    5474 - def isempty(self): +
    5475 return not self.select(limitby=(0,1)) +
    5476 +
    5477 - def count(self,distinct=None): +
    5478 return self.db._adapter.count(self.query,distinct) +
    5479 +
    5480 - def select(self, *fields, **attributes): +
    5481 return self.db._adapter.select(self.query,fields,attributes) +
    5482 +
    5483 - def delete(self): +
    5484 tablename=self.db._adapter.get_table(self.query) +5485 self.delete_uploaded_files() +5486 return self.db._adapter.delete(tablename,self.query) +
    5487 +
    5488 - def update(self, **update_fields): +
    5489 tablename = self.db._adapter.get_table(self.query) +5490 fields = self.db[tablename]._listify(update_fields,update=True) +5491 if not fields: +5492 raise SyntaxError, "No fields to update" +5493 self.delete_uploaded_files(update_fields) +5494 return self.db._adapter.update(tablename,self.query,fields) +
    5495 +
    5496 - def validate_and_update(self, **update_fields): +
    5497 tablename = self.db._adapter.get_table(self.query) +5498 response = Row() +5499 response.errors = self.db[tablename]._validate(**update_fields) +5500 fields = self.db[tablename]._listify(update_fields,update=True) +5501 if not fields: +5502 raise SyntaxError, "No fields to update" +5503 self.delete_uploaded_files(update_fields) +5504 if not response.errors: +5505 response.updated = self.db._adapter.update(tablename,self.query,fields) +5506 else: +5507 response.updated = None +5508 return response +
    5509 +
    5510 - def delete_uploaded_files(self, upload_fields=None): +
    5511 table = self.db[self.db._adapter.tables(self.query)[0]] +5512 # ## mind uploadfield==True means file is not in DB +5513 if upload_fields: +5514 fields = upload_fields.keys() +5515 else: +5516 fields = table.fields +5517 fields = [f for f in fields if table[f].type == 'upload' +5518 and table[f].uploadfield == True +5519 and table[f].autodelete] +5520 if not fields: +5521 return +5522 for record in self.select(*[table[f] for f in fields]): +5523 for fieldname in fields: +5524 field = table[fieldname] +5525 oldname = record.get(fieldname, None) +5526 if not oldname: +5527 continue +5528 if upload_fields and oldname == upload_fields[fieldname]: +5529 continue +5530 if field.custom_delete: +5531 field.custom_delete(oldname) +5532 else: +5533 uploadfolder = field.uploadfolder +5534 if not uploadfolder: +5535 uploadfolder = os.path.join(self.db._adapter.folder, '..', 'uploads') +5536 if field.uploadseparate: +5537 items = oldname.split('.') +5538 uploadfolder = os.path.join(uploadfolder, +5539 "%s.%s" % (items[0], items[1]), +5540 items[2][:2]) +5541 oldpath = os.path.join(uploadfolder, oldname) +5542 if os.path.exists(oldpath): +5543 os.unlink(oldpath) +
    5544 +
    5545 -def update_record(pack, a={}): +
    5546 (colset, table, id) = pack +5547 b = a or dict(colset) +5548 c = dict([(k,v) for (k,v) in b.items() if k in table.fields and table[k].type!='id']) +5549 table._db(table._id==id).update(**c) +5550 for (k, v) in c.items(): +5551 colset[k] = v +
    5552 +5553 +
    5554 -class Rows(object): +
    5555 +5556 """ +5557 A wrapper for the return value of a select. It basically represents a table. +5558 It has an iterator and each row is represented as a dictionary. +5559 """ +5560 +5561 # ## TODO: this class still needs some work to care for ID/OID +5562 +
    5563 - def __init__( +5564 self, +5565 db=None, +5566 records=[], +5567 colnames=[], +5568 compact=True, +5569 rawrows=None +5570 ): +
    5571 self.db = db +5572 self.records = records +5573 self.colnames = colnames +5574 self.compact = compact +5575 self.response = rawrows +
    5576 +
    5577 - def setvirtualfields(self,**keyed_virtualfields): +
    5578 if not keyed_virtualfields: +5579 return self +5580 for row in self.records: +5581 for (tablename,virtualfields) in keyed_virtualfields.items(): +5582 attributes = dir(virtualfields) +5583 virtualfields.__dict__.update(row) +5584 if not tablename in row: +5585 box = row[tablename] = Row() +5586 else: +5587 box = row[tablename] +5588 for attribute in attributes: +5589 if attribute[0] != '_': +5590 method = getattr(virtualfields,attribute) +5591 if hasattr(method,'im_func') and method.im_func.func_code.co_argcount: +5592 box[attribute]=method() +5593 return self +
    5594 +
    5595 - def __and__(self,other): +
    5596 if self.colnames!=other.colnames: raise Exception, 'Cannot & incompatible Rows objects' +5597 records = self.records+other.records +5598 return Rows(self.db,records,self.colnames) +
    5599 +
    5600 - def __or__(self,other): +
    5601 if self.colnames!=other.colnames: raise Exception, 'Cannot | incompatible Rows objects' +5602 records = self.records +5603 records += [record for record in other.records \ +5604 if not record in records] +5605 return Rows(self.db,records,self.colnames) +
    5606 +
    5607 - def __nonzero__(self): +
    5608 if len(self.records): +5609 return 1 +5610 return 0 +
    5611 +
    5612 - def __len__(self): +
    5613 return len(self.records) +
    5614 +
    5615 - def __getslice__(self, a, b): +
    5616 return Rows(self.db,self.records[a:b],self.colnames) +
    5617 +
    5618 - def __getitem__(self, i): +
    5619 row = self.records[i] +5620 keys = row.keys() +5621 if self.compact and len(keys) == 1 and keys[0] != '_extra': +5622 return row[row.keys()[0]] +5623 return row +
    5624 +
    5625 - def __iter__(self): +
    5626 """ +5627 iterator over records +5628 """ +5629 +5630 for i in xrange(len(self)): +5631 yield self[i] +
    5632 +
    5633 - def __str__(self): +
    5634 """ +5635 serializes the table into a csv file +5636 """ +5637 +5638 s = cStringIO.StringIO() +5639 self.export_to_csv_file(s) +5640 return s.getvalue() +
    5641 +
    5642 - def first(self): +
    5643 if not self.records: +5644 return None +5645 return self[0] +
    5646 +
    5647 - def last(self): +
    5648 if not self.records: +5649 return None +5650 return self[-1] +
    5651 +
    5652 - def find(self,f): +
    5653 """ +5654 returns a new Rows object, a subset of the original object, +5655 filtered by the function f +5656 """ +5657 if not self.records: +5658 return Rows(self.db, [], self.colnames) +5659 records = [] +5660 for i in range(0,len(self)): +5661 row = self[i] +5662 if f(row): +5663 records.append(self.records[i]) +5664 return Rows(self.db, records, self.colnames) +
    5665 +
    5666 - def exclude(self, f): +
    5667 """ +5668 removes elements from the calling Rows object, filtered by the function f, +5669 and returns a new Rows object containing the removed elements +5670 """ +5671 if not self.records: +5672 return Rows(self.db, [], self.colnames) +5673 removed = [] +5674 i=0 +5675 while i<len(self): +5676 row = self[i] +5677 if f(row): +5678 removed.append(self.records[i]) +5679 del self.records[i] +5680 else: +5681 i += 1 +5682 return Rows(self.db, removed, self.colnames) +
    5683 +
    5684 - def sort(self, f, reverse=False): +
    5685 """ +5686 returns a list of sorted elements (not sorted in place) +5687 """ +5688 return Rows(self.db,sorted(self,key=f,reverse=reverse),self.colnames) +
    5689 +
    5690 - def as_list(self, +5691 compact=True, +5692 storage_to_dict=True, +5693 datetime_to_str=True): +
    5694 """ +5695 returns the data as a list or dictionary. +5696 :param storage_to_dict: when True returns a dict, otherwise a list(default True) +5697 :param datetime_to_str: convert datetime fields as strings (default True) +5698 """ +5699 (oc, self.compact) = (self.compact, compact) +5700 if storage_to_dict: +5701 items = [item.as_dict(datetime_to_str) for item in self] +5702 else: +5703 items = [item for item in self] +5704 self.compact = compact +5705 return items +
    5706 +5707 +
    5708 - def as_dict(self, +5709 key='id', +5710 compact=True, +5711 storage_to_dict=True, +5712 datetime_to_str=True): +
    5713 """ +5714 returns the data as a dictionary of dictionaries (storage_to_dict=True) or records (False) +5715 +5716 :param key: the name of the field to be used as dict key, normally the id +5717 :param compact: ? (default True) +5718 :param storage_to_dict: when True returns a dict, otherwise a list(default True) +5719 :param datetime_to_str: convert datetime fields as strings (default True) +5720 """ +5721 rows = self.as_list(compact, storage_to_dict, datetime_to_str) +5722 if isinstance(key,str) and key.count('.')==1: +5723 (table, field) = key.split('.') +5724 return dict([(r[table][field],r) for r in rows]) +5725 elif isinstance(key,str): +5726 return dict([(r[key],r) for r in rows]) +5727 else: +5728 return dict([(key(r),r) for r in rows]) +
    5729 +
    5730 - def export_to_csv_file(self, ofile, null='<NULL>', *args, **kwargs): +
    5731 """ +5732 export data to csv, the first line contains the column names +5733 +5734 :param ofile: where the csv must be exported to +5735 :param null: how null values must be represented (default '<NULL>') +5736 :param delimiter: delimiter to separate values (default ',') +5737 :param quotechar: character to use to quote string values (default '"') +5738 :param quoting: quote system, use csv.QUOTE_*** (default csv.QUOTE_MINIMAL) +5739 :param represent: use the fields .represent value (default False) +5740 :param colnames: list of column names to use (default self.colnames) +5741 This will only work when exporting rows objects!!!! +5742 DO NOT use this with db.export_to_csv() +5743 """ +5744 delimiter = kwargs.get('delimiter', ',') +5745 quotechar = kwargs.get('quotechar', '"') +5746 quoting = kwargs.get('quoting', csv.QUOTE_MINIMAL) +5747 represent = kwargs.get('represent', False) +5748 writer = csv.writer(ofile, delimiter=delimiter, +5749 quotechar=quotechar, quoting=quoting) +5750 colnames = kwargs.get('colnames', self.colnames) +5751 # a proper csv starting with the column names +5752 writer.writerow(colnames) +5753 +5754 def none_exception(value): +5755 """ +5756 returns a cleaned up value that can be used for csv export: +5757 - unicode text is encoded as such +5758 - None values are replaced with the given representation (default <NULL>) +5759 """ +5760 if value is None: +5761 return null +5762 elif isinstance(value, unicode): +5763 return value.encode('utf8') +5764 elif isinstance(value,Reference): +5765 return int(value) +5766 elif hasattr(value, 'isoformat'): +5767 return value.isoformat()[:19].replace('T', ' ') +5768 elif isinstance(value, (list,tuple)): # for type='list:..' +5769 return bar_encode(value) +5770 return value +
    5771 +5772 for record in self: +5773 row = [] +5774 for col in colnames: +5775 if not table_field.match(col): +5776 row.append(record._extra[col]) +5777 else: +5778 (t, f) = col.split('.') +5779 field = self.db[t][f] +5780 if isinstance(record.get(t, None), (Row,dict)): +5781 value = record[t][f] +5782 else: +5783 value = record[f] +5784 if field.type=='blob' and value!=None: +5785 value = base64.b64encode(value) +5786 elif represent and field.represent: +5787 value = field.represent(value) +5788 row.append(none_exception(value)) +5789 writer.writerow(row) +
    5790 +
    5791 - def xml(self): +
    5792 """ +5793 serializes the table using sqlhtml.SQLTABLE (if present) +5794 """ +5795 +5796 import sqlhtml +5797 return sqlhtml.SQLTABLE(self).xml() +
    5798 +
    5799 - def json(self, mode='object', default=None): +
    5800 """ +5801 serializes the table to a JSON list of objects +5802 """ +5803 mode = mode.lower() +5804 if not mode in ['object', 'array']: +5805 raise SyntaxError, 'Invalid JSON serialization mode: %s' % mode +5806 +5807 def inner_loop(record, col): +5808 (t, f) = col.split('.') +5809 res = None +5810 if not table_field.match(col): +5811 res = record._extra[col] +5812 else: +5813 if isinstance(record.get(t, None), Row): +5814 res = record[t][f] +5815 else: +5816 res = record[f] +5817 if mode == 'object': +5818 return (f, res) +5819 else: +5820 return res +
    5821 +5822 if mode == 'object': +5823 items = [dict([inner_loop(record, col) for col in +5824 self.colnames]) for record in self] +5825 else: +5826 items = [[inner_loop(record, col) for col in self.colnames] +5827 for record in self] +5828 if have_serializers: +5829 return serializers.json(items,default=default or serializers.custom_json) +5830 else: +5831 import simplejson +5832 return simplejson.dumps(items) +5833 +
    5834 -def Rows_unpickler(data): +
    5835 return cPickle.loads(data) +
    5836 +
    5837 -def Rows_pickler(data): +
    5838 return Rows_unpickler, \ +5839 (cPickle.dumps(data.as_list(storage_to_dict=True, +5840 datetime_to_str=False)),) +
    5841 +5842 copy_reg.pickle(Rows, Rows_pickler, Rows_unpickler) +5843 +5844 +5845 ################################################################################ +5846 # dummy function used to define some doctests +5847 ################################################################################ +5848 +
    5849 -def test_all(): +
    5850 """ +5851 +5852 >>> if len(sys.argv)<2: db = DAL(\"sqlite://test.db\") +5853 >>> if len(sys.argv)>1: db = DAL(sys.argv[1]) +5854 >>> tmp = db.define_table('users',\ +5855 Field('stringf', 'string', length=32, required=True),\ +5856 Field('booleanf', 'boolean', default=False),\ +5857 Field('passwordf', 'password', notnull=True),\ +5858 Field('uploadf', 'upload'),\ +5859 Field('blobf', 'blob'),\ +5860 Field('integerf', 'integer', unique=True),\ +5861 Field('doublef', 'double', unique=True,notnull=True),\ +5862 Field('datef', 'date', default=datetime.date.today()),\ +5863 Field('timef', 'time'),\ +5864 Field('datetimef', 'datetime'),\ +5865 migrate='test_user.table') +5866 +5867 Insert a field +5868 +5869 >>> db.users.insert(stringf='a', booleanf=True, passwordf='p', blobf='0A',\ +5870 uploadf=None, integerf=5, doublef=3.14,\ +5871 datef=datetime.date(2001, 1, 1),\ +5872 timef=datetime.time(12, 30, 15),\ +5873 datetimef=datetime.datetime(2002, 2, 2, 12, 30, 15)) +5874 1 +5875 +5876 Drop the table +5877 +5878 >>> db.users.drop() +5879 +5880 Examples of insert, select, update, delete +5881 +5882 >>> tmp = db.define_table('person',\ +5883 Field('name'),\ +5884 Field('birth','date'),\ +5885 migrate='test_person.table') +5886 >>> person_id = db.person.insert(name=\"Marco\",birth='2005-06-22') +5887 >>> person_id = db.person.insert(name=\"Massimo\",birth='1971-12-21') +5888 +5889 commented len(db().select(db.person.ALL)) +5890 commented 2 +5891 +5892 >>> me = db(db.person.id==person_id).select()[0] # test select +5893 >>> me.name +5894 'Massimo' +5895 >>> db(db.person.name=='Massimo').update(name='massimo') # test update +5896 1 +5897 >>> db(db.person.name=='Marco').select().first().delete_record() # test delete +5898 1 +5899 +5900 Update a single record +5901 +5902 >>> me.update_record(name=\"Max\") +5903 >>> me.name +5904 'Max' +5905 +5906 Examples of complex search conditions +5907 +5908 >>> len(db((db.person.name=='Max')&(db.person.birth<'2003-01-01')).select()) +5909 1 +5910 >>> len(db((db.person.name=='Max')&(db.person.birth<datetime.date(2003,01,01))).select()) +5911 1 +5912 >>> len(db((db.person.name=='Max')|(db.person.birth<'2003-01-01')).select()) +5913 1 +5914 >>> me = db(db.person.id==person_id).select(db.person.name)[0] +5915 >>> me.name +5916 'Max' +5917 +5918 Examples of search conditions using extract from date/datetime/time +5919 +5920 >>> len(db(db.person.birth.month()==12).select()) +5921 1 +5922 >>> len(db(db.person.birth.year()>1900).select()) +5923 1 +5924 +5925 Example of usage of NULL +5926 +5927 >>> len(db(db.person.birth==None).select()) ### test NULL +5928 0 +5929 >>> len(db(db.person.birth!=None).select()) ### test NULL +5930 1 +5931 +5932 Examples of search conditions using lower, upper, and like +5933 +5934 >>> len(db(db.person.name.upper()=='MAX').select()) +5935 1 +5936 >>> len(db(db.person.name.like('%ax')).select()) +5937 1 +5938 >>> len(db(db.person.name.upper().like('%AX')).select()) +5939 1 +5940 >>> len(db(~db.person.name.upper().like('%AX')).select()) +5941 0 +5942 +5943 orderby, groupby and limitby +5944 +5945 >>> people = db().select(db.person.name, orderby=db.person.name) +5946 >>> order = db.person.name|~db.person.birth +5947 >>> people = db().select(db.person.name, orderby=order) +5948 +5949 >>> people = db().select(db.person.name, orderby=db.person.name, groupby=db.person.name) +5950 +5951 >>> people = db().select(db.person.name, orderby=order, limitby=(0,100)) +5952 +5953 Example of one 2 many relation +5954 +5955 >>> tmp = db.define_table('dog',\ +5956 Field('name'),\ +5957 Field('birth','date'),\ +5958 Field('owner',db.person),\ +5959 migrate='test_dog.table') +5960 >>> db.dog.insert(name='Snoopy', birth=None, owner=person_id) +5961 1 +5962 +5963 A simple JOIN +5964 +5965 >>> len(db(db.dog.owner==db.person.id).select()) +5966 1 +5967 +5968 >>> len(db().select(db.person.ALL, db.dog.name,left=db.dog.on(db.dog.owner==db.person.id))) +5969 1 +5970 +5971 Drop tables +5972 +5973 >>> db.dog.drop() +5974 >>> db.person.drop() +5975 +5976 Example of many 2 many relation and Set +5977 +5978 >>> tmp = db.define_table('author', Field('name'),\ +5979 migrate='test_author.table') +5980 >>> tmp = db.define_table('paper', Field('title'),\ +5981 migrate='test_paper.table') +5982 >>> tmp = db.define_table('authorship',\ +5983 Field('author_id', db.author),\ +5984 Field('paper_id', db.paper),\ +5985 migrate='test_authorship.table') +5986 >>> aid = db.author.insert(name='Massimo') +5987 >>> pid = db.paper.insert(title='QCD') +5988 >>> tmp = db.authorship.insert(author_id=aid, paper_id=pid) +5989 +5990 Define a Set +5991 +5992 >>> authored_papers = db((db.author.id==db.authorship.author_id)&(db.paper.id==db.authorship.paper_id)) +5993 >>> rows = authored_papers.select(db.author.name, db.paper.title) +5994 >>> for row in rows: print row.author.name, row.paper.title +5995 Massimo QCD +5996 +5997 Example of search condition using belongs +5998 +5999 >>> set = (1, 2, 3) +6000 >>> rows = db(db.paper.id.belongs(set)).select(db.paper.ALL) +6001 >>> print rows[0].title +6002 QCD +6003 +6004 Example of search condition using nested select +6005 +6006 >>> nested_select = db()._select(db.authorship.paper_id) +6007 >>> rows = db(db.paper.id.belongs(nested_select)).select(db.paper.ALL) +6008 >>> print rows[0].title +6009 QCD +6010 +6011 Example of expressions +6012 +6013 >>> mynumber = db.define_table('mynumber', Field('x', 'integer')) +6014 >>> db(mynumber.id>0).delete() +6015 0 +6016 >>> for i in range(10): tmp = mynumber.insert(x=i) +6017 >>> db(mynumber.id>0).select(mynumber.x.sum())[0](mynumber.x.sum()) +6018 45 +6019 +6020 >>> db(mynumber.x+2==5).select(mynumber.x + 2)[0](mynumber.x + 2) +6021 5 +6022 +6023 Output in csv +6024 +6025 >>> print str(authored_papers.select(db.author.name, db.paper.title)).strip() +6026 author.name,paper.title\r +6027 Massimo,QCD +6028 +6029 Delete all leftover tables +6030 +6031 >>> DAL.distributed_transaction_commit(db) +6032 +6033 >>> db.mynumber.drop() +6034 >>> db.authorship.drop() +6035 >>> db.author.drop() +6036 >>> db.paper.drop() +6037 """ +
    6038 ################################################################################ +6039 # deprecated since the new DAL; here only for backward compatibility +6040 ################################################################################ +6041 +6042 SQLField = Field +6043 SQLTable = Table +6044 SQLXorable = Expression +6045 SQLQuery = Query +6046 SQLSet = Set +6047 SQLRows = Rows +6048 SQLStorage = Row +6049 SQLDB = DAL +6050 GQLDB = DAL +6051 DAL.Field = Field # was necessary in gluon/globals.py session.connect +6052 DAL.Table = Table # was necessary in gluon/globals.py session.connect +6053 +6054 ################################################################################ +6055 # run tests +6056 ################################################################################ +6057 +6058 if __name__ == '__main__': +6059 import doctest +6060 doctest.testmod() +6061 +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.dal.BaseAdapter-class.html Index: applications/examples/static/epydoc/web2py.gluon.dal.BaseAdapter-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.dal.BaseAdapter-class.html +++ applications/examples/static/epydoc/web2py.gluon.dal.BaseAdapter-class.html @@ -0,0 +1,1911 @@ + + + + + web2py.gluon.dal.BaseAdapter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module dal :: + Class BaseAdapter + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class BaseAdapter

    source code

    +
    +    object --+    
    +             |    
    +ConnectionPool --+
    +                 |
    +                BaseAdapter
    +
    + +
    Known Subclasses:
    +
    + NoSQLAdapter, + MySQLAdapter, + DB2Adapter, + FireBirdAdapter, + InformixAdapter, + IngresAdapter, + PostgreSQLAdapter, + SQLiteAdapter, + MSSQLAdapter, + OracleAdapter, + SAPDBAdapter +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    integrity_error(self) + source code + +
    + +
    +   + + + + + + +
    file_exists(self, + filename)
    + to be used ONLY for files that on GAE may not be on filesystem
    + source code + +
    + +
    +   + + + + + + +
    file_open(self, + filename, + mode='rb', + lock=True)
    + to be used ONLY for files that on GAE may not be on filesystem
    + source code + +
    + +
    +   + + + + + + +
    file_close(self, + fileobj, + unlock=True)
    + to be used ONLY for files that on GAE may not be on filesystem
    + source code + +
    + +
    +   + + + + + + +
    file_delete(self, + filename) + source code + +
    + +
    +   + + + + + + +
    __init__(self, + db, + uri, + pool_size=0, + folder=1, + db_codec='UTF-8', + credential_decoder=<function <lambda> at 0xd635f0>, + driver_args={}, + adapter_args={})
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    sequence_name(self, + tablename) + source code + +
    + +
    +   + + + + + + +
    trigger_name(self, + tablename) + source code + +
    + +
    +   + + + + + + +
    create_table(self, + table, + migrate=True, + fake_migrate=True, + polymodel=1) + source code + +
    + +
    +   + + + + + + +
    migrate_table(self, + table, + sql_fields, + sql_fields_old, + sql_fields_aux, + logfile, + fake_migrate=True) + source code + +
    + +
    +   + + + + + + +
    LOWER(self, + first) + source code + +
    + +
    +   + + + + + + +
    UPPER(self, + first) + source code + +
    + +
    +   + + + + + + +
    EXTRACT(self, + first, + what) + source code + +
    + +
    +   + + + + + + +
    AGGREGATE(self, + first, + what) + source code + +
    + +
    +   + + + + + + +
    JOIN(self) + source code + +
    + +
    +   + + + + + + +
    LEFT_JOIN(self) + source code + +
    + +
    +   + + + + + + +
    RANDOM(self) + source code + +
    + +
    +   + + + + + + +
    NOT_NULL(self, + default, + field_type) + source code + +
    + +
    +   + + + + + + +
    COALESCE_ZERO(self, + first) + source code + +
    + +
    +   + + + + + + +
    ALLOW_NULL(self) + source code + +
    + +
    +   + + + + + + +
    SUBSTRING(self, + field, + parameters) + source code + +
    + +
    +   + + + + + + +
    PRIMARY_KEY(self, + key) + source code + +
    + +
    +   + + + + + + +
    _drop(self, + table, + mode) + source code + +
    + +
    +   + + + + + + +
    drop(self, + table, + mode='') + source code + +
    + +
    +   + + + + + + +
    _insert(self, + table, + fields) + source code + +
    + +
    +   + + + + + + +
    insert(self, + table, + fields) + source code + +
    + +
    +   + + + + + + +
    bulk_insert(self, + table, + items) + source code + +
    + +
    +   + + + + + + +
    NOT(self, + first) + source code + +
    + +
    +   + + + + + + +
    AND(self, + first, + second) + source code + +
    + +
    +   + + + + + + +
    OR(self, + first, + second) + source code + +
    + +
    +   + + + + + + +
    BELONGS(self, + first, + second) + source code + +
    + +
    +   + + + + + + +
    LIKE(self, + first, + second) + source code + +
    + +
    +   + + + + + + +
    STARTSWITH(self, + first, + second) + source code + +
    + +
    +   + + + + + + +
    ENDSWITH(self, + first, + second) + source code + +
    + +
    +   + + + + + + +
    CONTAINS(self, + first, + second) + source code + +
    + +
    +   + + + + + + +
    EQ(self, + first, + second=1) + source code + +
    + +
    +   + + + + + + +
    NE(self, + first, + second=1) + source code + +
    + +
    +   + + + + + + +
    LT(self, + first, + second=1) + source code + +
    + +
    +   + + + + + + +
    LE(self, + first, + second=1) + source code + +
    + +
    +   + + + + + + +
    GT(self, + first, + second=1) + source code + +
    + +
    +   + + + + + + +
    GE(self, + first, + second=1) + source code + +
    + +
    +   + + + + + + +
    ADD(self, + first, + second) + source code + +
    + +
    +   + + + + + + +
    SUB(self, + first, + second) + source code + +
    + +
    +   + + + + + + +
    MUL(self, + first, + second) + source code + +
    + +
    +   + + + + + + +
    DIV(self, + first, + second) + source code + +
    + +
    +   + + + + + + +
    MOD(self, + first, + second) + source code + +
    + +
    +   + + + + + + +
    AS(self, + first, + second) + source code + +
    + +
    +   + + + + + + +
    ON(self, + first, + second) + source code + +
    + +
    +   + + + + + + +
    INVERT(self, + first) + source code + +
    + +
    +   + + + + + + +
    COMMA(self, + first, + second) + source code + +
    + +
    +   + + + + + + +
    expand(self, + expression, + field_type=1) + source code + +
    + +
    +   + + + + + + +
    alias(self, + table, + alias)
    + given a table object, makes a new table object with alias + name.
    + source code + +
    + +
    +   + + + + + + +
    _truncate(self, + table, + mode='') + source code + +
    + +
    +   + + + + + + +
    truncate(self, + table, + mode=' ') + source code + +
    + +
    +   + + + + + + +
    _update(self, + tablename, + query, + fields) + source code + +
    + +
    +   + + + + + + +
    update(self, + tablename, + query, + fields) + source code + +
    + +
    +   + + + + + + +
    _delete(self, + tablename, + query) + source code + +
    + +
    +   + + + + + + +
    delete(self, + tablename, + query) + source code + +
    + +
    +   + + + + + + +
    get_table(self, + query) + source code + +
    + +
    +   + + + + + + +
    _select(self, + query, + fields, + attributes) + source code + +
    + +
    +   + + + + + + +
    select_limitby(self, + sql_s, + sql_f, + sql_t, + sql_w, + sql_o, + limitby) + source code + +
    + +
    +   + + + + + + +
    select(self, + query, + fields, + attributes)
    + Always returns a Rows object, even if it may be empty
    + source code + +
    + +
    +   + + + + + + +
    _count(self, + query, + distinct=1) + source code + +
    + +
    +   + + + + + + +
    count(self, + query, + distinct=1) + source code + +
    + +
    +   + + + + + + +
    tables(self, + query) + source code + +
    + +
    +   + + + + + + +
    commit(self) + source code + +
    + +
    +   + + + + + + +
    rollback(self) + source code + +
    + +
    +   + + + + + + +
    close(self) + source code + +
    + +
    +   + + + + + + +
    distributed_transaction_begin(self, + key) + source code + +
    + +
    +   + + + + + + +
    prepare(self, + key) + source code + +
    + +
    +   + + + + + + +
    commit_prepared(self, + key) + source code + +
    + +
    +   + + + + + + +
    rollback_prepared(self, + key) + source code + +
    + +
    +   + + + + + + +
    concat_add(self, + table) + source code + +
    + +
    +   + + + + + + +
    constraint_name(self, + table, + fieldname) + source code + +
    + +
    +   + + + + + + +
    create_sequence_and_triggers(self, + query, + table, + **args) + source code + +
    + +
    +   + + + + + + +
    log_execute(self, + *a, + **b) + source code + +
    + +
    +   + + + + + + +
    execute(self, + *a, + **b) + source code + +
    + +
    +   + + + + + + +
    represent(self, + obj, + fieldtype) + source code + +
    + +
    +   + + + + + + +
    represent_exceptions(self, + obj, + fieldtype) + source code + +
    + +
    +   + + + + + + +
    lastrowid(self, + table) + source code + +
    + +
    +   + + + + + + +
    integrity_error_class(self) + source code + +
    + +
    +   + + + + + + +
    rowslice(self, + rows, + minimum=0, + maximum=1)
    + by default this function does nothing, overload when db does not + do slicing
    + source code + +
    + +
    +   + + + + + + +
    parse(self, + rows, + colnames, + blob_decode=True) + source code + +
    + +
    +   + + + + + + +
    filter_tenant(self, + query, + tablenames) + source code + +
    + +
    +

    Inherited from ConnectionPool: + find_or_make_work_folder, + pool_connection +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +

    Inherited from ConnectionPool: + close_all_instances, + set_folder +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + driver = 1
    + PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    +   + + maxcharlength = 32768 +
    +   + + commit_on_alter_table = True +
    +   + + support_distributed_transaction = True +
    +   + + uploads_in_blob = True +
    +   + + types = {'blob': 'BLOB', 'boolean': 'CHAR(1)', 'date': 'DATE',... +
    +

    Inherited from ConnectionPool: + pools +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + db, + uri, + pool_size=0, + folder=1, + db_codec='UTF-8', + credential_decoder=<function <lambda> at 0xd635f0>, + driver_args={}, + adapter_args={}) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + +
    + + + + + +
    Class Variable Details[hide private]
    +
    + +
    + +
    +

    types

    + +
    +
    +
    +
    Value:
    +
    +{'blob': 'BLOB',
    + 'boolean': 'CHAR(1)',
    + 'date': 'DATE',
    + 'datetime': 'TIMESTAMP',
    + 'decimal': 'DOUBLE',
    + 'double': 'DOUBLE',
    + 'id': 'INTEGER PRIMARY KEY AUTOINCREMENT',
    + 'integer': 'INTEGER',
    +...
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.dal.ConnectionPool-class.html Index: applications/examples/static/epydoc/web2py.gluon.dal.ConnectionPool-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.dal.ConnectionPool-class.html +++ applications/examples/static/epydoc/web2py.gluon.dal.ConnectionPool-class.html @@ -0,0 +1,335 @@ + + + + + web2py.gluon.dal.ConnectionPool + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module dal :: + Class ConnectionPool + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class ConnectionPool

    source code

    +
    +object --+
    +         |
    +        ConnectionPool
    +
    + +
    Known Subclasses:
    +
    + BaseAdapter +
    + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    find_or_make_work_folder(self)
    + this actually does not make the folder.
    + source code + +
    + +
    +   + + + + + + +
    pool_connection(self, + f) + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __init__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +   + + + + + + +
    set_folder(folder) + source code + +
    + +
    +   + + + + + + +
    close_all_instances(action)
    + to close cleanly databases in a multithreaded environment
    + source code + +
    + +
    + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + pools = {} +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    find_or_make_work_folder(self) +

    +
    source code  +
    + + this actually does not make the folder. it has to be there +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.dal.CouchDBAdapter-class.html Index: applications/examples/static/epydoc/web2py.gluon.dal.CouchDBAdapter-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.dal.CouchDBAdapter-class.html +++ applications/examples/static/epydoc/web2py.gluon.dal.CouchDBAdapter-class.html @@ -0,0 +1,1201 @@ + + + + + web2py.gluon.dal.CouchDBAdapter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module dal :: + Class CouchDBAdapter + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class CouchDBAdapter

    source code

    +
    +    object --+            
    +             |            
    +ConnectionPool --+        
    +                 |        
    +       BaseAdapter --+    
    +                     |    
    +          NoSQLAdapter --+
    +                         |
    +                        CouchDBAdapter
    +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    file_exists(self, + filename)
    + to be used ONLY for files that on GAE may not be on filesystem
    + source code + +
    + +
    +   + + + + + + +
    file_open(self, + filename, + mode='rb', + lock=True)
    + to be used ONLY for files that on GAE may not be on filesystem
    + source code + +
    + +
    +   + + + + + + +
    file_close(self, + fileobj, + unlock=True)
    + to be used ONLY for files that on GAE may not be on filesystem
    + source code + +
    + +
    +   + + + + + + +
    expand(self, + expression, + field_type=1) + source code + +
    + +
    +   + + + + + + +
    AND(self, + first, + second) + source code + +
    + +
    +   + + + + + + +
    OR(self, + first, + second) + source code + +
    + +
    +   + + + + + + +
    EQ(self, + first, + second) + source code + +
    + +
    +   + + + + + + +
    NE(self, + first, + second) + source code + +
    + +
    +   + + + + + + +
    COMMA(self, + first, + second) + source code + +
    + +
    +   + + + + + + +
    represent(self, + obj, + fieldtype) + source code + +
    + +
    +   + + + + + + +
    __init__(self, + db, + uri='couchdb://127.0.0.1:5984', + pool_size=0, + folder=1, + db_codec='UTF-8', + credential_decoder=<function <lambda> at 0xd73500>, + driver_args={}, + adapter_args={})
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    create_table(self, + table, + migrate=True, + fake_migrate=True, + polymodel=1) + source code + +
    + +
    +   + + + + + + +
    insert(self, + table, + fields) + source code + +
    + +
    +   + + + + + + +
    _select(self, + query, + fields, + attributes) + source code + +
    + +
    +   + + + + + + +
    select(self, + query, + fields, + attributes)
    + Always returns a Rows object, even if it may be empty
    + source code + +
    + +
    +   + + + + + + +
    delete(self, + tablename, + query) + source code + +
    + +
    +   + + + + + + +
    update(self, + tablename, + query, + fields) + source code + +
    + +
    +   + + + + + + +
    count(self, + query, + distinct=1) + source code + +
    + +
    +

    Inherited from NoSQLAdapter: + ADD, + AGGREGATE, + AS, + DIV, + ENDSWITH, + EXTRACT, + LEFT_JOIN, + LIKE, + LOWER, + MUL, + ON, + PRIMARY_KEY, + RANDOM, + STARTSWITH, + SUB, + SUBSTRING, + UPPER, + alias, + close, + commit, + commit_prepared, + concat_add, + constraint_name, + create_sequence_and_triggers, + distributed_transaction_begin, + drop, + execute, + integrity_error_class, + lastrowid, + log_execute, + migrate_table, + prepare, + represent_exceptions, + rollback, + rollback_prepared, + rowslice +

    +

    Inherited from NoSQLAdapter (private): + _count, + _delete, + _insert, + _update +

    +

    Inherited from BaseAdapter: + ALLOW_NULL, + BELONGS, + COALESCE_ZERO, + CONTAINS, + GE, + GT, + INVERT, + JOIN, + LE, + LT, + MOD, + NOT, + NOT_NULL, + bulk_insert, + file_delete, + filter_tenant, + get_table, + integrity_error, + parse, + select_limitby, + sequence_name, + tables, + trigger_name, + truncate +

    +

    Inherited from BaseAdapter (private): + _drop, + _truncate +

    +

    Inherited from ConnectionPool: + find_or_make_work_folder, + pool_connection +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +

    Inherited from NoSQLAdapter: + to_unicode +

    +

    Inherited from ConnectionPool: + close_all_instances, + set_folder +

    +
    + + + + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + uploads_in_blob = True +
    +   + + types = {'blob': <type 'str'>, 'boolean': <type 'bool'>, 'date... +
    +

    Inherited from BaseAdapter: + commit_on_alter_table, + driver, + maxcharlength, + support_distributed_transaction +

    +

    Inherited from ConnectionPool: + pools +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    file_exists(self, + filename) +

    +
    source code  +
    + + to be used ONLY for files that on GAE may not be on filesystem +
    +
    Overrides: + BaseAdapter.file_exists +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    file_open(self, + filename, + mode='rb', + lock=True) +

    +
    source code  +
    + + to be used ONLY for files that on GAE may not be on filesystem +
    +
    Overrides: + BaseAdapter.file_open +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    file_close(self, + fileobj, + unlock=True) +

    +
    source code  +
    + + to be used ONLY for files that on GAE may not be on filesystem +
    +
    Overrides: + BaseAdapter.file_close +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    expand(self, + expression, + field_type=1) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.expand +
    +
    +
    +
    + +
    + +
    + + +
    +

    AND(self, + first, + second) +

    +
    source code  +
    + + +
    +
    Overrides: + NoSQLAdapter.AND +
    +
    +
    +
    + +
    + +
    + + +
    +

    OR(self, + first, + second) +

    +
    source code  +
    + + +
    +
    Overrides: + NoSQLAdapter.OR +
    +
    +
    +
    + +
    + +
    + + +
    +

    EQ(self, + first, + second) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.EQ +
    +
    +
    +
    + +
    + +
    + + +
    +

    NE(self, + first, + second) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.NE +
    +
    +
    +
    + +
    + +
    + + +
    +

    COMMA(self, + first, + second) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.COMMA +
    +
    +
    +
    + +
    + +
    + + +
    +

    represent(self, + obj, + fieldtype) +

    +
    source code  +
    + + +
    +
    Overrides: + NoSQLAdapter.represent +
    +
    +
    +
    + +
    + +
    + + +
    +

    __init__(self, + db, + uri='couchdb://127.0.0.1:5984', + pool_size=0, + folder=1, + db_codec='UTF-8', + credential_decoder=<function <lambda> at 0xd73500>, + driver_args={}, + adapter_args={}) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + BaseAdapter.__init__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    create_table(self, + table, + migrate=True, + fake_migrate=True, + polymodel=1) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.create_table +
    +
    +
    +
    + +
    + +
    + + +
    +

    insert(self, + table, + fields) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.insert +
    +
    +
    +
    + +
    + +
    + + +
    +

    _select(self, + query, + fields, + attributes) +

    +
    source code  +
    + + +
    +
    Overrides: + NoSQLAdapter._select +
    +
    +
    +
    + +
    + +
    + + +
    +

    select(self, + query, + fields, + attributes) +

    +
    source code  +
    + + Always returns a Rows object, even if it may be empty +
    +
    Overrides: + BaseAdapter.select +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    delete(self, + tablename, + query) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.delete +
    +
    +
    +
    + +
    + +
    + + +
    +

    update(self, + tablename, + query, + fields) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.update +
    +
    +
    +
    + +
    + +
    + + +
    +

    count(self, + query, + distinct=1) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.count +
    +
    +
    +
    +
    + + + + + + +
    + + + + + +
    Class Variable Details[hide private]
    +
    + +
    + +
    +

    types

    + +
    +
    +
    +
    Value:
    +
    +{'blob': <type 'str'>,
    + 'boolean': <type 'bool'>,
    + 'date': <type 'datetime.date'>,
    + 'datetime': <type 'datetime.datetime'>,
    + 'double': <type 'float'>,
    + 'id': <type 'long'>,
    + 'integer': <type 'long'>,
    + 'list:integer': <type 'list'>,
    +...
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.dal.CubridAdapter-class.html Index: applications/examples/static/epydoc/web2py.gluon.dal.CubridAdapter-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.dal.CubridAdapter-class.html +++ applications/examples/static/epydoc/web2py.gluon.dal.CubridAdapter-class.html @@ -0,0 +1,424 @@ + + + + + web2py.gluon.dal.CubridAdapter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module dal :: + Class CubridAdapter + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class CubridAdapter

    source code

    +
    +    object --+            
    +             |            
    +ConnectionPool --+        
    +                 |        
    +       BaseAdapter --+    
    +                     |    
    +          MySQLAdapter --+
    +                         |
    +                        CubridAdapter
    +
    + +
    + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + db, + uri, + pool_size=0, + folder=1, + db_codec='UTF-8', + credential_decoder=<function <lambda> at 0xd6e488>, + driver_args={}, + adapter_args={}) + source code + +
    + +
    +

    Inherited from MySQLAdapter: + RANDOM, + SUBSTRING, + commit_prepared, + concat_add, + distributed_transaction_begin, + lastrowid, + prepare, + rollback_prepared +

    +

    Inherited from MySQLAdapter (private): + _drop +

    +

    Inherited from BaseAdapter: + ADD, + AGGREGATE, + ALLOW_NULL, + AND, + AS, + BELONGS, + COALESCE_ZERO, + COMMA, + CONTAINS, + DIV, + ENDSWITH, + EQ, + EXTRACT, + GE, + GT, + INVERT, + JOIN, + LE, + LEFT_JOIN, + LIKE, + LOWER, + LT, + MOD, + MUL, + NE, + NOT, + NOT_NULL, + ON, + OR, + PRIMARY_KEY, + STARTSWITH, + SUB, + UPPER, + alias, + bulk_insert, + close, + commit, + constraint_name, + count, + create_sequence_and_triggers, + create_table, + delete, + drop, + execute, + expand, + file_close, + file_delete, + file_exists, + file_open, + filter_tenant, + get_table, + insert, + integrity_error, + integrity_error_class, + log_execute, + migrate_table, + parse, + represent, + represent_exceptions, + rollback, + rowslice, + select, + select_limitby, + sequence_name, + tables, + trigger_name, + truncate, + update +

    +

    Inherited from BaseAdapter (private): + _count, + _delete, + _insert, + _select, + _truncate, + _update +

    +

    Inherited from ConnectionPool: + find_or_make_work_folder, + pool_connection +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +

    Inherited from ConnectionPool: + close_all_instances, + set_folder +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + driver = 1
    + PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    +

    Inherited from MySQLAdapter: + commit_on_alter_table, + maxcharlength, + support_distributed_transaction, + types +

    +

    Inherited from BaseAdapter: + uploads_in_blob +

    +

    Inherited from ConnectionPool: + pools +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + db, + uri, + pool_size=0, + folder=1, + db_codec='UTF-8', + credential_decoder=<function <lambda> at 0xd6e488>, + driver_args={}, + adapter_args={}) +
    (Constructor) +

    +
    source code  +
    + + +
    +
    Overrides: + MySQLAdapter.__init__ +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.dal.DAL-class.html Index: applications/examples/static/epydoc/web2py.gluon.dal.DAL-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.dal.DAL-class.html +++ applications/examples/static/epydoc/web2py.gluon.dal.DAL-class.html @@ -0,0 +1,1040 @@ + + + + + web2py.gluon.dal.DAL + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module dal :: + Class DAL + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class DAL

    source code

    +
    +object --+    
    +         |    
    +      dict --+
    +             |
    +            DAL
    +
    + +
    +

    an instance of this class represents a database connection

    + Example: +
    +  db = DAL('sqlite://test.db')
    +  db.define_table('tablename', Field('fieldname1'),
    +                               Field('fieldname2'))
    +


    + + + + + + + + + + + + + +
    + + + + + +
    Nested Classes[hide private]
    +
    +   + + Field
    + an instance of this class represents a database field +
    +   + + Table
    + an instance of this class represents a database table +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + uri='sqlite://dummy.db', + pool_size=0, + folder=1, + db_codec='UTF-8', + check_reserved=1, + migrate=True, + fake_migrate=True, + migrate_enabled=True, + fake_migrate_all=True, + decode_credentials=True, + driver_args=1, + adapter_args={}, + attempts=5, + auto_import=True)
    + Creates a new Database Abstraction Layer instance.
    + source code + +
    + +
    +   + + + + + + +
    import_table_definitions(self, + path, + migrate=True, + fake_migrate=True) + source code + +
    + +
    +   + + + + + + +
    check_reserved_keyword(self, + name)
    + Validates ``name`` against SQL keywords Uses self.check_reserve + which is a list of operators to use.
    + source code + +
    + +
    +   + + + + + + +
    __contains__(self, + tablename)
    + Returns: +True if D has a key k, else False
    + source code + +
    + +
    +   + + + + + + +
    parse_as_rest(self, + patterns, + args, + vars, + query=1, + nested_select=True)
    + EXAMPLE:...
    + source code + +
    + +
    +   + + + + + + +
    define_table(self, + tablename, + *fields, + **args) + source code + +
    + +
    +   + + + + + + +
    __iter__(self)
    + iter(x)
    + source code + +
    + +
    +   + + + + + + +
    __getitem__(self, + key)
    + x[y]
    + source code + +
    + +
    +   + + + + + + +
    __setitem__(self, + key, + value)
    + x[i]=y
    + source code + +
    + +
    +   + + + + + + +
    __getattr__(self, + key) + source code + +
    + +
    +   + + + + + + +
    __setattr__(self, + key, + value)
    + x.__setattr__('name', value) <==> x.name = value
    + source code + +
    + +
    +   + + + + + + +
    __repr__(self)
    + repr(x)
    + source code + +
    + +
    +   + + + + + + +
    __call__(self, + query=1) + source code + +
    + +
    +   + + + + + + +
    commit(self) + source code + +
    + +
    +   + + + + + + +
    rollback(self) + source code + +
    + +
    +   + + + + + + +
    executesql(self, + query, + placeholders=1, + as_dict=True)
    + placeholders is optional and will always be None when using DAL if + using raw SQL with placeholders, placeholders may be a sequence of + values to be substituted in or, *if supported by the DB driver*, a + dictionary with keys matching named placeholders in your SQL.
    + source code + +
    + +
    +   + + + + + + +
    _update_referenced_by(self, + other) + source code + +
    + +
    +   + + + + + + +
    export_to_csv_file(self, + ofile, + *args, + **kwargs) + source code + +
    + +
    +   + + + + + + +
    import_from_csv_file(self, + ifile, + id_map={}, + null='<NULL>', + unique='uuid', + *args, + **kwargs) + source code + +
    + +
    +

    Inherited from dict: + __cmp__, + __delitem__, + __eq__, + __ge__, + __getattribute__, + __gt__, + __hash__, + __le__, + __len__, + __lt__, + __ne__, + __new__, + clear, + copy, + fromkeys, + get, + has_key, + items, + iteritems, + iterkeys, + itervalues, + keys, + pop, + popitem, + setdefault, + update, + values +

    +

    Inherited from object: + __delattr__, + __reduce__, + __reduce_ex__, + __str__ +

    +
    + + + + + + + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +   + + + + + + +
    set_folder(folder)
    + # ## this allows gluon to set a folder for this thread # ## + <<<<<<<<< Should go away as new DAL + replaces old sql.py
    + source code + +
    + +
    +   + + + + + + +
    distributed_transaction_begin(*instances) + source code + +
    + +
    +   + + + + + + +
    distributed_transaction_commit(*instances) + source code + +
    + +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + uri='sqlite://dummy.db', + pool_size=0, + folder=1, + db_codec='UTF-8', + check_reserved=1, + migrate=True, + fake_migrate=True, + migrate_enabled=True, + fake_migrate_all=True, + decode_credentials=True, + driver_args=1, + adapter_args={}, + attempts=5, + auto_import=True) +
    (Constructor) +

    +
    source code  +
    + +
    +
    +Creates a new Database Abstraction Layer instance.
    +
    +Keyword arguments:
    +
    +:uri: string that contains information for connecting to a database.
    +       (default: 'sqlite://dummy.db')
    +:pool_size: How many open connections to make to the database object.
    +:folder: <please update me>
    +:db_codec: string encoding of the database (default: 'UTF-8')
    +:check_reserved: list of adapters to check tablenames and column names
    +                 against sql reserved keywords. (Default None)
    +
    +* 'common' List of sql keywords that are common to all database types
    +        such as "SELECT, INSERT". (recommended)
    +* 'all' Checks against all known SQL keywords. (not recommended)
    +        <adaptername> Checks against the specific adapters list of keywords
    +        (recommended)
    +* '<adaptername>_nonreserved' Checks against the specific adapters
    +        list of nonreserved keywords. (if available)
    +:migrate (defaults to True) sets default migrate behavior for all tables
    +:fake_migrate (defaults to False) sets default fake_migrate behavior for all tables
    +:migrate_enabled (defaults to True). If set to False disables ALL migrations
    +:fake_migrate_all (defaults to False). If sets to True fake migrates ALL tables
    +:attempts (defaults to 5). Number of times to attempt connecting
    +
    +
    +
    +
    Returns:
    +
    +new empty dictionary
    +
    +
    +
    Overrides: + dict.__init__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    check_reserved_keyword(self, + name) +

    +
    source code  +
    + + Validates ``name`` against SQL keywords Uses self.check_reserve which + is a list of operators to use. self.check_reserved ['common', 'postgres', + 'mysql'] self.check_reserved ['all'] +
    +
    +
    +
    + +
    + +
    + + +
    +

    __contains__(self, + tablename) +
    (In operator) +

    +
    source code  +
    + + +
    +
    Returns:
    +
    +True if D has a key k, else False
    +
    +
    +
    Overrides: + dict.__contains__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    parse_as_rest(self, + patterns, + args, + vars, + query=1, + nested_select=True) +

    +
    source code  +
    + +
    +
    +        EXAMPLE:
    +
    +db.define_table('person',Field('name'),Field('info'))
    +db.define_table('pet',Field('person',db.person),Field('name'),Field('info'))
    +
    +@request.restful()
    +def index():
    +    def GET(*kargs,**kvars):
    +        patterns = [
    +            "/persons[person]",
    +            "/{person.name.startswith}",
    +            "/{person.name}/:field",
    +            "/{person.name}/pets[pet.person]",
    +            "/{person.name}/pet[pet.person]/{pet.name}",
    +            "/{person.name}/pet[pet.person]/{pet.name}/:field"
    +            ]
    +        parser = db.parse_as_rest(patterns,kargs,kvars)
    +        if parser.status == 200:
    +            return dict(content=parser.response)
    +        else:
    +            raise HTTP(parser.status,parser.error)
    +    def POST(table_name,**kvars):
    +        if table_name == 'person':
    +            return db.person.validate_and_insert(**kvars)
    +        elif table_name == 'pet':
    +            return db.pet.validate_and_insert(**kvars)
    +        else:
    +            raise HTTP(400)
    +    return locals()
    +        
    +
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    __iter__(self) +

    +
    source code  +
    + + iter(x) +
    +
    Overrides: + dict.__iter__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    __getitem__(self, + key) +
    (Indexing operator) +

    +
    source code  +
    + + x[y] +
    +
    Overrides: + dict.__getitem__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    __setitem__(self, + key, + value) +
    (Index assignment operator) +

    +
    source code  +
    + + x[i]=y +
    +
    Overrides: + dict.__setitem__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    __setattr__(self, + key, + value) +

    +
    source code  +
    + + x.__setattr__('name', value) <==> x.name = value +
    +
    Overrides: + object.__setattr__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    __repr__(self) +
    (Representation operator) +

    +
    source code  +
    + + repr(x) +
    +
    Overrides: + dict.__repr__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    executesql(self, + query, + placeholders=1, + as_dict=True) +

    +
    source code  +
    + +

    placeholders is optional and will always be None when using DAL if + using raw SQL with placeholders, placeholders may be a sequence of values + to be substituted in or, *if supported by the DB driver*, a dictionary + with keys matching named placeholders in your SQL.

    +

    Added 2009-12-05 "as_dict" optional argument. Will always be + None when using DAL. If using raw SQL can be set to True and the results + cursor returned by the DB driver will be converted to a sequence of + dictionaries keyed with the db field names. Tested with SQLite but should + work with any database since the cursor.description used to get field + names is part of the Python dbi 2.0 specs. Results returned with as_dict + = True are the same as those returned when applying .to_list() to a DAL + query.

    +

    [{field1: value1, field2: value2}, {field1: value1b, field2: + value2b}]

    + --bmeredyk +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.dal.DB2Adapter-class.html Index: applications/examples/static/epydoc/web2py.gluon.dal.DB2Adapter-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.dal.DB2Adapter-class.html +++ applications/examples/static/epydoc/web2py.gluon.dal.DB2Adapter-class.html @@ -0,0 +1,772 @@ + + + + + web2py.gluon.dal.DB2Adapter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module dal :: + Class DB2Adapter + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class DB2Adapter

    source code

    +
    +    object --+        
    +             |        
    +ConnectionPool --+    
    +                 |    
    +       BaseAdapter --+
    +                     |
    +                    DB2Adapter
    +
    + +
    Known Subclasses:
    +
    + TeradataAdapter +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    LEFT_JOIN(self) + source code + +
    + +
    +   + + + + + + +
    RANDOM(self) + source code + +
    + +
    +   + + + + + + +
    select_limitby(self, + sql_s, + sql_f, + sql_t, + sql_w, + sql_o, + limitby) + source code + +
    + +
    +   + + + + + + +
    represent_exceptions(self, + obj, + fieldtype) + source code + +
    + +
    +   + + + + + + +
    __init__(self, + db, + uri, + pool_size=0, + folder=1, + db_codec='UTF-8', + credential_decoder=<function <lambda> at 0xd6d938>, + driver_args={}, + adapter_args={})
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    execute(self, + command) + source code + +
    + +
    +   + + + + + + +
    lastrowid(self, + table) + source code + +
    + +
    +   + + + + + + +
    rowslice(self, + rows, + minimum=0, + maximum=1)
    + by default this function does nothing, overload when db does not + do slicing
    + source code + +
    + +
    +

    Inherited from BaseAdapter: + ADD, + AGGREGATE, + ALLOW_NULL, + AND, + AS, + BELONGS, + COALESCE_ZERO, + COMMA, + CONTAINS, + DIV, + ENDSWITH, + EQ, + EXTRACT, + GE, + GT, + INVERT, + JOIN, + LE, + LIKE, + LOWER, + LT, + MOD, + MUL, + NE, + NOT, + NOT_NULL, + ON, + OR, + PRIMARY_KEY, + STARTSWITH, + SUB, + SUBSTRING, + UPPER, + alias, + bulk_insert, + close, + commit, + commit_prepared, + concat_add, + constraint_name, + count, + create_sequence_and_triggers, + create_table, + delete, + distributed_transaction_begin, + drop, + expand, + file_close, + file_delete, + file_exists, + file_open, + filter_tenant, + get_table, + insert, + integrity_error, + integrity_error_class, + log_execute, + migrate_table, + parse, + prepare, + represent, + rollback, + rollback_prepared, + select, + sequence_name, + tables, + trigger_name, + truncate, + update +

    +

    Inherited from BaseAdapter (private): + _count, + _delete, + _drop, + _insert, + _select, + _truncate, + _update +

    +

    Inherited from ConnectionPool: + find_or_make_work_folder, + pool_connection +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +

    Inherited from ConnectionPool: + close_all_instances, + set_folder +

    +
    + + + + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + driver = globals().get('pyodbc', None)
    + PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    +   + + types = {'blob': 'BLOB', 'boolean': 'CHAR(1)', 'date': 'DATE',... +
    +

    Inherited from BaseAdapter: + commit_on_alter_table, + maxcharlength, + support_distributed_transaction, + uploads_in_blob +

    +

    Inherited from ConnectionPool: + pools +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    LEFT_JOIN(self) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.LEFT_JOIN +
    +
    +
    +
    + +
    + +
    + + +
    +

    RANDOM(self) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.RANDOM +
    +
    +
    +
    + +
    + +
    + + +
    +

    select_limitby(self, + sql_s, + sql_f, + sql_t, + sql_w, + sql_o, + limitby) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.select_limitby +
    +
    +
    +
    + +
    + +
    + + +
    +

    represent_exceptions(self, + obj, + fieldtype) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.represent_exceptions +
    +
    +
    +
    + +
    + +
    + + +
    +

    __init__(self, + db, + uri, + pool_size=0, + folder=1, + db_codec='UTF-8', + credential_decoder=<function <lambda> at 0xd6d938>, + driver_args={}, + adapter_args={}) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + BaseAdapter.__init__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    execute(self, + command) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.execute +
    +
    +
    +
    + +
    + +
    + + +
    +

    lastrowid(self, + table) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.lastrowid +
    +
    +
    +
    + +
    + +
    + + +
    +

    rowslice(self, + rows, + minimum=0, + maximum=1) +

    +
    source code  +
    + + by default this function does nothing, overload when db does not do + slicing +
    +
    Overrides: + BaseAdapter.rowslice +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + +
    + + + + + +
    Class Variable Details[hide private]
    +
    + +
    + +
    +

    types

    + +
    +
    +
    +
    Value:
    +
    +{'blob': 'BLOB',
    + 'boolean': 'CHAR(1)',
    + 'date': 'DATE',
    + 'datetime': 'TIMESTAMP',
    + 'decimal': 'NUMERIC(%(precision)s,%(scale)s)',
    + 'double': 'DOUBLE',
    + 'id': 'INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL',
    + 'integer': 'INT',
    +...
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.dal.DatabaseStoredFile-class.html Index: applications/examples/static/epydoc/web2py.gluon.dal.DatabaseStoredFile-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.dal.DatabaseStoredFile-class.html +++ applications/examples/static/epydoc/web2py.gluon.dal.DatabaseStoredFile-class.html @@ -0,0 +1,279 @@ + + + + + web2py.gluon.dal.DatabaseStoredFile + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module dal :: + Class DatabaseStoredFile + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class DatabaseStoredFile

    source code

    + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + db, + filename, + mode) + source code + +
    + +
    +   + + + + + + +
    read(self, + bytes) + source code + +
    + +
    +   + + + + + + +
    readline(self) + source code + +
    + +
    +   + + + + + + +
    write(self, + data) + source code + +
    + +
    +   + + + + + + +
    close(self) + source code + +
    + +
    + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +   + + + + + + +
    exists(db, + filename) + source code + +
    + +
    + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + web2py_filesystem = True +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.dal.Expression-class.html Index: applications/examples/static/epydoc/web2py.gluon.dal.Expression-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.dal.Expression-class.html +++ applications/examples/static/epydoc/web2py.gluon.dal.Expression-class.html @@ -0,0 +1,863 @@ + + + + + web2py.gluon.dal.Expression + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module dal :: + Class Expression + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class Expression

    source code

    +
    +object --+
    +         |
    +        Expression
    +
    + +
    Known Subclasses:
    +
    + Field +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + db, + op, + first=1, + second=1, + type=1)
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    sum(self) + source code + +
    + +
    +   + + + + + + +
    max(self) + source code + +
    + +
    +   + + + + + + +
    min(self) + source code + +
    + +
    +   + + + + + + +
    len(self) + source code + +
    + +
    +   + + + + + + +
    lower(self) + source code + +
    + +
    +   + + + + + + +
    upper(self) + source code + +
    + +
    +   + + + + + + +
    year(self) + source code + +
    + +
    +   + + + + + + +
    month(self) + source code + +
    + +
    +   + + + + + + +
    day(self) + source code + +
    + +
    +   + + + + + + +
    hour(self) + source code + +
    + +
    +   + + + + + + +
    minutes(self) + source code + +
    + +
    +   + + + + + + +
    coalesce_zero(self) + source code + +
    + +
    +   + + + + + + +
    seconds(self) + source code + +
    + +
    +   + + + + + + +
    __getslice__(self, + start, + stop) + source code + +
    + +
    +   + + + + + + +
    __getitem__(self, + i) + source code + +
    + +
    +   + + + + + + +
    __str__(self)
    + str(x)
    + source code + +
    + +
    +   + + + + + + +
    __or__(self, + other) + source code + +
    + +
    +   + + + + + + +
    __invert__(self) + source code + +
    + +
    +   + + + + + + +
    __add__(self, + other) + source code + +
    + +
    +   + + + + + + +
    __sub__(self, + other) + source code + +
    + +
    +   + + + + + + +
    __mul__(self, + other) + source code + +
    + +
    +   + + + + + + +
    __div__(self, + other) + source code + +
    + +
    +   + + + + + + +
    __mod__(self, + other) + source code + +
    + +
    +   + + + + + + +
    __eq__(self, + value) + source code + +
    + +
    +   + + + + + + +
    __ne__(self, + value) + source code + +
    + +
    +   + + + + + + +
    __lt__(self, + value) + source code + +
    + +
    +   + + + + + + +
    __le__(self, + value) + source code + +
    + +
    +   + + + + + + +
    __gt__(self, + value) + source code + +
    + +
    +   + + + + + + +
    __ge__(self, + value) + source code + +
    + +
    +   + + + + + + +
    like(self, + value) + source code + +
    + +
    +   + + + + + + +
    belongs(self, + value) + source code + +
    + +
    +   + + + + + + +
    startswith(self, + value) + source code + +
    + +
    +   + + + + + + +
    endswith(self, + value) + source code + +
    + +
    +   + + + + + + +
    contains(self, + value) + source code + +
    + +
    +   + + + + + + +
    with_alias(self, + alias) + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + db, + op, + first=1, + second=1, + type=1) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    __str__(self) +
    (Informal representation operator) +

    +
    source code  +
    + + str(x) +
    +
    Overrides: + object.__str__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.dal.Field-class.html Index: applications/examples/static/epydoc/web2py.gluon.dal.Field-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.dal.Field-class.html +++ applications/examples/static/epydoc/web2py.gluon.dal.Field-class.html @@ -0,0 +1,499 @@ + + + + + web2py.gluon.dal.Field + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module dal :: + Class Field + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class Field

    source code

    +
    +object --+    
    +         |    
    +Expression --+
    +             |
    +            Field
    +
    + +
    +

    an instance of this class represents a database field

    + example: +
    +   a = Field(name, 'string', length=32, default=None, required=False,
    +       requires=IS_NOT_EMPTY(), ondelete='CASCADE',
    +       notnull=False, unique=False,
    +       uploadfield=True, widget=None, label=None, comment=None,
    +       uploadfield=True, # True means store on disk,
    +                         # 'a_field_name' means store in this field in db
    +                         # False means file content will be discarded.
    +       writable=True, readable=True, update=None, authorize=None,
    +       autodelete=False, represent=None, uploadfolder=None,
    +       uploadseparate=False # upload to separate directories by uuid_keys
    +                            # first 2 character and tablename.fieldname
    +                            # False - old behavior
    +                            # True - put uploaded file in
    +                            #   <uploaddir>/<tablename>.<fieldname>/uuid_key[:2]
    +                            #        directory)
    +
    +

    to be used as argument of DAL.define_table

    +

    allowed field types: string, boolean, integer, double, text, blob, + date, time, datetime, upload, password

    + strings must have a length of Adapter.maxcharlength by default (512 or + 255 for mysql) fields should have a default or they will be required in + SQLFORMs the requires argument is used to validate the field input in + SQLFORMs

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + fieldname, + type='string', + length=1, + default=<function <lambda> at 0xd1ec80>, + required=True, + requires=<function <lambda> at 0xd1ec80>, + ondelete='CASCADE', + notnull=True, + unique=True, + uploadfield=True, + widget=1, + label=1, + comment=1, + writable=True, + readable=True, + update=1, + authorize=1, + autodelete=True, + represent=1, + uploadfolder=1, + uploadseparate=True, + compute=1, + custom_store=1, + custom_retrieve=1, + custom_delete=1)
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    store(self, + file, + filename=1, + path=1) + source code + +
    + +
    +   + + + + + + +
    retrieve(self, + name, + path=1) + source code + +
    + +
    +   + + + + + + +
    formatter(self, + value) + source code + +
    + +
    +   + + + + + + +
    validate(self, + value) + source code + +
    + +
    +   + + + + + + +
    count(self) + source code + +
    + +
    +   + + + + + + +
    __nonzero__(self) + source code + +
    + +
    +   + + + + + + +
    __str__(self)
    + str(x)
    + source code + +
    + +
    +

    Inherited from Expression: + __add__, + __div__, + __eq__, + __ge__, + __getitem__, + __getslice__, + __gt__, + __invert__, + __le__, + __lt__, + __mod__, + __mul__, + __ne__, + __or__, + __sub__, + belongs, + coalesce_zero, + contains, + day, + endswith, + hour, + len, + like, + lower, + max, + min, + minutes, + month, + seconds, + startswith, + sum, + upper, + with_alias, + year +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + fieldname, + type='string', + length=1, + default=<function <lambda> at 0xd1ec80>, + required=True, + requires=<function <lambda> at 0xd1ec80>, + ondelete='CASCADE', + notnull=True, + unique=True, + uploadfield=True, + widget=1, + label=1, + comment=1, + writable=True, + readable=True, + update=1, + authorize=1, + autodelete=True, + represent=1, + uploadfolder=1, + uploadseparate=True, + compute=1, + custom_store=1, + custom_retrieve=1, + custom_delete=1) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + Expression.__init__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    __str__(self) +
    (Informal representation operator) +

    +
    source code  +
    + + str(x) +
    +
    Overrides: + Expression.__str__ +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.dal.FireBirdAdapter-class.html Index: applications/examples/static/epydoc/web2py.gluon.dal.FireBirdAdapter-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.dal.FireBirdAdapter-class.html +++ applications/examples/static/epydoc/web2py.gluon.dal.FireBirdAdapter-class.html @@ -0,0 +1,905 @@ + + + + + web2py.gluon.dal.FireBirdAdapter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module dal :: + Class FireBirdAdapter + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class FireBirdAdapter

    source code

    +
    +    object --+        
    +             |        
    +ConnectionPool --+    
    +                 |    
    +       BaseAdapter --+
    +                     |
    +                    FireBirdAdapter
    +
    + +
    Known Subclasses:
    +
    + FireBirdEmbeddedAdapter +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    sequence_name(self, + tablename) + source code + +
    + +
    +   + + + + + + +
    trigger_name(self, + tablename) + source code + +
    + +
    +   + + + + + + +
    RANDOM(self) + source code + +
    + +
    +   + + + + + + +
    NOT_NULL(self, + default, + field_type) + source code + +
    + +
    +   + + + + + + +
    SUBSTRING(self, + field, + parameters) + source code + +
    + +
    +   + + + + + + +
    _drop(self, + table, + mode) + source code + +
    + +
    +   + + + + + + +
    select_limitby(self, + sql_s, + sql_f, + sql_t, + sql_w, + sql_o, + limitby) + source code + +
    + +
    +   + + + + + + +
    _truncate(self, + table, + mode='') + source code + +
    + +
    +   + + + + + + +
    __init__(self, + db, + uri, + pool_size=0, + folder=1, + db_codec='UTF-8', + credential_decoder=<function <lambda> at 0xd6d050>, + driver_args={}, + adapter_args={})
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    create_sequence_and_triggers(self, + query, + table, + **args) + source code + +
    + +
    +   + + + + + + +
    lastrowid(self, + table) + source code + +
    + +
    +

    Inherited from BaseAdapter: + ADD, + AGGREGATE, + ALLOW_NULL, + AND, + AS, + BELONGS, + COALESCE_ZERO, + COMMA, + CONTAINS, + DIV, + ENDSWITH, + EQ, + EXTRACT, + GE, + GT, + INVERT, + JOIN, + LE, + LEFT_JOIN, + LIKE, + LOWER, + LT, + MOD, + MUL, + NE, + NOT, + ON, + OR, + PRIMARY_KEY, + STARTSWITH, + SUB, + UPPER, + alias, + bulk_insert, + close, + commit, + commit_prepared, + concat_add, + constraint_name, + count, + create_table, + delete, + distributed_transaction_begin, + drop, + execute, + expand, + file_close, + file_delete, + file_exists, + file_open, + filter_tenant, + get_table, + insert, + integrity_error, + integrity_error_class, + log_execute, + migrate_table, + parse, + prepare, + represent, + represent_exceptions, + rollback, + rollback_prepared, + rowslice, + select, + tables, + truncate, + update +

    +

    Inherited from BaseAdapter (private): + _count, + _delete, + _insert, + _select, + _update +

    +

    Inherited from ConnectionPool: + find_or_make_work_folder, + pool_connection +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +

    Inherited from ConnectionPool: + close_all_instances, + set_folder +

    +
    + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + driver = globals().get('pyodbc', None)
    + PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    +   + + commit_on_alter_table = True +
    +   + + support_distributed_transaction = True +
    +   + + types = {'blob': 'BLOB SUB_TYPE 0', 'boolean': 'CHAR(1)', 'dat... +
    +

    Inherited from BaseAdapter: + maxcharlength, + uploads_in_blob +

    +

    Inherited from ConnectionPool: + pools +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    sequence_name(self, + tablename) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.sequence_name +
    +
    +
    +
    + +
    + +
    + + +
    +

    trigger_name(self, + tablename) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.trigger_name +
    +
    +
    +
    + +
    + +
    + + +
    +

    RANDOM(self) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.RANDOM +
    +
    +
    +
    + +
    + +
    + + +
    +

    NOT_NULL(self, + default, + field_type) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.NOT_NULL +
    +
    +
    +
    + +
    + +
    + + +
    +

    SUBSTRING(self, + field, + parameters) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.SUBSTRING +
    +
    +
    +
    + +
    + +
    + + +
    +

    _drop(self, + table, + mode) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter._drop +
    +
    +
    +
    + +
    + +
    + + +
    +

    select_limitby(self, + sql_s, + sql_f, + sql_t, + sql_w, + sql_o, + limitby) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.select_limitby +
    +
    +
    +
    + +
    + +
    + + +
    +

    _truncate(self, + table, + mode='') +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter._truncate +
    +
    +
    +
    + +
    + +
    + + +
    +

    __init__(self, + db, + uri, + pool_size=0, + folder=1, + db_codec='UTF-8', + credential_decoder=<function <lambda> at 0xd6d050>, + driver_args={}, + adapter_args={}) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + BaseAdapter.__init__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    create_sequence_and_triggers(self, + query, + table, + **args) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.create_sequence_and_triggers +
    +
    +
    +
    + +
    + +
    + + +
    +

    lastrowid(self, + table) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.lastrowid +
    +
    +
    +
    +
    + + + + + + +
    + + + + + +
    Class Variable Details[hide private]
    +
    + +
    + +
    +

    types

    + +
    +
    +
    +
    Value:
    +
    +{'blob': 'BLOB SUB_TYPE 0',
    + 'boolean': 'CHAR(1)',
    + 'date': 'DATE',
    + 'datetime': 'TIMESTAMP',
    + 'decimal': 'DECIMAL(%(precision)s,%(scale)s)',
    + 'double': 'DOUBLE PRECISION',
    + 'id': 'INTEGER PRIMARY KEY',
    + 'integer': 'INTEGER',
    +...
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.dal.FireBirdEmbeddedAdapter-class.html Index: applications/examples/static/epydoc/web2py.gluon.dal.FireBirdEmbeddedAdapter-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.dal.FireBirdEmbeddedAdapter-class.html +++ applications/examples/static/epydoc/web2py.gluon.dal.FireBirdEmbeddedAdapter-class.html @@ -0,0 +1,420 @@ + + + + + web2py.gluon.dal.FireBirdEmbeddedAdapter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module dal :: + Class FireBirdEmbeddedAdapter + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class FireBirdEmbeddedAdapter

    source code

    +
    +    object --+            
    +             |            
    +ConnectionPool --+        
    +                 |        
    +       BaseAdapter --+    
    +                     |    
    +       FireBirdAdapter --+
    +                         |
    +                        FireBirdEmbeddedAdapter
    +
    + +
    + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + db, + uri, + pool_size=0, + folder=1, + db_codec='UTF-8', + credential_decoder=<function <lambda> at 0xd6d230>, + driver_args={}, + adapter_args={})
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +

    Inherited from FireBirdAdapter: + NOT_NULL, + RANDOM, + SUBSTRING, + create_sequence_and_triggers, + lastrowid, + select_limitby, + sequence_name, + trigger_name +

    +

    Inherited from FireBirdAdapter (private): + _drop, + _truncate +

    +

    Inherited from BaseAdapter: + ADD, + AGGREGATE, + ALLOW_NULL, + AND, + AS, + BELONGS, + COALESCE_ZERO, + COMMA, + CONTAINS, + DIV, + ENDSWITH, + EQ, + EXTRACT, + GE, + GT, + INVERT, + JOIN, + LE, + LEFT_JOIN, + LIKE, + LOWER, + LT, + MOD, + MUL, + NE, + NOT, + ON, + OR, + PRIMARY_KEY, + STARTSWITH, + SUB, + UPPER, + alias, + bulk_insert, + close, + commit, + commit_prepared, + concat_add, + constraint_name, + count, + create_table, + delete, + distributed_transaction_begin, + drop, + execute, + expand, + file_close, + file_delete, + file_exists, + file_open, + filter_tenant, + get_table, + insert, + integrity_error, + integrity_error_class, + log_execute, + migrate_table, + parse, + prepare, + represent, + represent_exceptions, + rollback, + rollback_prepared, + rowslice, + select, + tables, + truncate, + update +

    +

    Inherited from BaseAdapter (private): + _count, + _delete, + _insert, + _select, + _update +

    +

    Inherited from ConnectionPool: + find_or_make_work_folder, + pool_connection +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +

    Inherited from ConnectionPool: + close_all_instances, + set_folder +

    +
    + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +

    Inherited from FireBirdAdapter: + commit_on_alter_table, + driver, + support_distributed_transaction, + types +

    +

    Inherited from BaseAdapter: + maxcharlength, + uploads_in_blob +

    +

    Inherited from ConnectionPool: + pools +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + db, + uri, + pool_size=0, + folder=1, + db_codec='UTF-8', + credential_decoder=<function <lambda> at 0xd6d230>, + driver_args={}, + adapter_args={}) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + FireBirdAdapter.__init__ +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.dal.GAEDecimalProperty-class.html Index: applications/examples/static/epydoc/web2py.gluon.dal.GAEDecimalProperty-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.dal.GAEDecimalProperty-class.html +++ applications/examples/static/epydoc/web2py.gluon.dal.GAEDecimalProperty-class.html @@ -0,0 +1,238 @@ + + + + + web2py.gluon.dal.GAEDecimalProperty + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module dal :: + Class GAEDecimalProperty + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class GAEDecimalProperty

    source code

    +
    +google.appengine.ext.db.Property --+
    +                                   |
    +                                  GAEDecimalProperty
    +
    + +
    +GAE decimal implementation

    + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + precision, + scale, + **kwargs) + source code + +
    + +
    +   + + + + + + +
    get_value_for_datastore(self, + model_instance) + source code + +
    + +
    +   + + + + + + +
    make_value_from_datastore(self, + value) + source code + +
    + +
    +   + + + + + + +
    validate(self, + value) + source code + +
    + +
    + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + data_type = decimal.Decimal +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.dal.GAEF-class.html Index: applications/examples/static/epydoc/web2py.gluon.dal.GAEF-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.dal.GAEF-class.html +++ applications/examples/static/epydoc/web2py.gluon.dal.GAEF-class.html @@ -0,0 +1,291 @@ + + + + + web2py.gluon.dal.GAEF + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module dal :: + Class GAEF + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class GAEF

    source code

    +
    +object --+
    +         |
    +        GAEF
    +
    + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + name, + op, + value, + apply)
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    __repr__(self)
    + repr(x)
    + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + name, + op, + value, + apply) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    __repr__(self) +
    (Representation operator) +

    +
    source code  +
    + + repr(x) +
    +
    Overrides: + object.__repr__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.dal.GoogleDatastoreAdapter-class.html Index: applications/examples/static/epydoc/web2py.gluon.dal.GoogleDatastoreAdapter-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.dal.GoogleDatastoreAdapter-class.html +++ applications/examples/static/epydoc/web2py.gluon.dal.GoogleDatastoreAdapter-class.html @@ -0,0 +1,1453 @@ + + + + + web2py.gluon.dal.GoogleDatastoreAdapter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module dal :: + Class GoogleDatastoreAdapter + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class GoogleDatastoreAdapter

    source code

    +
    +    object --+            
    +             |            
    +ConnectionPool --+        
    +                 |        
    +       BaseAdapter --+    
    +                     |    
    +          NoSQLAdapter --+
    +                         |
    +                        GoogleDatastoreAdapter
    +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    file_exists(self, + filename)
    + to be used ONLY for files that on GAE may not be on filesystem
    + source code + +
    + +
    +   + + + + + + +
    file_open(self, + filename, + mode='rb', + lock=True)
    + to be used ONLY for files that on GAE may not be on filesystem
    + source code + +
    + +
    +   + + + + + + +
    file_close(self, + fileobj, + unlock=True)
    + to be used ONLY for files that on GAE may not be on filesystem
    + source code + +
    + +
    +   + + + + + + +
    __init__(self, + db, + uri, + pool_size=0, + folder=1, + db_codec='UTF-8', + credential_decoder=<function <lambda> at 0xd71398>, + driver_args={}, + adapter_args={})
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    create_table(self, + table, + migrate=True, + fake_migrate=True, + polymodel=1) + source code + +
    + +
    +   + + + + + + +
    expand(self, + expression, + field_type=1) + source code + +
    + +
    +   + + + + + + +
    AND(self, + first, + second) + source code + +
    + +
    +   + + + + + + +
    EQ(self, + first, + second=1) + source code + +
    + +
    +   + + + + + + +
    NE(self, + first, + second=1) + source code + +
    + +
    +   + + + + + + +
    LT(self, + first, + second=1) + source code + +
    + +
    +   + + + + + + +
    LE(self, + first, + second=1) + source code + +
    + +
    +   + + + + + + +
    GT(self, + first, + second=1) + source code + +
    + +
    +   + + + + + + +
    GE(self, + first, + second=1) + source code + +
    + +
    +   + + + + + + +
    INVERT(self, + first) + source code + +
    + +
    +   + + + + + + +
    COMMA(self, + first, + second) + source code + +
    + +
    +   + + + + + + +
    BELONGS(self, + first, + second=1) + source code + +
    + +
    +   + + + + + + +
    CONTAINS(self, + first, + second) + source code + +
    + +
    +   + + + + + + +
    NOT(self, + first) + source code + +
    + +
    +   + + + + + + +
    truncate(self, + table, + mode) + source code + +
    + +
    +   + + + + + + +
    select_raw(self, + query, + fields=[], + attributes={}) + source code + +
    + +
    +   + + + + + + +
    select(self, + query, + fields, + attributes)
    + Always returns a Rows object, even if it may be empty
    + source code + +
    + +
    +   + + + + + + +
    count(self, + query, + distinct=1) + source code + +
    + +
    +   + + + + + + +
    delete(self, + tablename, + query)
    + This function was changed on 2010-05-04 because according to + http://code.google.com/p/googleappengine/issues/detail?id=3119 GAE no + longer support deleting more than 1000 records.
    + source code + +
    + +
    +   + + + + + + +
    update(self, + tablename, + query, + update_fields) + source code + +
    + +
    +   + + + + + + +
    insert(self, + table, + fields) + source code + +
    + +
    +   + + + + + + +
    bulk_insert(self, + table, + items) + source code + +
    + +
    +

    Inherited from NoSQLAdapter: + ADD, + AGGREGATE, + AS, + DIV, + ENDSWITH, + EXTRACT, + LEFT_JOIN, + LIKE, + LOWER, + MUL, + ON, + OR, + PRIMARY_KEY, + RANDOM, + STARTSWITH, + SUB, + SUBSTRING, + UPPER, + alias, + close, + commit, + commit_prepared, + concat_add, + constraint_name, + create_sequence_and_triggers, + distributed_transaction_begin, + drop, + execute, + integrity_error_class, + lastrowid, + log_execute, + migrate_table, + prepare, + represent, + represent_exceptions, + rollback, + rollback_prepared, + rowslice +

    +

    Inherited from NoSQLAdapter (private): + _count, + _delete, + _insert, + _select, + _update +

    +

    Inherited from BaseAdapter: + ALLOW_NULL, + COALESCE_ZERO, + JOIN, + MOD, + NOT_NULL, + file_delete, + filter_tenant, + get_table, + integrity_error, + parse, + select_limitby, + sequence_name, + tables, + trigger_name +

    +

    Inherited from BaseAdapter (private): + _drop, + _truncate +

    +

    Inherited from ConnectionPool: + find_or_make_work_folder, + pool_connection +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +

    Inherited from NoSQLAdapter: + to_unicode +

    +

    Inherited from ConnectionPool: + close_all_instances, + set_folder +

    +
    + + + + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + uploads_in_blob = True +
    +   + + types = {} +
    +

    Inherited from BaseAdapter: + commit_on_alter_table, + driver, + maxcharlength, + support_distributed_transaction +

    +

    Inherited from ConnectionPool: + pools +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    file_exists(self, + filename) +

    +
    source code  +
    + + to be used ONLY for files that on GAE may not be on filesystem +
    +
    Overrides: + BaseAdapter.file_exists +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    file_open(self, + filename, + mode='rb', + lock=True) +

    +
    source code  +
    + + to be used ONLY for files that on GAE may not be on filesystem +
    +
    Overrides: + BaseAdapter.file_open +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    file_close(self, + fileobj, + unlock=True) +

    +
    source code  +
    + + to be used ONLY for files that on GAE may not be on filesystem +
    +
    Overrides: + BaseAdapter.file_close +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    __init__(self, + db, + uri, + pool_size=0, + folder=1, + db_codec='UTF-8', + credential_decoder=<function <lambda> at 0xd71398>, + driver_args={}, + adapter_args={}) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + BaseAdapter.__init__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    create_table(self, + table, + migrate=True, + fake_migrate=True, + polymodel=1) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.create_table +
    +
    +
    +
    + +
    + +
    + + +
    +

    expand(self, + expression, + field_type=1) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.expand +
    +
    +
    +
    + +
    + +
    + + +
    +

    AND(self, + first, + second) +

    +
    source code  +
    + + +
    +
    Overrides: + NoSQLAdapter.AND +
    +
    +
    +
    + +
    + +
    + + +
    +

    EQ(self, + first, + second=1) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.EQ +
    +
    +
    +
    + +
    + +
    + + +
    +

    NE(self, + first, + second=1) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.NE +
    +
    +
    +
    + +
    + +
    + + +
    +

    LT(self, + first, + second=1) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.LT +
    +
    +
    +
    + +
    + +
    + + +
    +

    LE(self, + first, + second=1) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.LE +
    +
    +
    +
    + +
    + +
    + + +
    +

    GT(self, + first, + second=1) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.GT +
    +
    +
    +
    + +
    + +
    + + +
    +

    GE(self, + first, + second=1) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.GE +
    +
    +
    +
    + +
    + +
    + + +
    +

    INVERT(self, + first) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.INVERT +
    +
    +
    +
    + +
    + +
    + + +
    +

    COMMA(self, + first, + second) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.COMMA +
    +
    +
    +
    + +
    + +
    + + +
    +

    BELONGS(self, + first, + second=1) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.BELONGS +
    +
    +
    +
    + +
    + +
    + + +
    +

    CONTAINS(self, + first, + second) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.CONTAINS +
    +
    +
    +
    + +
    + +
    + + +
    +

    NOT(self, + first) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.NOT +
    +
    +
    +
    + +
    + +
    + + +
    +

    truncate(self, + table, + mode) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.truncate +
    +
    +
    +
    + +
    + +
    + + +
    +

    select(self, + query, + fields, + attributes) +

    +
    source code  +
    + + Always returns a Rows object, even if it may be empty +
    +
    Overrides: + BaseAdapter.select +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    count(self, + query, + distinct=1) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.count +
    +
    +
    +
    + +
    + +
    + + +
    +

    delete(self, + tablename, + query) +

    +
    source code  +
    + + This function was changed on 2010-05-04 because according to + http://code.google.com/p/googleappengine/issues/detail?id=3119 GAE no + longer support deleting more than 1000 records. +
    +
    Overrides: + BaseAdapter.delete +
    +
    +
    +
    + +
    + +
    + + +
    +

    update(self, + tablename, + query, + update_fields) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.update +
    +
    +
    +
    + +
    + +
    + + +
    +

    insert(self, + table, + fields) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.insert +
    +
    +
    +
    + +
    + +
    + + +
    +

    bulk_insert(self, + table, + items) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.bulk_insert +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.dal.GoogleSQLAdapter-class.html Index: applications/examples/static/epydoc/web2py.gluon.dal.GoogleSQLAdapter-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.dal.GoogleSQLAdapter-class.html +++ applications/examples/static/epydoc/web2py.gluon.dal.GoogleSQLAdapter-class.html @@ -0,0 +1,427 @@ + + + + + web2py.gluon.dal.GoogleSQLAdapter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module dal :: + Class GoogleSQLAdapter + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class GoogleSQLAdapter

    source code

    +
    + UseDatabaseStoredFile --+
    +                         |
    +    object --+           |
    +             |           |
    +ConnectionPool --+       |
    +                 |       |
    +       BaseAdapter --+   |
    +                     |   |
    +          MySQLAdapter --+
    +                         |
    +                        GoogleSQLAdapter
    +
    + +
    + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + db, + uri='google:sql://realm:domain/database', + pool_size=0, + folder=1, + db_codec='UTF-8', + check_reserved=1, + migrate=True, + fake_migrate=True, + credential_decoder=<function <lambda> at 0xd6ea28>, + driver_args={}, + adapter_args={}) + source code + +
    + +
    +

    Inherited from UseDatabaseStoredFile: + file_close, + file_delete, + file_exists, + file_open +

    +

    Inherited from MySQLAdapter: + RANDOM, + SUBSTRING, + commit_prepared, + concat_add, + distributed_transaction_begin, + lastrowid, + prepare, + rollback_prepared +

    +

    Inherited from MySQLAdapter (private): + _drop +

    +

    Inherited from BaseAdapter: + ADD, + AGGREGATE, + ALLOW_NULL, + AND, + AS, + BELONGS, + COALESCE_ZERO, + COMMA, + CONTAINS, + DIV, + ENDSWITH, + EQ, + EXTRACT, + GE, + GT, + INVERT, + JOIN, + LE, + LEFT_JOIN, + LIKE, + LOWER, + LT, + MOD, + MUL, + NE, + NOT, + NOT_NULL, + ON, + OR, + PRIMARY_KEY, + STARTSWITH, + SUB, + UPPER, + alias, + bulk_insert, + close, + commit, + constraint_name, + count, + create_sequence_and_triggers, + create_table, + delete, + drop, + execute, + expand, + filter_tenant, + get_table, + insert, + integrity_error, + integrity_error_class, + log_execute, + migrate_table, + parse, + represent, + represent_exceptions, + rollback, + rowslice, + select, + select_limitby, + sequence_name, + tables, + trigger_name, + truncate, + update +

    +

    Inherited from BaseAdapter (private): + _count, + _delete, + _insert, + _select, + _truncate, + _update +

    +

    Inherited from ConnectionPool: + find_or_make_work_folder, + pool_connection +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +

    Inherited from ConnectionPool: + close_all_instances, + set_folder +

    +
    + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +

    Inherited from MySQLAdapter: + commit_on_alter_table, + driver, + maxcharlength, + support_distributed_transaction, + types +

    +

    Inherited from BaseAdapter: + uploads_in_blob +

    +

    Inherited from ConnectionPool: + pools +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + db, + uri='google:sql://realm:domain/database', + pool_size=0, + folder=1, + db_codec='UTF-8', + check_reserved=1, + migrate=True, + fake_migrate=True, + credential_decoder=<function <lambda> at 0xd6ea28>, + driver_args={}, + adapter_args={}) +
    (Constructor) +

    +
    source code  +
    + + +
    +
    Overrides: + MySQLAdapter.__init__ +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.dal.InformixAdapter-class.html Index: applications/examples/static/epydoc/web2py.gluon.dal.InformixAdapter-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.dal.InformixAdapter-class.html +++ applications/examples/static/epydoc/web2py.gluon.dal.InformixAdapter-class.html @@ -0,0 +1,761 @@ + + + + + web2py.gluon.dal.InformixAdapter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module dal :: + Class InformixAdapter + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class InformixAdapter

    source code

    +
    +    object --+        
    +             |        
    +ConnectionPool --+    
    +                 |    
    +       BaseAdapter --+
    +                     |
    +                    InformixAdapter
    +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    RANDOM(self) + source code + +
    + +
    +   + + + + + + +
    NOT_NULL(self, + default, + field_type) + source code + +
    + +
    +   + + + + + + +
    select_limitby(self, + sql_s, + sql_f, + sql_t, + sql_w, + sql_o, + limitby) + source code + +
    + +
    +   + + + + + + +
    represent_exceptions(self, + obj, + fieldtype) + source code + +
    + +
    +   + + + + + + +
    __init__(self, + db, + uri, + pool_size=0, + folder=1, + db_codec='UTF-8', + credential_decoder=<function <lambda> at 0xd6d500>, + driver_args={}, + adapter_args={})
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    execute(self, + command) + source code + +
    + +
    +   + + + + + + +
    lastrowid(self, + table) + source code + +
    + +
    +   + + + + + + +
    integrity_error_class(self) + source code + +
    + +
    +

    Inherited from BaseAdapter: + ADD, + AGGREGATE, + ALLOW_NULL, + AND, + AS, + BELONGS, + COALESCE_ZERO, + COMMA, + CONTAINS, + DIV, + ENDSWITH, + EQ, + EXTRACT, + GE, + GT, + INVERT, + JOIN, + LE, + LEFT_JOIN, + LIKE, + LOWER, + LT, + MOD, + MUL, + NE, + NOT, + ON, + OR, + PRIMARY_KEY, + STARTSWITH, + SUB, + SUBSTRING, + UPPER, + alias, + bulk_insert, + close, + commit, + commit_prepared, + concat_add, + constraint_name, + count, + create_sequence_and_triggers, + create_table, + delete, + distributed_transaction_begin, + drop, + expand, + file_close, + file_delete, + file_exists, + file_open, + filter_tenant, + get_table, + insert, + integrity_error, + log_execute, + migrate_table, + parse, + prepare, + represent, + rollback, + rollback_prepared, + rowslice, + select, + sequence_name, + tables, + trigger_name, + truncate, + update +

    +

    Inherited from BaseAdapter (private): + _count, + _delete, + _drop, + _insert, + _select, + _truncate, + _update +

    +

    Inherited from ConnectionPool: + find_or_make_work_folder, + pool_connection +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +

    Inherited from ConnectionPool: + close_all_instances, + set_folder +

    +
    + + + + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + driver = globals().get('informixdb', None)
    + PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    +   + + types = {'blob': 'BLOB SUB_TYPE 0', 'boolean': 'CHAR(1)', 'dat... +
    +

    Inherited from BaseAdapter: + commit_on_alter_table, + maxcharlength, + support_distributed_transaction, + uploads_in_blob +

    +

    Inherited from ConnectionPool: + pools +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    RANDOM(self) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.RANDOM +
    +
    +
    +
    + +
    + +
    + + +
    +

    NOT_NULL(self, + default, + field_type) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.NOT_NULL +
    +
    +
    +
    + +
    + +
    + + +
    +

    select_limitby(self, + sql_s, + sql_f, + sql_t, + sql_w, + sql_o, + limitby) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.select_limitby +
    +
    +
    +
    + +
    + +
    + + +
    +

    represent_exceptions(self, + obj, + fieldtype) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.represent_exceptions +
    +
    +
    +
    + +
    + +
    + + +
    +

    __init__(self, + db, + uri, + pool_size=0, + folder=1, + db_codec='UTF-8', + credential_decoder=<function <lambda> at 0xd6d500>, + driver_args={}, + adapter_args={}) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + BaseAdapter.__init__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    execute(self, + command) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.execute +
    +
    +
    +
    + +
    + +
    + + +
    +

    lastrowid(self, + table) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.lastrowid +
    +
    +
    +
    + +
    + +
    + + +
    +

    integrity_error_class(self) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.integrity_error_class +
    +
    +
    +
    +
    + + + + + + +
    + + + + + +
    Class Variable Details[hide private]
    +
    + +
    + +
    +

    types

    + +
    +
    +
    +
    Value:
    +
    +{'blob': 'BLOB SUB_TYPE 0',
    + 'boolean': 'CHAR(1)',
    + 'date': 'DATE',
    + 'datetime': 'DATETIME',
    + 'decimal': 'NUMERIC(%(precision)s,%(scale)s)',
    + 'double': 'FLOAT',
    + 'id': 'SERIAL',
    + 'integer': 'INTEGER',
    +...
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.dal.IngresAdapter-class.html Index: applications/examples/static/epydoc/web2py.gluon.dal.IngresAdapter-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.dal.IngresAdapter-class.html +++ applications/examples/static/epydoc/web2py.gluon.dal.IngresAdapter-class.html @@ -0,0 +1,725 @@ + + + + + web2py.gluon.dal.IngresAdapter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module dal :: + Class IngresAdapter + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class IngresAdapter

    source code

    +
    +    object --+        
    +             |        
    +ConnectionPool --+    
    +                 |    
    +       BaseAdapter --+
    +                     |
    +                    IngresAdapter
    +
    + +
    Known Subclasses:
    +
    + IngresUnicodeAdapter +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    LEFT_JOIN(self) + source code + +
    + +
    +   + + + + + + +
    RANDOM(self) + source code + +
    + +
    +   + + + + + + +
    select_limitby(self, + sql_s, + sql_f, + sql_t, + sql_w, + sql_o, + limitby) + source code + +
    + +
    +   + + + + + + +
    __init__(self, + db, + uri, + pool_size=0, + folder=1, + db_codec='UTF-8', + credential_decoder=<function <lambda> at 0xd6de60>, + driver_args={}, + adapter_args={})
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    create_sequence_and_triggers(self, + query, + table, + **args) + source code + +
    + +
    +   + + + + + + +
    lastrowid(self, + table) + source code + +
    + +
    +   + + + + + + +
    integrity_error_class(self) + source code + +
    + +
    +

    Inherited from BaseAdapter: + ADD, + AGGREGATE, + ALLOW_NULL, + AND, + AS, + BELONGS, + COALESCE_ZERO, + COMMA, + CONTAINS, + DIV, + ENDSWITH, + EQ, + EXTRACT, + GE, + GT, + INVERT, + JOIN, + LE, + LIKE, + LOWER, + LT, + MOD, + MUL, + NE, + NOT, + NOT_NULL, + ON, + OR, + PRIMARY_KEY, + STARTSWITH, + SUB, + SUBSTRING, + UPPER, + alias, + bulk_insert, + close, + commit, + commit_prepared, + concat_add, + constraint_name, + count, + create_table, + delete, + distributed_transaction_begin, + drop, + execute, + expand, + file_close, + file_delete, + file_exists, + file_open, + filter_tenant, + get_table, + insert, + integrity_error, + log_execute, + migrate_table, + parse, + prepare, + represent, + represent_exceptions, + rollback, + rollback_prepared, + rowslice, + select, + sequence_name, + tables, + trigger_name, + truncate, + update +

    +

    Inherited from BaseAdapter (private): + _count, + _delete, + _drop, + _insert, + _select, + _truncate, + _update +

    +

    Inherited from ConnectionPool: + find_or_make_work_folder, + pool_connection +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +

    Inherited from ConnectionPool: + close_all_instances, + set_folder +

    +
    + + + + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + driver = globals().get('ingresdbi', None)
    + PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    +   + + types = {'blob': 'BLOB', 'boolean': 'CHAR(1)', 'date': 'ANSIDA... +
    +

    Inherited from BaseAdapter: + commit_on_alter_table, + maxcharlength, + support_distributed_transaction, + uploads_in_blob +

    +

    Inherited from ConnectionPool: + pools +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    LEFT_JOIN(self) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.LEFT_JOIN +
    +
    +
    +
    + +
    + +
    + + +
    +

    RANDOM(self) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.RANDOM +
    +
    +
    +
    + +
    + +
    + + +
    +

    select_limitby(self, + sql_s, + sql_f, + sql_t, + sql_w, + sql_o, + limitby) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.select_limitby +
    +
    +
    +
    + +
    + +
    + + +
    +

    __init__(self, + db, + uri, + pool_size=0, + folder=1, + db_codec='UTF-8', + credential_decoder=<function <lambda> at 0xd6de60>, + driver_args={}, + adapter_args={}) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + BaseAdapter.__init__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    create_sequence_and_triggers(self, + query, + table, + **args) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.create_sequence_and_triggers +
    +
    +
    +
    + +
    + +
    + + +
    +

    lastrowid(self, + table) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.lastrowid +
    +
    +
    +
    + +
    + +
    + + +
    +

    integrity_error_class(self) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.integrity_error_class +
    +
    +
    +
    +
    + + + + + + +
    + + + + + +
    Class Variable Details[hide private]
    +
    + +
    + +
    +

    types

    + +
    +
    +
    +
    Value:
    +
    +{'blob': 'BLOB',
    + 'boolean': 'CHAR(1)',
    + 'date': 'ANSIDATE',
    + 'datetime': 'TIMESTAMP WITHOUT TIME ZONE',
    + 'decimal': 'NUMERIC(%(precision)s,%(scale)s)',
    + 'double': 'FLOAT8',
    + 'id': 'integer4 not null unique with default next value for ii***line\
    +itemsequence',
    +...
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.dal.IngresUnicodeAdapter-class.html Index: applications/examples/static/epydoc/web2py.gluon.dal.IngresUnicodeAdapter-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.dal.IngresUnicodeAdapter-class.html +++ applications/examples/static/epydoc/web2py.gluon.dal.IngresUnicodeAdapter-class.html @@ -0,0 +1,401 @@ + + + + + web2py.gluon.dal.IngresUnicodeAdapter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module dal :: + Class IngresUnicodeAdapter + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class IngresUnicodeAdapter

    source code

    +
    +    object --+            
    +             |            
    +ConnectionPool --+        
    +                 |        
    +       BaseAdapter --+    
    +                     |    
    +         IngresAdapter --+
    +                         |
    +                        IngresUnicodeAdapter
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from IngresAdapter: + LEFT_JOIN, + RANDOM, + __init__, + create_sequence_and_triggers, + integrity_error_class, + lastrowid, + select_limitby +

    +

    Inherited from BaseAdapter: + ADD, + AGGREGATE, + ALLOW_NULL, + AND, + AS, + BELONGS, + COALESCE_ZERO, + COMMA, + CONTAINS, + DIV, + ENDSWITH, + EQ, + EXTRACT, + GE, + GT, + INVERT, + JOIN, + LE, + LIKE, + LOWER, + LT, + MOD, + MUL, + NE, + NOT, + NOT_NULL, + ON, + OR, + PRIMARY_KEY, + STARTSWITH, + SUB, + SUBSTRING, + UPPER, + alias, + bulk_insert, + close, + commit, + commit_prepared, + concat_add, + constraint_name, + count, + create_table, + delete, + distributed_transaction_begin, + drop, + execute, + expand, + file_close, + file_delete, + file_exists, + file_open, + filter_tenant, + get_table, + insert, + integrity_error, + log_execute, + migrate_table, + parse, + prepare, + represent, + represent_exceptions, + rollback, + rollback_prepared, + rowslice, + select, + sequence_name, + tables, + trigger_name, + truncate, + update +

    +

    Inherited from BaseAdapter (private): + _count, + _delete, + _drop, + _insert, + _select, + _truncate, + _update +

    +

    Inherited from ConnectionPool: + find_or_make_work_folder, + pool_connection +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +

    Inherited from ConnectionPool: + close_all_instances, + set_folder +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + types = {'blob': 'BLOB', 'boolean': 'CHAR(1)', 'date': 'ANSIDA... +
    +

    Inherited from IngresAdapter: + driver +

    +

    Inherited from BaseAdapter: + commit_on_alter_table, + maxcharlength, + support_distributed_transaction, + uploads_in_blob +

    +

    Inherited from ConnectionPool: + pools +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Class Variable Details[hide private]
    +
    + +
    + +
    +

    types

    + +
    +
    +
    +
    Value:
    +
    +{'blob': 'BLOB',
    + 'boolean': 'CHAR(1)',
    + 'date': 'ANSIDATE',
    + 'datetime': 'TIMESTAMP WITHOUT TIME ZONE',
    + 'decimal': 'NUMERIC(%(precision)s,%(scale)s)',
    + 'double': 'FLOAT8',
    + 'id': 'integer4 not null unique with default next value for ii***line\
    +itemsequence',
    +...
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.dal.JDBCPostgreSQLAdapter-class.html Index: applications/examples/static/epydoc/web2py.gluon.dal.JDBCPostgreSQLAdapter-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.dal.JDBCPostgreSQLAdapter-class.html +++ applications/examples/static/epydoc/web2py.gluon.dal.JDBCPostgreSQLAdapter-class.html @@ -0,0 +1,415 @@ + + + + + web2py.gluon.dal.JDBCPostgreSQLAdapter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module dal :: + Class JDBCPostgreSQLAdapter + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class JDBCPostgreSQLAdapter

    source code

    +
    +    object --+            
    +             |            
    +ConnectionPool --+        
    +                 |        
    +       BaseAdapter --+    
    +                     |    
    +     PostgreSQLAdapter --+
    +                         |
    +                        JDBCPostgreSQLAdapter
    +
    + +
    + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + db, + uri, + pool_size=0, + folder=1, + db_codec='UTF-8', + credential_decoder=<function <lambda> at 0xd6acf8>, + driver_args={}, + adapter_args={}) + source code + +
    + +
    +

    Inherited from PostgreSQLAdapter: + CONTAINS, + ENDSWITH, + LIKE, + RANDOM, + STARTSWITH, + commit_prepared, + create_sequence_and_triggers, + distributed_transaction_begin, + lastrowid, + prepare, + rollback_prepared, + sequence_name +

    +

    Inherited from BaseAdapter: + ADD, + AGGREGATE, + ALLOW_NULL, + AND, + AS, + BELONGS, + COALESCE_ZERO, + COMMA, + DIV, + EQ, + EXTRACT, + GE, + GT, + INVERT, + JOIN, + LE, + LEFT_JOIN, + LOWER, + LT, + MOD, + MUL, + NE, + NOT, + NOT_NULL, + ON, + OR, + PRIMARY_KEY, + SUB, + SUBSTRING, + UPPER, + alias, + bulk_insert, + close, + commit, + concat_add, + constraint_name, + count, + create_table, + delete, + drop, + execute, + expand, + file_close, + file_delete, + file_exists, + file_open, + filter_tenant, + get_table, + insert, + integrity_error, + integrity_error_class, + log_execute, + migrate_table, + parse, + represent, + represent_exceptions, + rollback, + rowslice, + select, + select_limitby, + tables, + trigger_name, + truncate, + update +

    +

    Inherited from BaseAdapter (private): + _count, + _delete, + _drop, + _insert, + _select, + _truncate, + _update +

    +

    Inherited from ConnectionPool: + find_or_make_work_folder, + pool_connection +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +

    Inherited from ConnectionPool: + close_all_instances, + set_folder +

    +
    + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +

    Inherited from PostgreSQLAdapter: + driver, + support_distributed_transaction, + types +

    +

    Inherited from BaseAdapter: + commit_on_alter_table, + maxcharlength, + uploads_in_blob +

    +

    Inherited from ConnectionPool: + pools +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + db, + uri, + pool_size=0, + folder=1, + db_codec='UTF-8', + credential_decoder=<function <lambda> at 0xd6acf8>, + driver_args={}, + adapter_args={}) +
    (Constructor) +

    +
    source code  +
    + + +
    +
    Overrides: + PostgreSQLAdapter.__init__ +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.dal.JDBCSQLiteAdapter-class.html Index: applications/examples/static/epydoc/web2py.gluon.dal.JDBCSQLiteAdapter-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.dal.JDBCSQLiteAdapter-class.html +++ applications/examples/static/epydoc/web2py.gluon.dal.JDBCSQLiteAdapter-class.html @@ -0,0 +1,464 @@ + + + + + web2py.gluon.dal.JDBCSQLiteAdapter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module dal :: + Class JDBCSQLiteAdapter + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class JDBCSQLiteAdapter

    source code

    +
    +    object --+            
    +             |            
    +ConnectionPool --+        
    +                 |        
    +       BaseAdapter --+    
    +                     |    
    +         SQLiteAdapter --+
    +                         |
    +                        JDBCSQLiteAdapter
    +
    + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + db, + uri, + pool_size=0, + folder=1, + db_codec='UTF-8', + credential_decoder=<function <lambda> at 0xd69f50>, + driver_args={}, + adapter_args={}) + source code + +
    + +
    +   + + + + + + +
    execute(self, + a) + source code + +
    + +
    +

    Inherited from SQLiteAdapter: + EXTRACT, + lastrowid +

    +

    Inherited from SQLiteAdapter (private): + _truncate +

    +

    Inherited from BaseAdapter: + ADD, + AGGREGATE, + ALLOW_NULL, + AND, + AS, + BELONGS, + COALESCE_ZERO, + COMMA, + CONTAINS, + DIV, + ENDSWITH, + EQ, + GE, + GT, + INVERT, + JOIN, + LE, + LEFT_JOIN, + LIKE, + LOWER, + LT, + MOD, + MUL, + NE, + NOT, + NOT_NULL, + ON, + OR, + PRIMARY_KEY, + RANDOM, + STARTSWITH, + SUB, + SUBSTRING, + UPPER, + alias, + bulk_insert, + close, + commit, + commit_prepared, + concat_add, + constraint_name, + count, + create_sequence_and_triggers, + create_table, + delete, + distributed_transaction_begin, + drop, + expand, + file_close, + file_delete, + file_exists, + file_open, + filter_tenant, + get_table, + insert, + integrity_error, + integrity_error_class, + log_execute, + migrate_table, + parse, + prepare, + represent, + represent_exceptions, + rollback, + rollback_prepared, + rowslice, + select, + select_limitby, + sequence_name, + tables, + trigger_name, + truncate, + update +

    +

    Inherited from BaseAdapter (private): + _count, + _delete, + _drop, + _insert, + _select, + _update +

    +

    Inherited from ConnectionPool: + find_or_make_work_folder, + pool_connection +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +

    Inherited from SQLiteAdapter: + web2py_extract +

    +

    Inherited from ConnectionPool: + close_all_instances, + set_folder +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + driver = 1
    + PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    +

    Inherited from BaseAdapter: + commit_on_alter_table, + maxcharlength, + support_distributed_transaction, + types, + uploads_in_blob +

    +

    Inherited from ConnectionPool: + pools +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + db, + uri, + pool_size=0, + folder=1, + db_codec='UTF-8', + credential_decoder=<function <lambda> at 0xd69f50>, + driver_args={}, + adapter_args={}) +
    (Constructor) +

    +
    source code  +
    + + +
    +
    Overrides: + SQLiteAdapter.__init__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    execute(self, + a) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.execute +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.dal.MSSQL2Adapter-class.html Index: applications/examples/static/epydoc/web2py.gluon.dal.MSSQL2Adapter-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.dal.MSSQL2Adapter-class.html +++ applications/examples/static/epydoc/web2py.gluon.dal.MSSQL2Adapter-class.html @@ -0,0 +1,500 @@ + + + + + web2py.gluon.dal.MSSQL2Adapter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module dal :: + Class MSSQL2Adapter + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class MSSQL2Adapter

    source code

    +
    +    object --+            
    +             |            
    +ConnectionPool --+        
    +                 |        
    +       BaseAdapter --+    
    +                     |    
    +          MSSQLAdapter --+
    +                         |
    +                        MSSQL2Adapter
    +
    + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    represent(self, + obj, + fieldtype) + source code + +
    + +
    +   + + + + + + +
    execute(self, + a) + source code + +
    + +
    +

    Inherited from MSSQLAdapter: + ALLOW_NULL, + EXTRACT, + LEFT_JOIN, + PRIMARY_KEY, + RANDOM, + SUBSTRING, + __init__, + integrity_error_class, + lastrowid, + represent_exceptions, + rowslice, + select_limitby +

    +

    Inherited from BaseAdapter: + ADD, + AGGREGATE, + AND, + AS, + BELONGS, + COALESCE_ZERO, + COMMA, + CONTAINS, + DIV, + ENDSWITH, + EQ, + GE, + GT, + INVERT, + JOIN, + LE, + LIKE, + LOWER, + LT, + MOD, + MUL, + NE, + NOT, + NOT_NULL, + ON, + OR, + STARTSWITH, + SUB, + UPPER, + alias, + bulk_insert, + close, + commit, + commit_prepared, + concat_add, + constraint_name, + count, + create_sequence_and_triggers, + create_table, + delete, + distributed_transaction_begin, + drop, + expand, + file_close, + file_delete, + file_exists, + file_open, + filter_tenant, + get_table, + insert, + integrity_error, + log_execute, + migrate_table, + parse, + prepare, + rollback, + rollback_prepared, + select, + sequence_name, + tables, + trigger_name, + truncate, + update +

    +

    Inherited from BaseAdapter (private): + _count, + _delete, + _drop, + _insert, + _select, + _truncate, + _update +

    +

    Inherited from ConnectionPool: + find_or_make_work_folder, + pool_connection +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +

    Inherited from ConnectionPool: + close_all_instances, + set_folder +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + types = {'blob': 'IMAGE', 'boolean': 'CHAR(1)', 'date': 'DATET... +
    +

    Inherited from MSSQLAdapter: + driver +

    +

    Inherited from BaseAdapter: + commit_on_alter_table, + maxcharlength, + support_distributed_transaction, + uploads_in_blob +

    +

    Inherited from ConnectionPool: + pools +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    represent(self, + obj, + fieldtype) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.represent +
    +
    +
    +
    + +
    + +
    + + +
    +

    execute(self, + a) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.execute +
    +
    +
    +
    +
    + + + + + + +
    + + + + + +
    Class Variable Details[hide private]
    +
    + +
    + +
    +

    types

    + +
    +
    +
    +
    Value:
    +
    +{'blob': 'IMAGE',
    + 'boolean': 'CHAR(1)',
    + 'date': 'DATETIME',
    + 'datetime': 'DATETIME',
    + 'decimal': 'NUMERIC(%(precision)s,%(scale)s)',
    + 'double': 'FLOAT',
    + 'id': 'INT IDENTITY PRIMARY KEY',
    + 'integer': 'INT',
    +...
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.dal.MSSQLAdapter-class.html Index: applications/examples/static/epydoc/web2py.gluon.dal.MSSQLAdapter-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.dal.MSSQLAdapter-class.html +++ applications/examples/static/epydoc/web2py.gluon.dal.MSSQLAdapter-class.html @@ -0,0 +1,930 @@ + + + + + web2py.gluon.dal.MSSQLAdapter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module dal :: + Class MSSQLAdapter + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class MSSQLAdapter

    source code

    +
    +    object --+        
    +             |        
    +ConnectionPool --+    
    +                 |    
    +       BaseAdapter --+
    +                     |
    +                    MSSQLAdapter
    +
    + +
    Known Subclasses:
    +
    + MSSQL2Adapter +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    EXTRACT(self, + field, + what) + source code + +
    + +
    +   + + + + + + +
    LEFT_JOIN(self) + source code + +
    + +
    +   + + + + + + +
    RANDOM(self) + source code + +
    + +
    +   + + + + + + +
    ALLOW_NULL(self) + source code + +
    + +
    +   + + + + + + +
    SUBSTRING(self, + field, + parameters) + source code + +
    + +
    +   + + + + + + +
    PRIMARY_KEY(self, + key) + source code + +
    + +
    +   + + + + + + +
    select_limitby(self, + sql_s, + sql_f, + sql_t, + sql_w, + sql_o, + limitby) + source code + +
    + +
    +   + + + + + + +
    represent_exceptions(self, + obj, + fieldtype) + source code + +
    + +
    +   + + + + + + +
    __init__(self, + db, + uri, + pool_size=0, + folder=1, + db_codec='UTF-8', + credential_decoder=<function <lambda> at 0xd6b8c0>, + driver_args={}, + adapter_args={}, + fake_connect=True)
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    lastrowid(self, + table) + source code + +
    + +
    +   + + + + + + +
    integrity_error_class(self) + source code + +
    + +
    +   + + + + + + +
    rowslice(self, + rows, + minimum=0, + maximum=1)
    + by default this function does nothing, overload when db does not + do slicing
    + source code + +
    + +
    +

    Inherited from BaseAdapter: + ADD, + AGGREGATE, + AND, + AS, + BELONGS, + COALESCE_ZERO, + COMMA, + CONTAINS, + DIV, + ENDSWITH, + EQ, + GE, + GT, + INVERT, + JOIN, + LE, + LIKE, + LOWER, + LT, + MOD, + MUL, + NE, + NOT, + NOT_NULL, + ON, + OR, + STARTSWITH, + SUB, + UPPER, + alias, + bulk_insert, + close, + commit, + commit_prepared, + concat_add, + constraint_name, + count, + create_sequence_and_triggers, + create_table, + delete, + distributed_transaction_begin, + drop, + execute, + expand, + file_close, + file_delete, + file_exists, + file_open, + filter_tenant, + get_table, + insert, + integrity_error, + log_execute, + migrate_table, + parse, + prepare, + represent, + rollback, + rollback_prepared, + select, + sequence_name, + tables, + trigger_name, + truncate, + update +

    +

    Inherited from BaseAdapter (private): + _count, + _delete, + _drop, + _insert, + _select, + _truncate, + _update +

    +

    Inherited from ConnectionPool: + find_or_make_work_folder, + pool_connection +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +

    Inherited from ConnectionPool: + close_all_instances, + set_folder +

    +
    + + + + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + driver = globals().get('pyodbc', None)
    + PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    +   + + types = {'blob': 'IMAGE', 'boolean': 'BIT', 'date': 'DATETIME'... +
    +

    Inherited from BaseAdapter: + commit_on_alter_table, + maxcharlength, + support_distributed_transaction, + uploads_in_blob +

    +

    Inherited from ConnectionPool: + pools +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    EXTRACT(self, + field, + what) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.EXTRACT +
    +
    +
    +
    + +
    + +
    + + +
    +

    LEFT_JOIN(self) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.LEFT_JOIN +
    +
    +
    +
    + +
    + +
    + + +
    +

    RANDOM(self) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.RANDOM +
    +
    +
    +
    + +
    + +
    + + +
    +

    ALLOW_NULL(self) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.ALLOW_NULL +
    +
    +
    +
    + +
    + +
    + + +
    +

    SUBSTRING(self, + field, + parameters) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.SUBSTRING +
    +
    +
    +
    + +
    + +
    + + +
    +

    PRIMARY_KEY(self, + key) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.PRIMARY_KEY +
    +
    +
    +
    + +
    + +
    + + +
    +

    select_limitby(self, + sql_s, + sql_f, + sql_t, + sql_w, + sql_o, + limitby) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.select_limitby +
    +
    +
    +
    + +
    + +
    + + +
    +

    represent_exceptions(self, + obj, + fieldtype) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.represent_exceptions +
    +
    +
    +
    + +
    + +
    + + +
    +

    __init__(self, + db, + uri, + pool_size=0, + folder=1, + db_codec='UTF-8', + credential_decoder=<function <lambda> at 0xd6b8c0>, + driver_args={}, + adapter_args={}, + fake_connect=True) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + BaseAdapter.__init__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    lastrowid(self, + table) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.lastrowid +
    +
    +
    +
    + +
    + +
    + + +
    +

    integrity_error_class(self) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.integrity_error_class +
    +
    +
    +
    + +
    + +
    + + +
    +

    rowslice(self, + rows, + minimum=0, + maximum=1) +

    +
    source code  +
    + + by default this function does nothing, overload when db does not do + slicing +
    +
    Overrides: + BaseAdapter.rowslice +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + +
    + + + + + +
    Class Variable Details[hide private]
    +
    + +
    + +
    +

    types

    + +
    +
    +
    +
    Value:
    +
    +{'blob': 'IMAGE',
    + 'boolean': 'BIT',
    + 'date': 'DATETIME',
    + 'datetime': 'DATETIME',
    + 'decimal': 'NUMERIC(%(precision)s,%(scale)s)',
    + 'double': 'FLOAT',
    + 'id': 'INT IDENTITY PRIMARY KEY',
    + 'integer': 'INT',
    +...
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.dal.MongoDBAdapter-class.html Index: applications/examples/static/epydoc/web2py.gluon.dal.MongoDBAdapter-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.dal.MongoDBAdapter-class.html +++ applications/examples/static/epydoc/web2py.gluon.dal.MongoDBAdapter-class.html @@ -0,0 +1,695 @@ + + + + + web2py.gluon.dal.MongoDBAdapter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module dal :: + Class MongoDBAdapter + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class MongoDBAdapter

    source code

    +
    +    object --+            
    +             |            
    +ConnectionPool --+        
    +                 |        
    +       BaseAdapter --+    
    +                     |    
    +          NoSQLAdapter --+
    +                         |
    +                        MongoDBAdapter
    +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + db, + uri='mongodb://127.0.0.1:5984/db', + pool_size=0, + folder=1, + db_codec='UTF-8', + credential_decoder=<function <lambda> at 0xd739b0>, + driver_args={}, + adapter_args={})
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    insert(self, + table, + fields) + source code + +
    + +
    +   + + + + + + +
    count(self, + query) + source code + +
    + +
    +   + + + + + + +
    select(self, + query, + fields, + attributes)
    + Always returns a Rows object, even if it may be empty
    + source code + +
    + +
    +   + + + + + + +
    delete(self, + tablename, + query) + source code + +
    + +
    +   + + + + + + +
    update(self, + tablename, + query, + fields) + source code + +
    + +
    +

    Inherited from NoSQLAdapter: + ADD, + AGGREGATE, + AND, + AS, + DIV, + ENDSWITH, + EXTRACT, + LEFT_JOIN, + LIKE, + LOWER, + MUL, + ON, + OR, + PRIMARY_KEY, + RANDOM, + STARTSWITH, + SUB, + SUBSTRING, + UPPER, + alias, + close, + commit, + commit_prepared, + concat_add, + constraint_name, + create_sequence_and_triggers, + distributed_transaction_begin, + drop, + execute, + integrity_error_class, + lastrowid, + log_execute, + migrate_table, + prepare, + represent, + represent_exceptions, + rollback, + rollback_prepared, + rowslice +

    +

    Inherited from NoSQLAdapter (private): + _count, + _delete, + _insert, + _select, + _update +

    +

    Inherited from BaseAdapter: + ALLOW_NULL, + BELONGS, + COALESCE_ZERO, + COMMA, + CONTAINS, + EQ, + GE, + GT, + INVERT, + JOIN, + LE, + LT, + MOD, + NE, + NOT, + NOT_NULL, + bulk_insert, + create_table, + expand, + file_close, + file_delete, + file_exists, + file_open, + filter_tenant, + get_table, + integrity_error, + parse, + select_limitby, + sequence_name, + tables, + trigger_name, + truncate +

    +

    Inherited from BaseAdapter (private): + _drop, + _truncate +

    +

    Inherited from ConnectionPool: + find_or_make_work_folder, + pool_connection +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +

    Inherited from NoSQLAdapter: + to_unicode +

    +

    Inherited from ConnectionPool: + close_all_instances, + set_folder +

    +
    + + + + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + uploads_in_blob = True +
    +   + + types = {'blob': <type 'str'>, 'boolean': <type 'bool'>, 'date... +
    +

    Inherited from BaseAdapter: + commit_on_alter_table, + driver, + maxcharlength, + support_distributed_transaction +

    +

    Inherited from ConnectionPool: + pools +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + db, + uri='mongodb://127.0.0.1:5984/db', + pool_size=0, + folder=1, + db_codec='UTF-8', + credential_decoder=<function <lambda> at 0xd739b0>, + driver_args={}, + adapter_args={}) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + BaseAdapter.__init__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    insert(self, + table, + fields) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.insert +
    +
    +
    +
    + +
    + +
    + + +
    +

    count(self, + query) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.count +
    +
    +
    +
    + +
    + +
    + + +
    +

    select(self, + query, + fields, + attributes) +

    +
    source code  +
    + + Always returns a Rows object, even if it may be empty +
    +
    Overrides: + BaseAdapter.select +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    delete(self, + tablename, + query) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.delete +
    +
    +
    +
    + +
    + +
    + + +
    +

    update(self, + tablename, + query, + fields) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.update +
    +
    +
    +
    +
    + + + + + + +
    + + + + + +
    Class Variable Details[hide private]
    +
    + +
    + +
    +

    types

    + +
    +
    +
    +
    Value:
    +
    +{'blob': <type 'str'>,
    + 'boolean': <type 'bool'>,
    + 'date': <type 'datetime.date'>,
    + 'datetime': <type 'datetime.datetime'>,
    + 'double': <type 'float'>,
    + 'id': <type 'long'>,
    + 'integer': <type 'long'>,
    + 'list:integer': <type 'list'>,
    +...
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.dal.MySQLAdapter-class.html Index: applications/examples/static/epydoc/web2py.gluon.dal.MySQLAdapter-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.dal.MySQLAdapter-class.html +++ applications/examples/static/epydoc/web2py.gluon.dal.MySQLAdapter-class.html @@ -0,0 +1,887 @@ + + + + + web2py.gluon.dal.MySQLAdapter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module dal :: + Class MySQLAdapter + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class MySQLAdapter

    source code

    +
    +    object --+        
    +             |        
    +ConnectionPool --+    
    +                 |    
    +       BaseAdapter --+
    +                     |
    +                    MySQLAdapter
    +
    + +
    Known Subclasses:
    +
    + CubridAdapter, + GoogleSQLAdapter +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    RANDOM(self) + source code + +
    + +
    +   + + + + + + +
    SUBSTRING(self, + field, + parameters) + source code + +
    + +
    +   + + + + + + +
    _drop(self, + table, + mode) + source code + +
    + +
    +   + + + + + + +
    distributed_transaction_begin(self, + key) + source code + +
    + +
    +   + + + + + + +
    prepare(self, + key) + source code + +
    + +
    +   + + + + + + +
    commit_prepared(self, + ley) + source code + +
    + +
    +   + + + + + + +
    rollback_prepared(self, + key) + source code + +
    + +
    +   + + + + + + +
    concat_add(self, + table) + source code + +
    + +
    +   + + + + + + +
    __init__(self, + db, + uri, + pool_size=0, + folder=1, + db_codec='UTF-8', + credential_decoder=<function <lambda> at 0xd6a500>, + driver_args={}, + adapter_args={})
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    lastrowid(self, + table) + source code + +
    + +
    +

    Inherited from BaseAdapter: + ADD, + AGGREGATE, + ALLOW_NULL, + AND, + AS, + BELONGS, + COALESCE_ZERO, + COMMA, + CONTAINS, + DIV, + ENDSWITH, + EQ, + EXTRACT, + GE, + GT, + INVERT, + JOIN, + LE, + LEFT_JOIN, + LIKE, + LOWER, + LT, + MOD, + MUL, + NE, + NOT, + NOT_NULL, + ON, + OR, + PRIMARY_KEY, + STARTSWITH, + SUB, + UPPER, + alias, + bulk_insert, + close, + commit, + constraint_name, + count, + create_sequence_and_triggers, + create_table, + delete, + drop, + execute, + expand, + file_close, + file_delete, + file_exists, + file_open, + filter_tenant, + get_table, + insert, + integrity_error, + integrity_error_class, + log_execute, + migrate_table, + parse, + represent, + represent_exceptions, + rollback, + rowslice, + select, + select_limitby, + sequence_name, + tables, + trigger_name, + truncate, + update +

    +

    Inherited from BaseAdapter (private): + _count, + _delete, + _insert, + _select, + _truncate, + _update +

    +

    Inherited from ConnectionPool: + find_or_make_work_folder, + pool_connection +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +

    Inherited from ConnectionPool: + close_all_instances, + set_folder +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + driver
    + PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    +   + + maxcharlength = 255 +
    +   + + commit_on_alter_table = True +
    +   + + support_distributed_transaction = True +
    +   + + types = {'blob': 'LONGBLOB', 'boolean': 'CHAR(1)', 'date': 'DA... +
    +

    Inherited from BaseAdapter: + uploads_in_blob +

    +

    Inherited from ConnectionPool: + pools +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    RANDOM(self) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.RANDOM +
    +
    +
    +
    + +
    + +
    + + +
    +

    SUBSTRING(self, + field, + parameters) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.SUBSTRING +
    +
    +
    +
    + +
    + +
    + + +
    +

    _drop(self, + table, + mode) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter._drop +
    +
    +
    +
    + +
    + +
    + + +
    +

    distributed_transaction_begin(self, + key) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.distributed_transaction_begin +
    +
    +
    +
    + +
    + +
    + + +
    +

    prepare(self, + key) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.prepare +
    +
    +
    +
    + +
    + +
    + + +
    +

    commit_prepared(self, + ley) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.commit_prepared +
    +
    +
    +
    + +
    + +
    + + +
    +

    rollback_prepared(self, + key) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.rollback_prepared +
    +
    +
    +
    + +
    + +
    + + +
    +

    concat_add(self, + table) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.concat_add +
    +
    +
    +
    + +
    + +
    + + +
    +

    __init__(self, + db, + uri, + pool_size=0, + folder=1, + db_codec='UTF-8', + credential_decoder=<function <lambda> at 0xd6a500>, + driver_args={}, + adapter_args={}) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + BaseAdapter.__init__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    lastrowid(self, + table) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.lastrowid +
    +
    +
    +
    +
    + + + + + + +
    + + + + + +
    Class Variable Details[hide private]
    +
    + +
    + +
    +

    driver

    +

    PyMySQL: A pure-Python drop-in replacement for MySQLdb.

    +

    Copyright (c) 2010 PyMySQL contributors

    +

    Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, and + to permit persons to whom the Software is furnished to do so, subject to + the following conditions:

    +

    The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software.

    + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY + KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + USE OR OTHER DEALINGS IN THE SOFTWARE. +
    +
    +
    +
    Value:
    +
    web2py.gluon.contrib.pymysql
    +
    +
    +
    + +
    + +
    +

    types

    + +
    +
    +
    +
    Value:
    +
    +{'blob': 'LONGBLOB',
    + 'boolean': 'CHAR(1)',
    + 'date': 'DATE',
    + 'datetime': 'DATETIME',
    + 'decimal': 'NUMERIC(%(precision)s,%(scale)s)',
    + 'double': 'DOUBLE',
    + 'id': 'INT AUTO_INCREMENT NOT NULL',
    + 'integer': 'INT',
    +...
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.dal.NoSQLAdapter-class.html Index: applications/examples/static/epydoc/web2py.gluon.dal.NoSQLAdapter-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.dal.NoSQLAdapter-class.html +++ applications/examples/static/epydoc/web2py.gluon.dal.NoSQLAdapter-class.html @@ -0,0 +1,2158 @@ + + + + + web2py.gluon.dal.NoSQLAdapter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module dal :: + Class NoSQLAdapter + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class NoSQLAdapter

    source code

    +
    +    object --+        
    +             |        
    +ConnectionPool --+    
    +                 |    
    +       BaseAdapter --+
    +                     |
    +                    NoSQLAdapter
    +
    + +
    Known Subclasses:
    +
    + CouchDBAdapter, + GoogleDatastoreAdapter, + MongoDBAdapter +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    represent(self, + obj, + fieldtype) + source code + +
    + +
    +   + + + + + + +
    _insert(self, + table, + fields) + source code + +
    + +
    +   + + + + + + +
    _count(self, + query, + distinct=1) + source code + +
    + +
    +   + + + + + + +
    _select(self, + query, + fields, + attributes) + source code + +
    + +
    +   + + + + + + +
    _delete(self, + tablename, + query) + source code + +
    + +
    +   + + + + + + +
    _update(self, + tablename, + query, + fields) + source code + +
    + +
    +   + + + + + + +
    commit(self)
    + remember: no transactions on many NoSQL
    + source code + +
    + +
    +   + + + + + + +
    rollback(self)
    + remember: no transactions on many NoSQL
    + source code + +
    + +
    +   + + + + + + +
    close(self)
    + remember: no transactions on many NoSQL
    + source code + +
    + +
    +   + + + + + + +
    OR(self, + first, + second) + source code + +
    + +
    +   + + + + + + +
    AND(self, + first, + second) + source code + +
    + +
    +   + + + + + + +
    AS(self, + first, + second) + source code + +
    + +
    +   + + + + + + +
    ON(self, + first, + second) + source code + +
    + +
    +   + + + + + + +
    STARTSWITH(self, + first, + second=1) + source code + +
    + +
    +   + + + + + + +
    ENDSWITH(self, + first, + second=1) + source code + +
    + +
    +   + + + + + + +
    ADD(self, + first, + second) + source code + +
    + +
    +   + + + + + + +
    SUB(self, + first, + second) + source code + +
    + +
    +   + + + + + + +
    MUL(self, + first, + second) + source code + +
    + +
    +   + + + + + + +
    DIV(self, + first, + second) + source code + +
    + +
    +   + + + + + + +
    LOWER(self, + first) + source code + +
    + +
    +   + + + + + + +
    UPPER(self, + first) + source code + +
    + +
    +   + + + + + + +
    EXTRACT(self, + first, + what) + source code + +
    + +
    +   + + + + + + +
    AGGREGATE(self, + first, + what) + source code + +
    + +
    +   + + + + + + +
    LEFT_JOIN(self) + source code + +
    + +
    +   + + + + + + +
    RANDOM(self) + source code + +
    + +
    +   + + + + + + +
    SUBSTRING(self, + field, + parameters) + source code + +
    + +
    +   + + + + + + +
    PRIMARY_KEY(self, + key) + source code + +
    + +
    +   + + + + + + +
    LIKE(self, + first, + second) + source code + +
    + +
    +   + + + + + + +
    drop(self, + table, + mode) + source code + +
    + +
    +   + + + + + + +
    alias(self, + table, + alias)
    + given a table object, makes a new table object with alias + name.
    + source code + +
    + +
    +   + + + + + + +
    migrate_table(self, + *a, + **b) + source code + +
    + +
    +   + + + + + + +
    distributed_transaction_begin(self, + key) + source code + +
    + +
    +   + + + + + + +
    prepare(self, + key) + source code + +
    + +
    +   + + + + + + +
    commit_prepared(self, + key) + source code + +
    + +
    +   + + + + + + +
    rollback_prepared(self, + key) + source code + +
    + +
    +   + + + + + + +
    concat_add(self, + table) + source code + +
    + +
    +   + + + + + + +
    constraint_name(self, + table, + fieldname) + source code + +
    + +
    +   + + + + + + +
    create_sequence_and_triggers(self, + query, + table, + **args) + source code + +
    + +
    +   + + + + + + +
    log_execute(self, + *a, + **b) + source code + +
    + +
    +   + + + + + + +
    execute(self, + *a, + **b) + source code + +
    + +
    +   + + + + + + +
    represent_exceptions(self, + obj, + fieldtype) + source code + +
    + +
    +   + + + + + + +
    lastrowid(self, + table) + source code + +
    + +
    +   + + + + + + +
    integrity_error_class(self) + source code + +
    + +
    +   + + + + + + +
    rowslice(self, + rows, + minimum=0, + maximum=1)
    + by default this function does nothing, overload when db does not + do slicing
    + source code + +
    + +
    +

    Inherited from BaseAdapter: + ALLOW_NULL, + BELONGS, + COALESCE_ZERO, + COMMA, + CONTAINS, + EQ, + GE, + GT, + INVERT, + JOIN, + LE, + LT, + MOD, + NE, + NOT, + NOT_NULL, + __init__, + bulk_insert, + count, + create_table, + delete, + expand, + file_close, + file_delete, + file_exists, + file_open, + filter_tenant, + get_table, + insert, + integrity_error, + parse, + select, + select_limitby, + sequence_name, + tables, + trigger_name, + truncate, + update +

    +

    Inherited from BaseAdapter (private): + _drop, + _truncate +

    +

    Inherited from ConnectionPool: + find_or_make_work_folder, + pool_connection +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +   + + + + + + +
    to_unicode(obj) + source code + +
    + +
    +

    Inherited from ConnectionPool: + close_all_instances, + set_folder +

    +
    + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +

    Inherited from BaseAdapter: + commit_on_alter_table, + driver, + maxcharlength, + support_distributed_transaction, + types, + uploads_in_blob +

    +

    Inherited from ConnectionPool: + pools +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    represent(self, + obj, + fieldtype) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.represent +
    +
    +
    +
    + +
    + +
    + + +
    +

    _insert(self, + table, + fields) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter._insert +
    +
    +
    +
    + +
    + +
    + + +
    +

    _count(self, + query, + distinct=1) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter._count +
    +
    +
    +
    + +
    + +
    + + +
    +

    _select(self, + query, + fields, + attributes) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter._select +
    +
    +
    +
    + +
    + +
    + + +
    +

    _delete(self, + tablename, + query) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter._delete +
    +
    +
    +
    + +
    + +
    + + +
    +

    _update(self, + tablename, + query, + fields) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter._update +
    +
    +
    +
    + +
    + +
    + + +
    +

    commit(self) +

    +
    source code  +
    + + remember: no transactions on many NoSQL +
    +
    Overrides: + BaseAdapter.commit +
    +
    +
    +
    + +
    + +
    + + +
    +

    rollback(self) +

    +
    source code  +
    + + remember: no transactions on many NoSQL +
    +
    Overrides: + BaseAdapter.rollback +
    +
    +
    +
    + +
    + +
    + + +
    +

    close(self) +

    +
    source code  +
    + + remember: no transactions on many NoSQL +
    +
    Overrides: + BaseAdapter.close +
    +
    +
    +
    + +
    + +
    + + +
    +

    OR(self, + first, + second) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.OR +
    +
    +
    +
    + +
    + +
    + + +
    +

    AND(self, + first, + second) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.AND +
    +
    +
    +
    + +
    + +
    + + +
    +

    AS(self, + first, + second) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.AS +
    +
    +
    +
    + +
    + +
    + + +
    +

    ON(self, + first, + second) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.ON +
    +
    +
    +
    + +
    + +
    + + +
    +

    STARTSWITH(self, + first, + second=1) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.STARTSWITH +
    +
    +
    +
    + +
    + +
    + + +
    +

    ENDSWITH(self, + first, + second=1) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.ENDSWITH +
    +
    +
    +
    + +
    + +
    + + +
    +

    ADD(self, + first, + second) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.ADD +
    +
    +
    +
    + +
    + +
    + + +
    +

    SUB(self, + first, + second) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.SUB +
    +
    +
    +
    + +
    + +
    + + +
    +

    MUL(self, + first, + second) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.MUL +
    +
    +
    +
    + +
    + +
    + + +
    +

    DIV(self, + first, + second) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.DIV +
    +
    +
    +
    + +
    + +
    + + +
    +

    LOWER(self, + first) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.LOWER +
    +
    +
    +
    + +
    + +
    + + +
    +

    UPPER(self, + first) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.UPPER +
    +
    +
    +
    + +
    + +
    + + +
    +

    EXTRACT(self, + first, + what) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.EXTRACT +
    +
    +
    +
    + +
    + +
    + + +
    +

    AGGREGATE(self, + first, + what) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.AGGREGATE +
    +
    +
    +
    + +
    + +
    + + +
    +

    LEFT_JOIN(self) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.LEFT_JOIN +
    +
    +
    +
    + +
    + +
    + + +
    +

    RANDOM(self) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.RANDOM +
    +
    +
    +
    + +
    + +
    + + +
    +

    SUBSTRING(self, + field, + parameters) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.SUBSTRING +
    +
    +
    +
    + +
    + +
    + + +
    +

    PRIMARY_KEY(self, + key) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.PRIMARY_KEY +
    +
    +
    +
    + +
    + +
    + + +
    +

    LIKE(self, + first, + second) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.LIKE +
    +
    +
    +
    + +
    + +
    + + +
    +

    drop(self, + table, + mode) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.drop +
    +
    +
    +
    + +
    + +
    + + +
    +

    alias(self, + table, + alias) +

    +
    source code  +
    + + given a table object, makes a new table object with alias name. +
    +
    Overrides: + BaseAdapter.alias +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    migrate_table(self, + *a, + **b) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.migrate_table +
    +
    +
    +
    + +
    + +
    + + +
    +

    distributed_transaction_begin(self, + key) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.distributed_transaction_begin +
    +
    +
    +
    + +
    + +
    + + +
    +

    prepare(self, + key) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.prepare +
    +
    +
    +
    + +
    + +
    + + +
    +

    commit_prepared(self, + key) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.commit_prepared +
    +
    +
    +
    + +
    + +
    + + +
    +

    rollback_prepared(self, + key) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.rollback_prepared +
    +
    +
    +
    + +
    + +
    + + +
    +

    concat_add(self, + table) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.concat_add +
    +
    +
    +
    + +
    + +
    + + +
    +

    constraint_name(self, + table, + fieldname) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.constraint_name +
    +
    +
    +
    + +
    + +
    + + +
    +

    create_sequence_and_triggers(self, + query, + table, + **args) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.create_sequence_and_triggers +
    +
    +
    +
    + +
    + +
    + + +
    +

    log_execute(self, + *a, + **b) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.log_execute +
    +
    +
    +
    + +
    + +
    + + +
    +

    execute(self, + *a, + **b) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.execute +
    +
    +
    +
    + +
    + +
    + + +
    +

    represent_exceptions(self, + obj, + fieldtype) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.represent_exceptions +
    +
    +
    +
    + +
    + +
    + + +
    +

    lastrowid(self, + table) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.lastrowid +
    +
    +
    +
    + +
    + +
    + + +
    +

    integrity_error_class(self) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.integrity_error_class +
    +
    +
    +
    + +
    + +
    + + +
    +

    rowslice(self, + rows, + minimum=0, + maximum=1) +

    +
    source code  +
    + + by default this function does nothing, overload when db does not do + slicing +
    +
    Overrides: + BaseAdapter.rowslice +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.dal.OracleAdapter-class.html Index: applications/examples/static/epydoc/web2py.gluon.dal.OracleAdapter-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.dal.OracleAdapter-class.html +++ applications/examples/static/epydoc/web2py.gluon.dal.OracleAdapter-class.html @@ -0,0 +1,997 @@ + + + + + web2py.gluon.dal.OracleAdapter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module dal :: + Class OracleAdapter + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class OracleAdapter

    source code

    +
    +    object --+        
    +             |        
    +ConnectionPool --+    
    +                 |    
    +       BaseAdapter --+
    +                     |
    +                    OracleAdapter
    +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    sequence_name(self, + tablename) + source code + +
    + +
    +   + + + + + + +
    trigger_name(self, + tablename) + source code + +
    + +
    +   + + + + + + +
    LEFT_JOIN(self) + source code + +
    + +
    +   + + + + + + +
    RANDOM(self) + source code + +
    + +
    +   + + + + + + +
    NOT_NULL(self, + default, + field_type) + source code + +
    + +
    +   + + + + + + +
    _drop(self, + table, + mode) + source code + +
    + +
    +   + + + + + + +
    select_limitby(self, + sql_s, + sql_f, + sql_t, + sql_w, + sql_o, + limitby) + source code + +
    + +
    +   + + + + + + +
    constraint_name(self, + tablename, + fieldname) + source code + +
    + +
    +   + + + + + + +
    represent_exceptions(self, + obj, + fieldtype) + source code + +
    + +
    +   + + + + + + +
    __init__(self, + db, + uri, + pool_size=0, + folder=1, + db_codec='UTF-8', + credential_decoder=<function <lambda> at 0xd6b2a8>, + driver_args={}, + adapter_args={})
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    execute(self, + command) + source code + +
    + +
    +   + + + + + + +
    create_sequence_and_triggers(self, + query, + table, + **args) + source code + +
    + +
    +   + + + + + + +
    lastrowid(self, + table) + source code + +
    + +
    +

    Inherited from BaseAdapter: + ADD, + AGGREGATE, + ALLOW_NULL, + AND, + AS, + BELONGS, + COALESCE_ZERO, + COMMA, + CONTAINS, + DIV, + ENDSWITH, + EQ, + EXTRACT, + GE, + GT, + INVERT, + JOIN, + LE, + LIKE, + LOWER, + LT, + MOD, + MUL, + NE, + NOT, + ON, + OR, + PRIMARY_KEY, + STARTSWITH, + SUB, + SUBSTRING, + UPPER, + alias, + bulk_insert, + close, + commit, + commit_prepared, + concat_add, + count, + create_table, + delete, + distributed_transaction_begin, + drop, + expand, + file_close, + file_delete, + file_exists, + file_open, + filter_tenant, + get_table, + insert, + integrity_error, + integrity_error_class, + log_execute, + migrate_table, + parse, + prepare, + represent, + rollback, + rollback_prepared, + rowslice, + select, + tables, + truncate, + update +

    +

    Inherited from BaseAdapter (private): + _count, + _delete, + _insert, + _select, + _truncate, + _update +

    +

    Inherited from ConnectionPool: + find_or_make_work_folder, + pool_connection +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +

    Inherited from ConnectionPool: + close_all_instances, + set_folder +

    +
    + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + driver = globals().get('cx_Oracle', None)
    + PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    +   + + commit_on_alter_table = True +
    +   + + types = {'blob': 'CLOB', 'boolean': 'CHAR(1)', 'date': 'DATE',... +
    +   + + oracle_fix = re.compile(r'[^\']*(\'[^\']*\'[^\']*)*:(?P<clob>C... +
    +

    Inherited from BaseAdapter: + maxcharlength, + support_distributed_transaction, + uploads_in_blob +

    +

    Inherited from ConnectionPool: + pools +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    sequence_name(self, + tablename) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.sequence_name +
    +
    +
    +
    + +
    + +
    + + +
    +

    trigger_name(self, + tablename) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.trigger_name +
    +
    +
    +
    + +
    + +
    + + +
    +

    LEFT_JOIN(self) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.LEFT_JOIN +
    +
    +
    +
    + +
    + +
    + + +
    +

    RANDOM(self) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.RANDOM +
    +
    +
    +
    + +
    + +
    + + +
    +

    NOT_NULL(self, + default, + field_type) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.NOT_NULL +
    +
    +
    +
    + +
    + +
    + + +
    +

    _drop(self, + table, + mode) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter._drop +
    +
    +
    +
    + +
    + +
    + + +
    +

    select_limitby(self, + sql_s, + sql_f, + sql_t, + sql_w, + sql_o, + limitby) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.select_limitby +
    +
    +
    +
    + +
    + +
    + + +
    +

    constraint_name(self, + tablename, + fieldname) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.constraint_name +
    +
    +
    +
    + +
    + +
    + + +
    +

    represent_exceptions(self, + obj, + fieldtype) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.represent_exceptions +
    +
    +
    +
    + +
    + +
    + + +
    +

    __init__(self, + db, + uri, + pool_size=0, + folder=1, + db_codec='UTF-8', + credential_decoder=<function <lambda> at 0xd6b2a8>, + driver_args={}, + adapter_args={}) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + BaseAdapter.__init__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    execute(self, + command) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.execute +
    +
    +
    +
    + +
    + +
    + + +
    +

    create_sequence_and_triggers(self, + query, + table, + **args) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.create_sequence_and_triggers +
    +
    +
    +
    + +
    + +
    + + +
    +

    lastrowid(self, + table) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.lastrowid +
    +
    +
    +
    +
    + + + + + + +
    + + + + + +
    Class Variable Details[hide private]
    +
    + +
    + +
    +

    types

    + +
    +
    +
    +
    Value:
    +
    +{'blob': 'CLOB',
    + 'boolean': 'CHAR(1)',
    + 'date': 'DATE',
    + 'datetime': 'DATE',
    + 'decimal': 'NUMERIC(%(precision)s,%(scale)s)',
    + 'double': 'FLOAT',
    + 'id': 'NUMBER PRIMARY KEY',
    + 'integer': 'INT',
    +...
    +
    +
    +
    +
    +
    + +
    + +
    +

    oracle_fix

    + +
    +
    +
    +
    Value:
    +
    +re.compile(r'[^\']*(\'[^\']*\'[^\']*)*:(?P<clob>CLOB\(\'([^\']+|\'\')*\
    +\'\))')
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.dal.PostgreSQLAdapter-class.html Index: applications/examples/static/epydoc/web2py.gluon.dal.PostgreSQLAdapter-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.dal.PostgreSQLAdapter-class.html +++ applications/examples/static/epydoc/web2py.gluon.dal.PostgreSQLAdapter-class.html @@ -0,0 +1,1004 @@ + + + + + web2py.gluon.dal.PostgreSQLAdapter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module dal :: + Class PostgreSQLAdapter + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class PostgreSQLAdapter

    source code

    +
    +    object --+        
    +             |        
    +ConnectionPool --+    
    +                 |    
    +       BaseAdapter --+
    +                     |
    +                    PostgreSQLAdapter
    +
    + +
    Known Subclasses:
    +
    + JDBCPostgreSQLAdapter +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    sequence_name(self, + table) + source code + +
    + +
    +   + + + + + + +
    RANDOM(self) + source code + +
    + +
    +   + + + + + + +
    distributed_transaction_begin(self, + key) + source code + +
    + +
    +   + + + + + + +
    prepare(self, + key) + source code + +
    + +
    +   + + + + + + +
    commit_prepared(self, + key) + source code + +
    + +
    +   + + + + + + +
    rollback_prepared(self, + key) + source code + +
    + +
    +   + + + + + + +
    create_sequence_and_triggers(self, + query, + table, + **args) + source code + +
    + +
    +   + + + + + + +
    __init__(self, + db, + uri, + pool_size=0, + folder=1, + db_codec='UTF-8', + credential_decoder=<function <lambda> at 0xd6a9b0>, + driver_args={}, + adapter_args={})
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    lastrowid(self, + table) + source code + +
    + +
    +   + + + + + + +
    LIKE(self, + first, + second) + source code + +
    + +
    +   + + + + + + +
    STARTSWITH(self, + first, + second) + source code + +
    + +
    +   + + + + + + +
    ENDSWITH(self, + first, + second) + source code + +
    + +
    +   + + + + + + +
    CONTAINS(self, + first, + second) + source code + +
    + +
    +

    Inherited from BaseAdapter: + ADD, + AGGREGATE, + ALLOW_NULL, + AND, + AS, + BELONGS, + COALESCE_ZERO, + COMMA, + DIV, + EQ, + EXTRACT, + GE, + GT, + INVERT, + JOIN, + LE, + LEFT_JOIN, + LOWER, + LT, + MOD, + MUL, + NE, + NOT, + NOT_NULL, + ON, + OR, + PRIMARY_KEY, + SUB, + SUBSTRING, + UPPER, + alias, + bulk_insert, + close, + commit, + concat_add, + constraint_name, + count, + create_table, + delete, + drop, + execute, + expand, + file_close, + file_delete, + file_exists, + file_open, + filter_tenant, + get_table, + insert, + integrity_error, + integrity_error_class, + log_execute, + migrate_table, + parse, + represent, + represent_exceptions, + rollback, + rowslice, + select, + select_limitby, + tables, + trigger_name, + truncate, + update +

    +

    Inherited from BaseAdapter (private): + _count, + _delete, + _drop, + _insert, + _select, + _truncate, + _update +

    +

    Inherited from ConnectionPool: + find_or_make_work_folder, + pool_connection +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +

    Inherited from ConnectionPool: + close_all_instances, + set_folder +

    +
    + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + driver
    + A Python driver for PostgreSQL + +psycopg is a PostgreSQL_ database adapter for the Python_ programming +language. +
    +   + + support_distributed_transaction = True +
    +   + + types = {'blob': 'BYTEA', 'boolean': 'CHAR(1)', 'date': 'DATE'... +
    +

    Inherited from BaseAdapter: + commit_on_alter_table, + maxcharlength, + uploads_in_blob +

    +

    Inherited from ConnectionPool: + pools +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    sequence_name(self, + table) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.sequence_name +
    +
    +
    +
    + +
    + +
    + + +
    +

    RANDOM(self) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.RANDOM +
    +
    +
    +
    + +
    + +
    + + +
    +

    distributed_transaction_begin(self, + key) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.distributed_transaction_begin +
    +
    +
    +
    + +
    + +
    + + +
    +

    prepare(self, + key) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.prepare +
    +
    +
    +
    + +
    + +
    + + +
    +

    commit_prepared(self, + key) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.commit_prepared +
    +
    +
    +
    + +
    + +
    + + +
    +

    rollback_prepared(self, + key) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.rollback_prepared +
    +
    +
    +
    + +
    + +
    + + +
    +

    create_sequence_and_triggers(self, + query, + table, + **args) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.create_sequence_and_triggers +
    +
    +
    +
    + +
    + +
    + + +
    +

    __init__(self, + db, + uri, + pool_size=0, + folder=1, + db_codec='UTF-8', + credential_decoder=<function <lambda> at 0xd6a9b0>, + driver_args={}, + adapter_args={}) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + BaseAdapter.__init__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    lastrowid(self, + table) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.lastrowid +
    +
    +
    +
    + +
    + +
    + + +
    +

    LIKE(self, + first, + second) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.LIKE +
    +
    +
    +
    + +
    + +
    + + +
    +

    STARTSWITH(self, + first, + second) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.STARTSWITH +
    +
    +
    +
    + +
    + +
    + + +
    +

    ENDSWITH(self, + first, + second) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.ENDSWITH +
    +
    +
    +
    + +
    + +
    + + +
    +

    CONTAINS(self, + first, + second) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.CONTAINS +
    +
    +
    +
    +
    + + + + + + +
    + + + + + +
    Class Variable Details[hide private]
    +
    + +
    + +
    +

    driver

    +
    +A Python driver for PostgreSQL
    +
    +psycopg is a PostgreSQL_ database adapter for the Python_ programming
    +language. This is version 2, a complete rewrite of the original code to
    +provide new-style classes for connection and cursor objects and other sweet
    +candies. Like the original, psycopg 2 was written with the aim of being very
    +small and fast, and stable as a rock.
    +
    +Homepage: http://initd.org/projects/psycopg2
    +
    +.. _PostgreSQL: http://www.postgresql.org/
    +.. _Python: http://www.python.org/
    +
    +:Groups:
    +  * `Connections creation`: connect
    +  * `Value objects constructors`: Binary, Date, DateFromTicks, Time,
    +    TimeFromTicks, Timestamp, TimestampFromTicks
    +
    +
    +
    +
    +
    +
    Value:
    +
    psycopg2
    +
    +
    +
    + +
    + +
    +

    types

    + +
    +
    +
    +
    Value:
    +
    +{'blob': 'BYTEA',
    + 'boolean': 'CHAR(1)',
    + 'date': 'DATE',
    + 'datetime': 'TIMESTAMP',
    + 'decimal': 'NUMERIC(%(precision)s,%(scale)s)',
    + 'double': 'FLOAT8',
    + 'id': 'SERIAL PRIMARY KEY',
    + 'integer': 'INTEGER',
    +...
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.dal.Query-class.html Index: applications/examples/static/epydoc/web2py.gluon.dal.Query-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.dal.Query-class.html +++ applications/examples/static/epydoc/web2py.gluon.dal.Query-class.html @@ -0,0 +1,350 @@ + + + + + web2py.gluon.dal.Query + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module dal :: + Class Query + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class Query

    source code

    +
    +object --+
    +         |
    +        Query
    +
    + +
    +

    a query object necessary to define a set. it can be stored or can be + passed to DAL.__call__() to obtain a Set

    + Example: +
    +   query = db.users.name=='Max'
    +   set = db(query)
    +   records = set.select()
    +


    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + db, + op, + first=1, + second=1)
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    __str__(self)
    + str(x)
    + source code + +
    + +
    +   + + + + + + +
    __and__(self, + other) + source code + +
    + +
    +   + + + + + + +
    __or__(self, + other) + source code + +
    + +
    +   + + + + + + +
    __invert__(self) + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + db, + op, + first=1, + second=1) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    __str__(self) +
    (Informal representation operator) +

    +
    source code  +
    + + str(x) +
    +
    Overrides: + object.__str__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.dal.Reference-class.html Index: applications/examples/static/epydoc/web2py.gluon.dal.Reference-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.dal.Reference-class.html +++ applications/examples/static/epydoc/web2py.gluon.dal.Reference-class.html @@ -0,0 +1,358 @@ + + + + + web2py.gluon.dal.Reference + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module dal :: + Class Reference + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class Reference

    source code

    +
    +object --+    
    +         |    
    +       int --+
    +             |
    +            Reference
    +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __allocate(self) + source code + +
    + +
    +   + + + + + + +
    __getattr__(self, + key) + source code + +
    + +
    +   + + + + + + +
    __setattr__(self, + key, + value)
    + x.__setattr__('name', value) <==> x.name = value
    + source code + +
    + +
    +   + + + + + + +
    __getitem__(self, + key) + source code + +
    + +
    +   + + + + + + +
    __setitem__(self, + key, + value) + source code + +
    + +
    +

    Inherited from int: + __abs__, + __add__, + __and__, + __cmp__, + __coerce__, + __div__, + __divmod__, + __float__, + __floordiv__, + __getattribute__, + __getnewargs__, + __hash__, + __hex__, + __index__, + __int__, + __invert__, + __long__, + __lshift__, + __mod__, + __mul__, + __neg__, + __new__, + __nonzero__, + __oct__, + __or__, + __pos__, + __pow__, + __radd__, + __rand__, + __rdiv__, + __rdivmod__, + __repr__, + __rfloordiv__, + __rlshift__, + __rmod__, + __rmul__, + __ror__, + __rpow__, + __rrshift__, + __rshift__, + __rsub__, + __rtruediv__, + __rxor__, + __str__, + __sub__, + __truediv__, + __xor__ +

    +

    Inherited from object: + __delattr__, + __init__, + __reduce__, + __reduce_ex__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __setattr__(self, + key, + value) +

    +
    source code  +
    + + x.__setattr__('name', value) <==> x.name = value +
    +
    Overrides: + object.__setattr__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.dal.Row-class.html Index: applications/examples/static/epydoc/web2py.gluon.dal.Row-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.dal.Row-class.html +++ applications/examples/static/epydoc/web2py.gluon.dal.Row-class.html @@ -0,0 +1,571 @@ + + + + + web2py.gluon.dal.Row + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module dal :: + Class Row + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class Row

    source code

    +
    +object --+    
    +         |    
    +      dict --+
    +             |
    +            Row
    +
    + +
    +a dictionary that lets you do d['a'] as well as d.a this is only used + to store a Row

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __getitem__(self, + key)
    + x[y]
    + source code + +
    + +
    +   + + + + + + +
    __call__(self, + key) + source code + +
    + +
    +   + + + + + + +
    __setitem__(self, + key, + value)
    + x[i]=y
    + source code + +
    + +
    +   + + + + + + +
    __getattr__(self, + key) + source code + +
    + +
    +   + + + + + + +
    __setattr__(self, + key, + value)
    + x.__setattr__('name', value) <==> x.name = value
    + source code + +
    + +
    +   + + + + + + +
    __repr__(self)
    + repr(x)
    + source code + +
    + +
    +   + + + + + + +
    __int__(self) + source code + +
    + +
    +   + + + + + + +
    __eq__(self, + other)
    + x==y
    + source code + +
    + +
    +   + + + + + + +
    __ne__(self, + other)
    + x!=y
    + source code + +
    + +
    +   + + + + + + +
    __copy__(self) + source code + +
    + +
    +   + + + + + + +
    as_dict(self, + datetime_to_str=True) + source code + +
    + +
    +

    Inherited from dict: + __cmp__, + __contains__, + __delitem__, + __ge__, + __getattribute__, + __gt__, + __hash__, + __init__, + __iter__, + __le__, + __len__, + __lt__, + __new__, + clear, + copy, + fromkeys, + get, + has_key, + items, + iteritems, + iterkeys, + itervalues, + keys, + pop, + popitem, + setdefault, + update, + values +

    +

    Inherited from object: + __delattr__, + __reduce__, + __reduce_ex__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __getitem__(self, + key) +
    (Indexing operator) +

    +
    source code  +
    + + x[y] +
    +
    Overrides: + dict.__getitem__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    __setitem__(self, + key, + value) +
    (Index assignment operator) +

    +
    source code  +
    + + x[i]=y +
    +
    Overrides: + dict.__setitem__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    __setattr__(self, + key, + value) +

    +
    source code  +
    + + x.__setattr__('name', value) <==> x.name = value +
    +
    Overrides: + object.__setattr__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    __repr__(self) +
    (Representation operator) +

    +
    source code  +
    + + repr(x) +
    +
    Overrides: + dict.__repr__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    __eq__(self, + other) +
    (Equality operator) +

    +
    source code  +
    + + x==y +
    +
    Overrides: + dict.__eq__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    __ne__(self, + other) +

    +
    source code  +
    + + x!=y +
    +
    Overrides: + dict.__ne__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.dal.Rows-class.html Index: applications/examples/static/epydoc/web2py.gluon.dal.Rows-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.dal.Rows-class.html +++ applications/examples/static/epydoc/web2py.gluon.dal.Rows-class.html @@ -0,0 +1,709 @@ + + + + + web2py.gluon.dal.Rows + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module dal :: + Class Rows + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class Rows

    source code

    +
    +object --+
    +         |
    +        Rows
    +
    + +
    +A wrapper for the return value of a select. It basically represents a + table. It has an iterator and each row is represented as a + dictionary.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + db=1, + records=[], + colnames=[], + compact=True, + rawrows=1)
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    setvirtualfields(self, + **keyed_virtualfields) + source code + +
    + +
    +   + + + + + + +
    __and__(self, + other) + source code + +
    + +
    +   + + + + + + +
    __or__(self, + other) + source code + +
    + +
    +   + + + + + + +
    __nonzero__(self) + source code + +
    + +
    +   + + + + + + +
    __len__(self) + source code + +
    + +
    +   + + + + + + +
    __getslice__(self, + a, + b) + source code + +
    + +
    +   + + + + + + +
    __getitem__(self, + i) + source code + +
    + +
    +   + + + + + + +
    __iter__(self)
    + iterator over records
    + source code + +
    + +
    +   + + + + + + +
    __str__(self)
    + serializes the table into a csv file
    + source code + +
    + +
    +   + + + + + + +
    first(self) + source code + +
    + +
    +   + + + + + + +
    last(self) + source code + +
    + +
    +   + + + + + + +
    find(self, + f)
    + returns a new Rows object, a subset of the original object, + filtered by the function f
    + source code + +
    + +
    +   + + + + + + +
    exclude(self, + f)
    + removes elements from the calling Rows object, filtered by the + function f, and returns a new Rows object containing the removed + elements
    + source code + +
    + +
    +   + + + + + + +
    sort(self, + f, + reverse=True)
    + returns a list of sorted elements (not sorted in place)
    + source code + +
    + +
    +   + + + + + + +
    as_list(self, + compact=True, + storage_to_dict=True, + datetime_to_str=True)
    + returns the data as a list or dictionary.
    + source code + +
    + +
    +   + + + + + + +
    as_dict(self, + key='id', + compact=True, + storage_to_dict=True, + datetime_to_str=True)
    + returns the data as a dictionary of dictionaries + (storage_to_dict=True) or records (False)
    + source code + +
    + +
    +   + + + + + + +
    export_to_csv_file(self, + ofile, + null='<NULL>', + *args, + **kwargs)
    + export data to csv, the first line contains the column names...
    + source code + +
    + +
    +   + + + + + + +
    xml(self)
    + serializes the table using sqlhtml.SQLTABLE (if present)
    + source code + +
    + +
    +   + + + + + + +
    json(self, + mode='object', + default=1)
    + serializes the table to a JSON list of objects
    + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + db=1, + records=[], + colnames=[], + compact=True, + rawrows=1) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    __str__(self) +
    (Informal representation operator) +

    +
    source code  +
    + + serializes the table into a csv file +
    +
    Overrides: + object.__str__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    as_list(self, + compact=True, + storage_to_dict=True, + datetime_to_str=True) +

    +
    source code  +
    + + returns the data as a list or dictionary. :param storage_to_dict: when + True returns a dict, otherwise a list(default True) :param + datetime_to_str: convert datetime fields as strings (default True) +
    +
    +
    +
    + +
    + +
    + + +
    +

    as_dict(self, + key='id', + compact=True, + storage_to_dict=True, + datetime_to_str=True) +

    +
    source code  +
    + +

    returns the data as a dictionary of dictionaries + (storage_to_dict=True) or records (False)

    + :param key: the name of the field to be used as dict key, normally the + id :param compact: ? (default True) :param storage_to_dict: when True + returns a dict, otherwise a list(default True) :param datetime_to_str: + convert datetime fields as strings (default True) +
    +
    +
    +
    + +
    + +
    + + +
    +

    export_to_csv_file(self, + ofile, + null='<NULL>', + *args, + **kwargs) +

    +
    source code  +
    + +
    +
    +export data to csv, the first line contains the column names
    +
    +:param ofile: where the csv must be exported to
    +:param null: how null values must be represented (default '<NULL>')
    +:param delimiter: delimiter to separate values (default ',')
    +:param quotechar: character to use to quote string values (default '"')
    +:param quoting: quote system, use csv.QUOTE_*** (default csv.QUOTE_MINIMAL)
    +:param represent: use the fields .represent value (default False)
    +:param colnames: list of column names to use (default self.colnames)
    +                 This will only work when exporting rows objects!!!!
    +                 DO NOT use this with db.export_to_csv()
    +
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.dal.SAPDBAdapter-class.html Index: applications/examples/static/epydoc/web2py.gluon.dal.SAPDBAdapter-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.dal.SAPDBAdapter-class.html +++ applications/examples/static/epydoc/web2py.gluon.dal.SAPDBAdapter-class.html @@ -0,0 +1,654 @@ + + + + + web2py.gluon.dal.SAPDBAdapter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module dal :: + Class SAPDBAdapter + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class SAPDBAdapter

    source code

    +
    +    object --+        
    +             |        
    +ConnectionPool --+    
    +                 |    
    +       BaseAdapter --+
    +                     |
    +                    SAPDBAdapter
    +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    sequence_name(self, + table) + source code + +
    + +
    +   + + + + + + +
    select_limitby(self, + sql_s, + sql_f, + sql_t, + sql_w, + sql_o, + limitby) + source code + +
    + +
    +   + + + + + + +
    create_sequence_and_triggers(self, + query, + table, + **args) + source code + +
    + +
    +   + + + + + + +
    __init__(self, + db, + uri, + pool_size=0, + folder=1, + db_codec='UTF-8', + credential_decoder=<function <lambda> at 0xd6e320>, + driver_args={}, + adapter_args={})
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    lastrowid(self, + table) + source code + +
    + +
    +

    Inherited from BaseAdapter: + ADD, + AGGREGATE, + ALLOW_NULL, + AND, + AS, + BELONGS, + COALESCE_ZERO, + COMMA, + CONTAINS, + DIV, + ENDSWITH, + EQ, + EXTRACT, + GE, + GT, + INVERT, + JOIN, + LE, + LEFT_JOIN, + LIKE, + LOWER, + LT, + MOD, + MUL, + NE, + NOT, + NOT_NULL, + ON, + OR, + PRIMARY_KEY, + RANDOM, + STARTSWITH, + SUB, + SUBSTRING, + UPPER, + alias, + bulk_insert, + close, + commit, + commit_prepared, + concat_add, + constraint_name, + count, + create_table, + delete, + distributed_transaction_begin, + drop, + execute, + expand, + file_close, + file_delete, + file_exists, + file_open, + filter_tenant, + get_table, + insert, + integrity_error, + integrity_error_class, + log_execute, + migrate_table, + parse, + prepare, + represent, + represent_exceptions, + rollback, + rollback_prepared, + rowslice, + select, + tables, + trigger_name, + truncate, + update +

    +

    Inherited from BaseAdapter (private): + _count, + _delete, + _drop, + _insert, + _select, + _truncate, + _update +

    +

    Inherited from ConnectionPool: + find_or_make_work_folder, + pool_connection +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +

    Inherited from ConnectionPool: + close_all_instances, + set_folder +

    +
    + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + driver = globals().get('sapdb', None)
    + PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    +   + + support_distributed_transaction = True +
    +   + + types = {'blob': 'LONG', 'boolean': 'CHAR(1)', 'date': 'DATE',... +
    +

    Inherited from BaseAdapter: + commit_on_alter_table, + maxcharlength, + uploads_in_blob +

    +

    Inherited from ConnectionPool: + pools +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    sequence_name(self, + table) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.sequence_name +
    +
    +
    +
    + +
    + +
    + + +
    +

    select_limitby(self, + sql_s, + sql_f, + sql_t, + sql_w, + sql_o, + limitby) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.select_limitby +
    +
    +
    +
    + +
    + +
    + + +
    +

    create_sequence_and_triggers(self, + query, + table, + **args) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.create_sequence_and_triggers +
    +
    +
    +
    + +
    + +
    + + +
    +

    __init__(self, + db, + uri, + pool_size=0, + folder=1, + db_codec='UTF-8', + credential_decoder=<function <lambda> at 0xd6e320>, + driver_args={}, + adapter_args={}) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + BaseAdapter.__init__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    lastrowid(self, + table) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.lastrowid +
    +
    +
    +
    +
    + + + + + + +
    + + + + + +
    Class Variable Details[hide private]
    +
    + +
    + +
    +

    types

    + +
    +
    +
    +
    Value:
    +
    +{'blob': 'LONG',
    + 'boolean': 'CHAR(1)',
    + 'date': 'DATE',
    + 'datetime': 'TIMESTAMP',
    + 'decimal': 'FIXED(%(precision)s,%(scale)s)',
    + 'double': 'FLOAT',
    + 'id': 'INT PRIMARY KEY',
    + 'integer': 'INT',
    +...
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.dal.SQLALL-class.html Index: applications/examples/static/epydoc/web2py.gluon.dal.SQLALL-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.dal.SQLALL-class.html +++ applications/examples/static/epydoc/web2py.gluon.dal.SQLALL-class.html @@ -0,0 +1,289 @@ + + + + + web2py.gluon.dal.SQLALL + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module dal :: + Class SQLALL + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class SQLALL

    source code

    +
    +object --+
    +         |
    +        SQLALL
    +
    + +
    +

    Helper class providing a comma-separated string having all the field + names (prefixed by table name and '.')

    + normally only called from within gluon.sql

    + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + table)
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    __str__(self)
    + str(x)
    + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + table) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    __str__(self) +
    (Informal representation operator) +

    +
    source code  +
    + + str(x) +
    +
    Overrides: + object.__str__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.dal.SQLCallableList-class.html Index: applications/examples/static/epydoc/web2py.gluon.dal.SQLCallableList-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.dal.SQLCallableList-class.html +++ applications/examples/static/epydoc/web2py.gluon.dal.SQLCallableList-class.html @@ -0,0 +1,232 @@ + + + + + web2py.gluon.dal.SQLCallableList + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module dal :: + Class SQLCallableList + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class SQLCallableList

    source code

    +
    +object --+    
    +         |    
    +      list --+
    +             |
    +            SQLCallableList
    +
    + +
    + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __call__(self) + source code + +
    + +
    +

    Inherited from list: + __add__, + __contains__, + __delitem__, + __delslice__, + __eq__, + __ge__, + __getattribute__, + __getitem__, + __getslice__, + __gt__, + __hash__, + __iadd__, + __imul__, + __init__, + __iter__, + __le__, + __len__, + __lt__, + __mul__, + __ne__, + __new__, + __repr__, + __reversed__, + __rmul__, + __setitem__, + __setslice__, + append, + count, + extend, + index, + insert, + pop, + remove, + reverse, + sort +

    +

    Inherited from object: + __delattr__, + __reduce__, + __reduce_ex__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.dal.SQLCustomType-class.html Index: applications/examples/static/epydoc/web2py.gluon.dal.SQLCustomType-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.dal.SQLCustomType-class.html +++ applications/examples/static/epydoc/web2py.gluon.dal.SQLCustomType-class.html @@ -0,0 +1,374 @@ + + + + + web2py.gluon.dal.SQLCustomType + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module dal :: + Class SQLCustomType + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class SQLCustomType

    source code

    +
    +object --+
    +         |
    +        SQLCustomType
    +
    + +
    +
    +
    +allows defining of custom SQL types
    +
    +Example::
    +
    +    decimal = SQLCustomType(
    +        type ='double',
    +        native ='integer',
    +        encoder =(lambda x: int(float(x) * 100)),
    +        decoder = (lambda x: Decimal("0.00") + Decimal(str(float(x)/100)) )
    +        )
    +
    +    db.define_table(
    +        'example',
    +        Field('value', type=decimal)
    +        )
    +
    +:param type: the web2py type (default = 'string')
    +:param native: the backend type
    +:param encoder: how to encode the value to store it in the backend
    +:param decoder: how to decode the value retrieved from the backend
    +:param validator: what validators to use ( default = None, will use the
    +    default validator for type)
    +
    +


    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + type='string', + native=1, + encoder=1, + decoder=1, + validator=1, + _class=1)
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    startswith(self, + dummy=1) + source code + +
    + +
    +   + + + + + + +
    __getslice__(self, + a=0, + b=100) + source code + +
    + +
    +   + + + + + + +
    __getitem__(self, + i) + source code + +
    + +
    +   + + + + + + +
    __str__(self)
    + str(x)
    + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + type='string', + native=1, + encoder=1, + decoder=1, + validator=1, + _class=1) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    __str__(self) +
    (Informal representation operator) +

    +
    source code  +
    + + str(x) +
    +
    Overrides: + object.__str__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.dal.SQLiteAdapter-class.html Index: applications/examples/static/epydoc/web2py.gluon.dal.SQLiteAdapter-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.dal.SQLiteAdapter-class.html +++ applications/examples/static/epydoc/web2py.gluon.dal.SQLiteAdapter-class.html @@ -0,0 +1,613 @@ + + + + + web2py.gluon.dal.SQLiteAdapter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module dal :: + Class SQLiteAdapter + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class SQLiteAdapter

    source code

    +
    +    object --+        
    +             |        
    +ConnectionPool --+    
    +                 |    
    +       BaseAdapter --+
    +                     |
    +                    SQLiteAdapter
    +
    + +
    Known Subclasses:
    +
    + JDBCSQLiteAdapter +
    + +
    + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    EXTRACT(self, + field, + what) + source code + +
    + +
    +   + + + + + + +
    __init__(self, + db, + uri, + pool_size=0, + folder=1, + db_codec='UTF-8', + credential_decoder=<function <lambda> at 0xd69d70>, + driver_args={}, + adapter_args={})
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    _truncate(self, + table, + mode='') + source code + +
    + +
    +   + + + + + + +
    lastrowid(self, + table) + source code + +
    + +
    +

    Inherited from BaseAdapter: + ADD, + AGGREGATE, + ALLOW_NULL, + AND, + AS, + BELONGS, + COALESCE_ZERO, + COMMA, + CONTAINS, + DIV, + ENDSWITH, + EQ, + GE, + GT, + INVERT, + JOIN, + LE, + LEFT_JOIN, + LIKE, + LOWER, + LT, + MOD, + MUL, + NE, + NOT, + NOT_NULL, + ON, + OR, + PRIMARY_KEY, + RANDOM, + STARTSWITH, + SUB, + SUBSTRING, + UPPER, + alias, + bulk_insert, + close, + commit, + commit_prepared, + concat_add, + constraint_name, + count, + create_sequence_and_triggers, + create_table, + delete, + distributed_transaction_begin, + drop, + execute, + expand, + file_close, + file_delete, + file_exists, + file_open, + filter_tenant, + get_table, + insert, + integrity_error, + integrity_error_class, + log_execute, + migrate_table, + parse, + prepare, + represent, + represent_exceptions, + rollback, + rollback_prepared, + rowslice, + select, + select_limitby, + sequence_name, + tables, + trigger_name, + truncate, + update +

    +

    Inherited from BaseAdapter (private): + _count, + _delete, + _drop, + _insert, + _select, + _update +

    +

    Inherited from ConnectionPool: + find_or_make_work_folder, + pool_connection +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +   + + + + + + +
    web2py_extract(lookup, + s) + source code + +
    + +
    +

    Inherited from ConnectionPool: + close_all_instances, + set_folder +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + driver
    + PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    +

    Inherited from BaseAdapter: + commit_on_alter_table, + maxcharlength, + support_distributed_transaction, + types, + uploads_in_blob +

    +

    Inherited from ConnectionPool: + pools +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    EXTRACT(self, + field, + what) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.EXTRACT +
    +
    +
    +
    + +
    + +
    + + +
    +

    __init__(self, + db, + uri, + pool_size=0, + folder=1, + db_codec='UTF-8', + credential_decoder=<function <lambda> at 0xd69d70>, + driver_args={}, + adapter_args={}) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + BaseAdapter.__init__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    _truncate(self, + table, + mode='') +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter._truncate +
    +
    +
    +
    + +
    + +
    + + +
    +

    lastrowid(self, + table) +

    +
    source code  +
    + + +
    +
    Overrides: + BaseAdapter.lastrowid +
    +
    +
    +
    +
    + + + + + + +
    + + + + + +
    Class Variable Details[hide private]
    +
    + +
    + +
    +

    driver

    +

    PyMySQL: A pure-Python drop-in replacement for MySQLdb.

    +

    Copyright (c) 2010 PyMySQL contributors

    +

    Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, and + to permit persons to whom the Software is furnished to do so, subject to + the following conditions:

    +

    The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software.

    + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY + KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + USE OR OTHER DEALINGS IN THE SOFTWARE. +
    +
    +
    +
    Value:
    +
    sqlite3.dbapi2
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.dal.Set-class.html Index: applications/examples/static/epydoc/web2py.gluon.dal.Set-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.dal.Set-class.html +++ applications/examples/static/epydoc/web2py.gluon.dal.Set-class.html @@ -0,0 +1,467 @@ + + + + + web2py.gluon.dal.Set + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module dal :: + Class Set + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class Set

    source code

    +
    +object --+
    +         |
    +        Set
    +
    + +
    +
    +
    +a Set represents a set of records in the database,
    +the records are identified by the query=Query(...) object.
    +normally the Set is generated by DAL.__call__(Query(...))
    +
    +given a set, for example
    +   set = db(db.users.name=='Max')
    +you can:
    +   set.update(db.users.name='Massimo')
    +   set.delete() # all elements in the set
    +   set.select(orderby=db.users.id, groupby=db.users.name, limitby=(0,10))
    +and take subsets:
    +   subset = set(db.users.id<5)
    +
    +


    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + db, + query)
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    __call__(self, + query) + source code + +
    + +
    +   + + + + + + +
    _count(self, + distinct=1) + source code + +
    + +
    +   + + + + + + +
    _select(self, + *fields, + **attributes) + source code + +
    + +
    +   + + + + + + +
    _delete(self) + source code + +
    + +
    +   + + + + + + +
    _update(self, + **update_fields) + source code + +
    + +
    +   + + + + + + +
    isempty(self) + source code + +
    + +
    +   + + + + + + +
    count(self, + distinct=1) + source code + +
    + +
    +   + + + + + + +
    select(self, + *fields, + **attributes) + source code + +
    + +
    +   + + + + + + +
    delete(self) + source code + +
    + +
    +   + + + + + + +
    update(self, + **update_fields) + source code + +
    + +
    +   + + + + + + +
    validate_and_update(self, + **update_fields) + source code + +
    + +
    +   + + + + + + +
    delete_uploaded_files(self, + upload_fields=1) + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + db, + query) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.dal.Table-class.html Index: applications/examples/static/epydoc/web2py.gluon.dal.Table-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.dal.Table-class.html +++ applications/examples/static/epydoc/web2py.gluon.dal.Table-class.html @@ -0,0 +1,952 @@ + + + + + web2py.gluon.dal.Table + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module dal :: + Class Table + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class Table

    source code

    +
    +object --+    
    +         |    
    +      dict --+
    +             |
    +            Table
    +
    + +
    +

    an instance of this class represents a database table

    + Example: +
    +   db = DAL(...)
    +   db.define_table('users', Field('name'))
    +   db.users.insert(name='me') # print db.users._insert(...) to see SQL
    +   db.users.drop()
    +


    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + db, + tablename, + *fields, + **args)
    + Initializes the table and performs checking on the provided + fields.
    + source code + +
    + +
    +   + + + + + + +
    _validate(self, + **vars) + source code + +
    + +
    +   + + + + + + +
    _create_references(self) + source code + +
    + +
    +   + + + + + + +
    _filter_fields(self, + record, + id=True) + source code + +
    + +
    +   + + + + + + +
    _build_query(self, + key)
    + for keyed table only
    + source code + +
    + +
    +   + + + + + + +
    __getitem__(self, + key)
    + x[y]
    + source code + +
    + +
    +   + + + + + + +
    __call__(self, + key=<function <lambda> at 0xd1ec80>, + **kwargs) + source code + +
    + +
    +   + + + + + + +
    __setitem__(self, + key, + value)
    + x[i]=y
    + source code + +
    + +
    +   + + + + + + +
    __delitem__(self, + key)
    + del x[y]
    + source code + +
    + +
    +   + + + + + + +
    __getattr__(self, + key) + source code + +
    + +
    +   + + + + + + +
    __setattr__(self, + key, + value)
    + x.__setattr__('name', value) <==> x.name = value
    + source code + +
    + +
    +   + + + + + + +
    __iter__(self)
    + iter(x)
    + source code + +
    + +
    +   + + + + + + +
    __repr__(self)
    + repr(x)
    + source code + +
    + +
    +   + + + + + + +
    __str__(self)
    + str(x)
    + source code + +
    + +
    +   + + + + + + +
    _drop(self, + mode='') + source code + +
    + +
    +   + + + + + + +
    drop(self, + mode='') + source code + +
    + +
    +   + + + + + + +
    _listify(self, + fields, + update=True) + source code + +
    + +
    +   + + + + + + +
    _insert(self, + **fields) + source code + +
    + +
    +   + + + + + + +
    insert(self, + **fields) + source code + +
    + +
    +   + + + + + + +
    validate_and_insert(self, + **fields) + source code + +
    + +
    +   + + + + + + +
    update_or_insert(self, + key=<function <lambda> at 0xd1ec80>, + **values) + source code + +
    + +
    +   + + + + + + +
    bulk_insert(self, + items)
    + here items is a list of dictionaries
    + source code + +
    + +
    +   + + + + + + +
    _truncate(self, + mode=1) + source code + +
    + +
    +   + + + + + + +
    truncate(self, + mode=1) + source code + +
    + +
    +   + + + + + + +
    import_from_csv_file(self, + csvfile, + id_map=1, + null='<NULL>', + unique='uuid', + *args, + **kwargs)
    + import records from csv file.
    + source code + +
    + +
    +   + + + + + + +
    with_alias(self, + alias) + source code + +
    + +
    +   + + + + + + +
    on(self, + query) + source code + +
    + +
    +

    Inherited from dict: + __cmp__, + __contains__, + __eq__, + __ge__, + __getattribute__, + __gt__, + __hash__, + __le__, + __len__, + __lt__, + __ne__, + __new__, + clear, + copy, + fromkeys, + get, + has_key, + items, + iteritems, + iterkeys, + itervalues, + keys, + pop, + popitem, + setdefault, + update, + values +

    +

    Inherited from object: + __delattr__, + __reduce__, + __reduce_ex__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + db, + tablename, + *fields, + **args) +
    (Constructor) +

    +
    source code  +
    + +

    Initializes the table and performs checking on the provided + fields.

    +

    Each table will have automatically an 'id'.

    +

    If a field is of type Table, the fields (excluding 'id') from that + table will be used instead.

    + :raises SyntaxError: when a supplied field is of incorrect type. +
    +
    Returns:
    +
    +new empty dictionary
    +
    +
    +
    Overrides: + dict.__init__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    __getitem__(self, + key) +
    (Indexing operator) +

    +
    source code  +
    + + x[y] +
    +
    Overrides: + dict.__getitem__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    __setitem__(self, + key, + value) +
    (Index assignment operator) +

    +
    source code  +
    + + x[i]=y +
    +
    Overrides: + dict.__setitem__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    __delitem__(self, + key) +
    (Index deletion operator) +

    +
    source code  +
    + + del x[y] +
    +
    Overrides: + dict.__delitem__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    __setattr__(self, + key, + value) +

    +
    source code  +
    + + x.__setattr__('name', value) <==> x.name = value +
    +
    Overrides: + object.__setattr__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    __iter__(self) +

    +
    source code  +
    + + iter(x) +
    +
    Overrides: + dict.__iter__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    __repr__(self) +
    (Representation operator) +

    +
    source code  +
    + + repr(x) +
    +
    Overrides: + dict.__repr__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    __str__(self) +
    (Informal representation operator) +

    +
    source code  +
    + + str(x) +
    +
    Overrides: + object.__str__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    import_from_csv_file(self, + csvfile, + id_map=1, + null='<NULL>', + unique='uuid', + *args, + **kwargs) +

    +
    source code  +
    + + import records from csv file. Column headers must have same names as + table fields. field 'id' is ignored. If column names read 'table.file' + the 'table.' prefix is ignored. 'unique' argument is a field which must + be unique (typically a uuid field) +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.dal.TeradataAdapter-class.html Index: applications/examples/static/epydoc/web2py.gluon.dal.TeradataAdapter-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.dal.TeradataAdapter-class.html +++ applications/examples/static/epydoc/web2py.gluon.dal.TeradataAdapter-class.html @@ -0,0 +1,482 @@ + + + + + web2py.gluon.dal.TeradataAdapter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module dal :: + Class TeradataAdapter + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class TeradataAdapter

    source code

    +
    +    object --+            
    +             |            
    +ConnectionPool --+        
    +                 |        
    +       BaseAdapter --+    
    +                     |    
    +            DB2Adapter --+
    +                         |
    +                        TeradataAdapter
    +
    + +
    + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + db, + uri, + pool_size=0, + folder=1, + db_codec='UTF-8', + credential_decoder=<function <lambda> at 0xd6db90>, + driver_args={}, + adapter_args={})
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +

    Inherited from DB2Adapter: + LEFT_JOIN, + RANDOM, + execute, + lastrowid, + represent_exceptions, + rowslice, + select_limitby +

    +

    Inherited from BaseAdapter: + ADD, + AGGREGATE, + ALLOW_NULL, + AND, + AS, + BELONGS, + COALESCE_ZERO, + COMMA, + CONTAINS, + DIV, + ENDSWITH, + EQ, + EXTRACT, + GE, + GT, + INVERT, + JOIN, + LE, + LIKE, + LOWER, + LT, + MOD, + MUL, + NE, + NOT, + NOT_NULL, + ON, + OR, + PRIMARY_KEY, + STARTSWITH, + SUB, + SUBSTRING, + UPPER, + alias, + bulk_insert, + close, + commit, + commit_prepared, + concat_add, + constraint_name, + count, + create_sequence_and_triggers, + create_table, + delete, + distributed_transaction_begin, + drop, + expand, + file_close, + file_delete, + file_exists, + file_open, + filter_tenant, + get_table, + insert, + integrity_error, + integrity_error_class, + log_execute, + migrate_table, + parse, + prepare, + represent, + rollback, + rollback_prepared, + select, + sequence_name, + tables, + trigger_name, + truncate, + update +

    +

    Inherited from BaseAdapter (private): + _count, + _delete, + _drop, + _insert, + _select, + _truncate, + _update +

    +

    Inherited from ConnectionPool: + find_or_make_work_folder, + pool_connection +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +

    Inherited from ConnectionPool: + close_all_instances, + set_folder +

    +
    + + + + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + driver = globals().get('pyodbc', None)
    + PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    +   + + types = {'boolean': 'CHAR(1)', 'string': 'VARCHAR(%(length)s)'... +
    +

    Inherited from BaseAdapter: + commit_on_alter_table, + maxcharlength, + support_distributed_transaction, + uploads_in_blob +

    +

    Inherited from ConnectionPool: + pools +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + db, + uri, + pool_size=0, + folder=1, + db_codec='UTF-8', + credential_decoder=<function <lambda> at 0xd6db90>, + driver_args={}, + adapter_args={}) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + DB2Adapter.__init__ +
    +
    +
    +
    +
    + + + + + + +
    + + + + + +
    Class Variable Details[hide private]
    +
    + +
    + +
    +

    types

    + +
    +
    +
    +
    Value:
    +
    +{'boolean': 'CHAR(1)', 'string': 'VARCHAR(%(length)s)', 'text': 'CLOB'\
    +, 'password': 'VARCHAR(%(length)s)', 'blob': 'BLOB', 'upload': 'VARCHA\
    +R(%(length)s)', 'integer': 'INT', 'double': 'DOUBLE', 'decimal': 'NUME\
    +RIC(%(precision)s,%(scale)s)', 'date': 'DATE', 'time': 'TIME', 'dateti\
    +me': 'TIMESTAMP', 'id': 'INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY \
    +KEY NOT NULL', 'reference': 'INT, FOREIGN KEY (%(field_name)s) REFEREN\
    +CES %(foreign_key)s ON DELETE %(on_delete_action)s', 'reference FK': '\
    +, CONSTRAINT FK_%(constraint_name)s FOREIGN KEY (%(field_name)s) REFER\
    +...
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.dal.UseDatabaseStoredFile-class.html Index: applications/examples/static/epydoc/web2py.gluon.dal.UseDatabaseStoredFile-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.dal.UseDatabaseStoredFile-class.html +++ applications/examples/static/epydoc/web2py.gluon.dal.UseDatabaseStoredFile-class.html @@ -0,0 +1,211 @@ + + + + + web2py.gluon.dal.UseDatabaseStoredFile + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module dal :: + Class UseDatabaseStoredFile + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class UseDatabaseStoredFile

    source code

    +
    Known Subclasses:
    +
    + GoogleSQLAdapter +
    + +
    + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    file_exists(self, + filename) + source code + +
    + +
    +   + + + + + + +
    file_open(self, + filename, + mode='rb', + lock=True) + source code + +
    + +
    +   + + + + + + +
    file_close(self, + fileobj, + unlock=True) + source code + +
    + +
    +   + + + + + + +
    file_delete(self, + filename) + source code + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.debug-module.html Index: applications/examples/static/epydoc/web2py.gluon.debug-module.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.debug-module.html +++ applications/examples/static/epydoc/web2py.gluon.debug-module.html @@ -0,0 +1,297 @@ + + + + + web2py.gluon.debug + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module debug + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Module debug

    source code

    +This file is part of the web2py Web Framework Developed by Massimo Di + Pierro <mdipierro@cs.depaul.edu>, limodou <limodou@gmail.com> + and srackham <srackham@gmail.com>. License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)

    + + + + + + + + + + +
    + + + + + +
    Classes[hide private]
    +
    +   + + Pipe +
    + + + + + + + + + + + + + + + +
    + + + + + +
    Functions[hide private]
    +
    +   + + + + + + +
    set_trace()
    + breakpoint shortcut (like pdb)
    + source code + +
    + +
    +   + + + + + + +
    stop_trace()
    + stop waiting for the debugger (called atexit)
    + source code + +
    + +
    +   + + + + + + +
    communicate(command=1)
    + send command to debbuger, wait result
    + source code + +
    + +
    + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Variables[hide private]
    +
    +   + + logger = logging.getLogger("web2py") +
    +   + + pipe_in = Pipe('in') +
    +   + + pipe_out = Pipe('out') +
    +   + + debugger = pdb.Pdb(completekey= None, stdin= pipe_in, stdout= ... +
    + + + + + + +
    + + + + + +
    Variables Details[hide private]
    +
    + +
    + +
    +

    debugger

    + +
    +
    +
    +
    Value:
    +
    +pdb.Pdb(completekey= None, stdin= pipe_in, stdout= pipe_out,)
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.debug-pysrc.html Index: applications/examples/static/epydoc/web2py.gluon.debug-pysrc.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.debug-pysrc.html +++ applications/examples/static/epydoc/web2py.gluon.debug-pysrc.html @@ -0,0 +1,482 @@ + + + + + web2py.gluon.debug + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module debug + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Module web2py.gluon.debug

    +
    + 1  #!/usr/bin/env python 
    + 2  # -*- coding: utf-8 -*- 
    + 3   
    + 4  """ 
    + 5  This file is part of the web2py Web Framework 
    + 6  Developed by Massimo Di Pierro <mdipierro@cs.depaul.edu>, 
    + 7  limodou <limodou@gmail.com> and srackham <srackham@gmail.com>. 
    + 8  License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) 
    + 9   
    +10  """ 
    +11   
    +12  import logging 
    +13  import pdb 
    +14  import Queue 
    +15  import sys 
    +16   
    +17  logger = logging.getLogger("web2py") 
    +18   
    +
    19 -class Pipe(Queue.Queue): +
    20 - def __init__(self, name, mode='r', *args, **kwargs): +
    21 self.__name = name +22 Queue.Queue.__init__(self, *args, **kwargs) +
    23 +
    24 - def write(self, data): +
    25 logger.debug("debug %s writting %s" % (self.__name, data)) +26 self.put(data) +
    27 +
    28 - def flush(self): +
    29 # mark checkpoint (complete message) +30 logger.debug("debug %s flushing..." % self.__name) +31 self.put(None) +32 # wait until it is processed +33 self.join() +34 logger.debug("debug %s flush done" % self.__name) +
    35 +
    36 - def read(self, count=None, timeout=None): +
    37 logger.debug("debug %s reading..." % (self.__name, )) +38 data = self.get(block=True, timeout=timeout) +39 # signal that we are ready +40 self.task_done() +41 logger.debug("debug %s read %s" % (self.__name, data)) +42 return data +
    43 +
    44 - def readline(self): +
    45 logger.debug("debug %s readline..." % (self.__name, )) +46 return self.read() +
    47 +48 +49 pipe_in = Pipe('in') +50 pipe_out = Pipe('out') +51 +52 debugger = pdb.Pdb(completekey=None, stdin=pipe_in, stdout=pipe_out,) +53 +
    54 -def set_trace(): +
    55 "breakpoint shortcut (like pdb)" +56 logger.info("DEBUG: set_trace!") +57 debugger.set_trace(sys._getframe().f_back) +
    58 +59 +
    60 -def stop_trace(): +
    61 "stop waiting for the debugger (called atexit)" +62 # this should prevent communicate is wait forever a command result +63 # and the main thread has finished +64 logger.info("DEBUG: stop_trace!") +65 pipe_out.write("debug finished!") +66 pipe_out.write(None) +
    67 #pipe_out.flush() +68 +
    69 -def communicate(command=None): +
    70 "send command to debbuger, wait result" +71 if command is not None: +72 logger.info("DEBUG: sending command %s" % command) +73 pipe_in.write(command) +74 #pipe_in.flush() +75 result = [] +76 while True: +77 data = pipe_out.read() +78 if data is None: +79 break +80 result.append(data) +81 logger.info("DEBUG: result %s" % repr(result)) +82 return ''.join(result) +
    83 +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.debug.Pipe-class.html Index: applications/examples/static/epydoc/web2py.gluon.debug.Pipe-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.debug.Pipe-class.html +++ applications/examples/static/epydoc/web2py.gluon.debug.Pipe-class.html @@ -0,0 +1,297 @@ + + + + + web2py.gluon.debug.Pipe + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module debug :: + Class Pipe + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class Pipe

    source code

    +
    +Queue.Queue --+
    +              |
    +             Pipe
    +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + name, + mode='r', + *args, + **kwargs) + source code + +
    + +
    +   + + + + + + +
    write(self, + data) + source code + +
    + +
    +   + + + + + + +
    flush(self) + source code + +
    + +
    +   + + + + + + +
    read(self, + count=1, + timeout=1) + source code + +
    + +
    +   + + + + + + +
    readline(self) + source code + +
    + +
    +

    Inherited from Queue.Queue: + empty, + full, + get, + get_nowait, + join, + put, + put_nowait, + qsize, + task_done +

    +

    Inherited from Queue.Queue (private): + _empty, + _full, + _get, + _init, + _put, + _qsize +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + name, + mode='r', + *args, + **kwargs) +
    (Constructor) +

    +
    source code  +
    + + +
    +
    Overrides: + Queue.Queue.__init__ +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.decoder-module.html Index: applications/examples/static/epydoc/web2py.gluon.decoder-module.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.decoder-module.html +++ applications/examples/static/epydoc/web2py.gluon.decoder-module.html @@ -0,0 +1,288 @@ + + + + + web2py.gluon.decoder + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module decoder + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Module decoder

    source code

    + + + + + + + + + + + + +
    + + + + + +
    Functions[hide private]
    +
    +   + + + + + + +
    autoDetectXMLEncoding(buffer)
    + buffer -> encoding_name +The buffer should be at least 4 bytes long.
    + source code + +
    + +
    +   + + + + + + +
    decoder(buffer) + source code + +
    + +
    + + + + + + + + + +
    + + + + + +
    Variables[hide private]
    +
    +   + + autodetect_dict = {(0, 0, 254, 255): 'ucs4_be', (0, 60, 0, 63)... +
    + + + + + + +
    + + + + + +
    Function Details[hide private]
    +
    + +
    + +
    + + +
    +

    autoDetectXMLEncoding(buffer) +

    +
    source code  +
    + +
    +buffer -> encoding_name
    +The buffer should be at least 4 bytes long.
    +    Returns None if encoding cannot be detected.
    +    Note that encoding_name might not have an installed
    +    decoder (e.g. EBCDIC)
    +
    +
    +
    +
    +
    +
    +
    + + + + + + +
    + + + + + +
    Variables Details[hide private]
    +
    + +
    + +
    +

    autodetect_dict

    + +
    +
    +
    +
    Value:
    +
    +{(0, 0, 254, 255): 'ucs4_be',
    + (0, 60, 0, 63): 'utf_16_be',
    + (60, 0, 63, 0): 'utf_16_le',
    + (60, 63, 120, 109): 'utf_8',
    + (76, 111, 167, 148): 'EBCDIC',
    + (254, 255, None, None): 'utf_16_be',
    + (255, 254, None, None): 'utf_16_le',
    + (255, 254, 0, 0): 'ucs4_le'}
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.decoder-pysrc.html Index: applications/examples/static/epydoc/web2py.gluon.decoder-pysrc.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.decoder-pysrc.html +++ applications/examples/static/epydoc/web2py.gluon.decoder-pysrc.html @@ -0,0 +1,197 @@ + + + + + web2py.gluon.decoder + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module decoder + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Module web2py.gluon.decoder

    +
    + 1  import codecs, encodings 
    + 2   
    + 3  """Caller will hand this library a buffer and ask it to either convert 
    + 4  it or auto-detect the type. 
    + 5   
    + 6  Based on http://code.activestate.com/recipes/52257/ 
    + 7   
    + 8  Licensed under the PSF License 
    + 9  """ 
    +10   
    +11  # None represents a potentially variable byte. "##" in the XML spec... 
    +12  autodetect_dict={ # bytepattern     : ("name", 
    +13                  (0x00, 0x00, 0xFE, 0xFF) : ("ucs4_be"), 
    +14                  (0xFF, 0xFE, 0x00, 0x00) : ("ucs4_le"), 
    +15                  (0xFE, 0xFF, None, None) : ("utf_16_be"), 
    +16                  (0xFF, 0xFE, None, None) : ("utf_16_le"), 
    +17                  (0x00, 0x3C, 0x00, 0x3F) : ("utf_16_be"), 
    +18                  (0x3C, 0x00, 0x3F, 0x00) : ("utf_16_le"), 
    +19                  (0x3C, 0x3F, 0x78, 0x6D): ("utf_8"), 
    +20                  (0x4C, 0x6F, 0xA7, 0x94): ("EBCDIC") 
    +21                   } 
    +22   
    +
    23 -def autoDetectXMLEncoding(buffer): +
    24 """ buffer -> encoding_name +25 The buffer should be at least 4 bytes long. +26 Returns None if encoding cannot be detected. +27 Note that encoding_name might not have an installed +28 decoder (e.g. EBCDIC) +29 """ +30 # a more efficient implementation would not decode the whole +31 # buffer at once but otherwise we'd have to decode a character at +32 # a time looking for the quote character...that's a pain +33 +34 encoding = "utf_8" # according to the XML spec, this is the default +35 # this code successively tries to refine the default +36 # whenever it fails to refine, it falls back to +37 # the last place encoding was set. +38 if len(buffer)>=4: +39 bytes = (byte1, byte2, byte3, byte4) = tuple(map(ord, buffer[0:4])) +40 enc_info = autodetect_dict.get(bytes, None) +41 if not enc_info: # try autodetection again removing potentially +42 # variable bytes +43 bytes = (byte1, byte2, None, None) +44 enc_info = autodetect_dict.get(bytes) +45 else: +46 enc_info = None +47 +48 if enc_info: +49 encoding = enc_info # we've got a guess... these are +50 #the new defaults +51 +52 # try to find a more precise encoding using xml declaration +53 secret_decoder_ring = codecs.lookup(encoding)[1] +54 (decoded,length) = secret_decoder_ring(buffer) +55 first_line = decoded.split("\n")[0] +56 if first_line and first_line.startswith(u"<?xml"): +57 encoding_pos = first_line.find(u"encoding") +58 if encoding_pos!=-1: +59 # look for double quote +60 quote_pos=first_line.find('"', encoding_pos) +61 +62 if quote_pos==-1: # look for single quote +63 quote_pos=first_line.find("'", encoding_pos) +64 +65 if quote_pos>-1: +66 quote_char,rest=(first_line[quote_pos], +67 first_line[quote_pos+1:]) +68 encoding=rest[:rest.find(quote_char)] +69 +70 return encoding +
    71 +
    72 -def decoder(buffer): +
    73 encoding = autoDetectXMLEncoding(buffer) +74 return buffer.decode(encoding).encode('utf8') +
    75 +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.fileutils-module.html Index: applications/examples/static/epydoc/web2py.gluon.fileutils-module.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.fileutils-module.html +++ applications/examples/static/epydoc/web2py.gluon.fileutils-module.html @@ -0,0 +1,649 @@ + + + + + web2py.gluon.fileutils + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module fileutils + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Module fileutils

    source code

    +This file is part of the web2py Web Framework Copyrighted by Massimo + Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Functions[hide private]
    +
    +   + + + + + + +
    read_file(filename, + mode='r')
    + returns content from filename, making sure to close the file + explicitly on exit.
    + source code + +
    + +
    +   + + + + + + +
    write_file(filename, + value, + mode='w')
    + writes <value> to filename, making sure to close the file + explicitly on exit.
    + source code + +
    + +
    +   + + + + + + +
    readlines_file(filename, + mode='r')
    + applies .split(' ') to the output of read_file()
    + source code + +
    + +
    +   + + + + + + +
    abspath(*relpath, + **base)
    + convert relative path to absolute path based (by default) on + applications_parent
    + source code + +
    + +
    +   + + + + + + +
    mktree(path) + source code + +
    + +
    +   + + + + + + +
    listdir(path, + expression='^.+$', + drop=True, + add_dirs=True, + sort=True)
    + like os.listdir() but you can specify a regex pattern to filter + files.
    + source code + +
    + +
    +   + + + + + + +
    recursive_unlink(f) + source code + +
    + +
    +   + + + + + + +
    cleanpath(path)
    + turns any expression/path into a valid filename.
    + source code + +
    + +
    +   + + + + + + +
    _extractall(filename, + path='.', + members=1) + source code + +
    + +
    +   + + + + + + +
    tar(file, + dir, + expression='^.+$')
    + tars dir into file, only tars file that match expression
    + source code + +
    + +
    +   + + + + + + +
    untar(file, + dir)
    + untar file into dir
    + source code + +
    + +
    +   + + + + + + +
    w2p_pack(filename, + path, + compiled=True) + source code + +
    + +
    +   + + + + + + +
    w2p_unpack(filename, + path, + delete_tar=True) + source code + +
    + +
    +   + + + + + + +
    w2p_pack_plugin(filename, + path, + plugin_name)
    + Pack the given plugin into a w2p file.
    + source code + +
    + +
    +   + + + + + + +
    w2p_unpack_plugin(filename, + path, + delete_tar=True) + source code + +
    + +
    +   + + + + + + +
    tar_compiled(file, + dir, + expression='^.+$')
    + used to tar a compiled application.
    + source code + +
    + +
    +   + + + + + + +
    up(path) + source code + +
    + +
    +   + + + + + + +
    get_session(request, + other_application='admin')
    + checks that user is authorized to access other_application
    + source code + +
    + +
    +   + + + + + + +
    check_credentials(request, + other_application='admin', + expiration=3600)
    + checks that user is authorized to access other_application
    + source code + +
    + +
    +   + + + + + + +
    fix_newlines(path) + source code + +
    + +
    +   + + + + + + +
    copystream(src, + dest, + size, + chunk_size=100000)
    + this is here because I think there is a bug in + shutil.copyfileobj
    + source code + +
    + +
    +   + + + + + + +
    make_fake_file_like_object() + source code + +
    + +
    + + + + + + +
    + + + + + +
    Function Details[hide private]
    +
    + +
    + +
    + + +
    +

    listdir(path, + expression='^.+$', + drop=True, + add_dirs=True, + sort=True) +

    +
    source code  +
    + + like os.listdir() but you can specify a regex pattern to filter files. + if add_dirs is True, the returned items will have the full path. +
    +
    +
    +
    + +
    + +
    + + +
    +

    cleanpath(path) +

    +
    source code  +
    + + turns any expression/path into a valid filename. replaces / with _ and + removes special characters. +
    +
    +
    +
    + +
    + +
    + + +
    +

    w2p_pack_plugin(filename, + path, + plugin_name) +

    +
    source code  +
    + +
    +Pack the given plugin into a w2p file.
    +Will match files at:
    +    <path>/*/plugin_[name].*
    +    <path>/*/plugin_[name]/*
    +
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    tar_compiled(file, + dir, + expression='^.+$') +

    +
    source code  +
    + + used to tar a compiled application. the content of models, views, + controllers is not stored in the tar file. +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.fileutils-pysrc.html Index: applications/examples/static/epydoc/web2py.gluon.fileutils-pysrc.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.fileutils-pysrc.html +++ applications/examples/static/epydoc/web2py.gluon.fileutils-pysrc.html @@ -0,0 +1,592 @@ + + + + + web2py.gluon.fileutils + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module fileutils + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Module web2py.gluon.fileutils

    +
    +  1  #!/usr/bin/env python 
    +  2  # -*- coding: utf-8 -*- 
    +  3   
    +  4  """ 
    +  5  This file is part of the web2py Web Framework 
    +  6  Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> 
    +  7  License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) 
    +  8  """ 
    +  9   
    + 10  import storage 
    + 11  import os 
    + 12  import re 
    + 13  import tarfile 
    + 14  import glob 
    + 15  import time 
    + 16  from http import HTTP 
    + 17  from gzip import open as gzopen 
    + 18  from settings import global_settings 
    + 19   
    + 20   
    + 21  __all__ = [ 
    + 22      'read_file', 
    + 23      'write_file', 
    + 24      'readlines_file', 
    + 25      'up', 
    + 26      'abspath', 
    + 27      'mktree', 
    + 28      'listdir', 
    + 29      'recursive_unlink', 
    + 30      'cleanpath', 
    + 31      'tar', 
    + 32      'untar', 
    + 33      'tar_compiled', 
    + 34      'get_session', 
    + 35      'check_credentials', 
    + 36      'w2p_pack', 
    + 37      'w2p_unpack', 
    + 38      'w2p_pack_plugin', 
    + 39      'w2p_unpack_plugin', 
    + 40      'fix_newlines', 
    + 41      'make_fake_file_like_object', 
    + 42      ] 
    + 43   
    +
    44 -def read_file(filename, mode='r'): +
    45 "returns content from filename, making sure to close the file explicitly on exit." + 46 f = open(filename, mode) + 47 try: + 48 return f.read() + 49 finally: + 50 f.close() +
    51 +
    52 -def write_file(filename, value, mode='w'): +
    53 "writes <value> to filename, making sure to close the file explicitly on exit." + 54 f = open(filename, mode) + 55 try: + 56 return f.write(value) + 57 finally: + 58 f.close() +
    59 +
    60 -def readlines_file(filename, mode='r'): +
    61 "applies .split('\n') to the output of read_file()" + 62 return read_file(filename, mode).split('\n') +
    63 +
    64 -def abspath(*relpath, **base): +
    65 "convert relative path to absolute path based (by default) on applications_parent" + 66 path = os.path.join(*relpath) + 67 gluon = base.get('gluon', False) + 68 if os.path.isabs(path): + 69 return path + 70 if gluon: + 71 return os.path.join(global_settings.gluon_parent, path) + 72 return os.path.join(global_settings.applications_parent, path) +
    73 + 74 +
    75 -def mktree(path): +
    76 head,tail =os.path.split(path) + 77 if head: + 78 if tail: mktree(head) + 79 if not os.path.exists(head): + 80 os.mkdir(head) +
    81 +
    82 -def listdir( + 83 path, + 84 expression='^.+$', + 85 drop=True, + 86 add_dirs=False, + 87 sort=True, + 88 ): +
    89 """ + 90 like os.listdir() but you can specify a regex pattern to filter files. + 91 if add_dirs is True, the returned items will have the full path. + 92 """ + 93 if path[-1:] != os.path.sep: + 94 path = path + os.path.sep + 95 if drop: + 96 n = len(path) + 97 else: + 98 n = 0 + 99 regex = re.compile(expression) +100 items = [] +101 for (root, dirs, files) in os.walk(path, topdown=True): +102 for dir in dirs[:]: +103 if dir.startswith('.'): +104 dirs.remove(dir) +105 if add_dirs: +106 items.append(root[n:]) +107 for file in sorted(files): +108 if regex.match(file) and not file.startswith('.'): +109 items.append(os.path.join(root, file)[n:]) +110 if sort: +111 return sorted(items) +112 else: +113 return items +
    114 +115 +123 +124 +
    125 -def cleanpath(path): +
    126 """ +127 turns any expression/path into a valid filename. replaces / with _ and +128 removes special characters. +129 """ +130 +131 items = path.split('.') +132 if len(items) > 1: +133 path = re.sub('[^\w\.]+', '_', '_'.join(items[:-1]) + '.' +134 + ''.join(items[-1:])) +135 else: +136 path = re.sub('[^\w\.]+', '_', ''.join(items[-1:])) +137 return path +
    138 +139 +
    140 -def _extractall(filename, path='.', members=None): +
    141 if not hasattr(tarfile.TarFile, 'extractall'): +142 from tarfile import ExtractError +143 +144 class TarFile(tarfile.TarFile): +145 +146 def extractall(self, path='.', members=None): +147 """Extract all members from the archive to the current working +148 directory and set owner, modification time and permissions on +149 directories afterwards. `path' specifies a different directory +150 to extract to. `members' is optional and must be a subset of the +151 list returned by getmembers(). +152 """ +153 +154 directories = [] +155 if members is None: +156 members = self +157 for tarinfo in members: +158 if tarinfo.isdir(): +159 +160 # Extract directory with a safe mode, so that +161 # all files below can be extracted as well. +162 +163 try: +164 os.makedirs(os.path.join(path, +165 tarinfo.name), 0777) +166 except EnvironmentError: +167 pass +168 directories.append(tarinfo) +169 else: +170 self.extract(tarinfo, path) +171 +172 # Reverse sort directories. +173 +174 directories.sort(lambda a, b: cmp(a.name, b.name)) +175 directories.reverse() +176 +177 # Set correct owner, mtime and filemode on directories. +178 +179 for tarinfo in directories: +180 path = os.path.join(path, tarinfo.name) +181 try: +182 self.chown(tarinfo, path) +183 self.utime(tarinfo, path) +184 self.chmod(tarinfo, path) +185 except ExtractError, e: +186 if self.errorlevel > 1: +187 raise +188 else: +189 self._dbg(1, 'tarfile: %s' % e) +
    190 +191 +192 _cls = TarFile +193 else: +194 _cls = tarfile.TarFile +195 +196 tar = _cls(filename, 'r') +197 ret = tar.extractall(path, members) +198 tar.close() +199 return ret +200 +
    201 -def tar(file, dir, expression='^.+$'): +
    202 """ +203 tars dir into file, only tars file that match expression +204 """ +205 +206 tar = tarfile.TarFile(file, 'w') +207 try: +208 for file in listdir(dir, expression, add_dirs=True): +209 tar.add(os.path.join(dir, file), file, False) +210 finally: +211 tar.close() +
    212 +
    213 -def untar(file, dir): +
    214 """ +215 untar file into dir +216 """ +217 +218 _extractall(file, dir) +
    219 +220 +
    221 -def w2p_pack(filename, path, compiled=False): +
    222 filename = abspath(filename) +223 path = abspath(path) +224 tarname = filename + '.tar' +225 if compiled: +226 tar_compiled(tarname, path, '^[\w\.\-]+$') +227 else: +228 tar(tarname, path, '^[\w\.\-]+$') +229 w2pfp = gzopen(filename, 'wb') +230 tarfp = open(tarname, 'rb') +231 w2pfp.write(tarfp.read()) +232 w2pfp.close() +233 tarfp.close() +234 os.unlink(tarname) +
    235 +
    236 -def w2p_unpack(filename, path, delete_tar=True): +
    237 filename = abspath(filename) +238 path = abspath(path) +239 if filename[-4:] == '.w2p' or filename[-3:] == '.gz': +240 if filename[-4:] == '.w2p': +241 tarname = filename[:-4] + '.tar' +242 else: +243 tarname = filename[:-3] + '.tar' +244 fgzipped = gzopen(filename, 'rb') +245 tarfile = open(tarname, 'wb') +246 tarfile.write(fgzipped.read()) +247 tarfile.close() +248 fgzipped.close() +249 else: +250 tarname = filename +251 untar(tarname, path) +252 if delete_tar: +253 os.unlink(tarname) +
    254 +255 +
    256 -def w2p_pack_plugin(filename, path, plugin_name): +
    257 """Pack the given plugin into a w2p file. +258 Will match files at: +259 <path>/*/plugin_[name].* +260 <path>/*/plugin_[name]/* +261 """ +262 filename = abspath(filename) +263 path = abspath(path) +264 if not filename.endswith('web2py.plugin.%s.w2p' % plugin_name): +265 raise Exception, "Not a web2py plugin name" +266 plugin_tarball = tarfile.open(filename, 'w:gz') +267 try: +268 app_dir = path +269 while app_dir[-1]=='/': +270 app_dir = app_dir[:-1] +271 files1=glob.glob(os.path.join(app_dir,'*/plugin_%s.*' % plugin_name)) +272 files2=glob.glob(os.path.join(app_dir,'*/plugin_%s/*' % plugin_name)) +273 for file in files1+files2: +274 plugin_tarball.add(file, arcname=file[len(app_dir)+1:]) +275 finally: +276 plugin_tarball.close() +
    277 +278 +
    279 -def w2p_unpack_plugin(filename, path, delete_tar=True): +
    280 filename = abspath(filename) +281 path = abspath(path) +282 if not os.path.basename(filename).startswith('web2py.plugin.'): +283 raise Exception, "Not a web2py plugin" +284 w2p_unpack(filename,path,delete_tar) +
    285 +286 +
    287 -def tar_compiled(file, dir, expression='^.+$'): +
    288 """ +289 used to tar a compiled application. +290 the content of models, views, controllers is not stored in the tar file. +291 """ +292 +293 tar = tarfile.TarFile(file, 'w') +294 for file in listdir(dir, expression, add_dirs=True): +295 filename = os.path.join(dir, file) +296 if os.path.islink(filename): +297 continue +298 if os.path.isfile(filename) and file[-4:] != '.pyc': +299 if file[:6] == 'models': +300 continue +301 if file[:5] == 'views': +302 continue +303 if file[:11] == 'controllers': +304 continue +305 if file[:7] == 'modules': +306 continue +307 tar.add(filename, file, False) +308 tar.close() +
    309 +
    310 -def up(path): +
    311 return os.path.dirname(os.path.normpath(path)) +
    312 +313 +
    314 -def get_session(request, other_application='admin'): +
    315 """ checks that user is authorized to access other_application""" +316 if request.application == other_application: +317 raise KeyError +318 try: +319 session_id = request.cookies['session_id_' + other_application].value +320 osession = storage.load_storage(os.path.join( +321 up(request.folder), other_application, 'sessions', session_id)) +322 except: +323 osession = storage.Storage() +324 return osession +
    325 +326 +
    327 -def check_credentials(request, other_application='admin', expiration = 60*60): +
    328 """ checks that user is authorized to access other_application""" +329 if request.env.web2py_runtime_gae: +330 from google.appengine.api import users +331 if users.is_current_user_admin(): +332 return True +333 else: +334 login_html = '<a href="%s">Sign in with your google account</a>.' \ +335 % users.create_login_url(request.env.path_info) +336 raise HTTP(200, '<html><body>%s</body></html>' % login_html) +337 else: +338 dt = time.time() - expiration +339 s = get_session(request, other_application) +340 return (s.authorized and s.last_time and s.last_time > dt) +
    341 +342 +
    343 -def fix_newlines(path): +
    344 regex = re.compile(r'''(\r +345 |\r| +346 )''') +347 for filename in listdir(path, '.*\.(py|html)$', drop=False): +348 rdata = read_file(filename, 'rb') +349 wdata = regex.sub('\n', rdata) +350 if wdata != rdata: +351 write_file(filename, wdata, 'wb') +
    352 +
    353 -def copystream( +354 src, +355 dest, +356 size, +357 chunk_size=10 ** 5, +358 ): +
    359 """ +360 this is here because I think there is a bug in shutil.copyfileobj +361 """ +362 while size > 0: +363 if size < chunk_size: +364 data = src.read(size) +365 else: +366 data = src.read(chunk_size) +367 length = len(data) +368 if length > size: +369 (data, length) = (data[:size], size) +370 size -= length +371 if length == 0: +372 break +373 dest.write(data) +374 if length < chunk_size: +375 break +376 dest.seek(0) +377 return +
    378 +379 +
    381 class LogFile(object): +382 def write(self, value): +383 pass +
    384 def close(self): +385 pass +386 return LogFile() +387 +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.globals-module.html Index: applications/examples/static/epydoc/web2py.gluon.globals-module.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.globals-module.html +++ applications/examples/static/epydoc/web2py.gluon.globals-module.html @@ -0,0 +1,206 @@ + + + + + web2py.gluon.globals + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module globals + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Module globals

    source code

    +
    +
    +This file is part of the web2py Web Framework
    +Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
    +License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
    +
    +Contains the classes for the global used variables:
    +
    +- Request
    +- Response
    +- Session
    +
    +


    + + + + + + + + + + + + + + + + +
    + + + + + +
    Classes[hide private]
    +
    +   + + Request
    + defines the request object and the default values of its members... +
    +   + + Response
    + defines the response object and the default values of its + members response.write( ) can be used to write in the output + html +
    +   + + Session
    + defines the session object and the default values of its members + (None) +
    + + + + + + + + + + + + +
    + + + + + +
    Variables[hide private]
    +
    +   + + regex_session_id = re.compile(r'^([\w-]+/)?[\w-\.]+$') +
    +   + + current = threading.local() +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.globals-pysrc.html Index: applications/examples/static/epydoc/web2py.gluon.globals-pysrc.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.globals-pysrc.html +++ applications/examples/static/epydoc/web2py.gluon.globals-pysrc.html @@ -0,0 +1,768 @@ + + + + + web2py.gluon.globals + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module globals + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Module web2py.gluon.globals

    +
    +  1  #!/usr/bin/env python 
    +  2  # -*- coding: utf-8 -*- 
    +  3   
    +  4  """ 
    +  5  This file is part of the web2py Web Framework 
    +  6  Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> 
    +  7  License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) 
    +  8   
    +  9  Contains the classes for the global used variables: 
    + 10   
    + 11  - Request 
    + 12  - Response 
    + 13  - Session 
    + 14   
    + 15  """ 
    + 16   
    + 17  from storage import Storage, List 
    + 18  from streamer import streamer, stream_file_or_304_or_206, DEFAULT_CHUNK_SIZE 
    + 19  from xmlrpc import handler 
    + 20  from contenttype import contenttype 
    + 21  from html import xmlescape, TABLE, TR, PRE 
    + 22  from http import HTTP 
    + 23  from fileutils import up 
    + 24  from serializers import json, custom_json 
    + 25  import settings 
    + 26  from utils import web2py_uuid 
    + 27  from settings import global_settings 
    + 28   
    + 29  import hashlib 
    + 30  import portalocker 
    + 31  import cPickle 
    + 32  import cStringIO 
    + 33  import datetime 
    + 34  import re 
    + 35  import Cookie 
    + 36  import os 
    + 37  import sys 
    + 38  import traceback 
    + 39  import threading 
    + 40   
    + 41  regex_session_id = re.compile('^([\w\-]+/)?[\w\-\.]+$') 
    + 42   
    + 43  __all__ = ['Request', 'Response', 'Session'] 
    + 44   
    + 45  current = threading.local()  # thread-local storage for request-scope globals 
    + 46   
    +
    47 -class Request(Storage): +
    48 + 49 """ + 50 defines the request object and the default values of its members + 51 + 52 - env: environment variables, by gluon.main.wsgibase() + 53 - cookies + 54 - get_vars + 55 - post_vars + 56 - vars + 57 - folder + 58 - application + 59 - function + 60 - args + 61 - extension + 62 - now: datetime.datetime.today() + 63 - restful() + 64 """ + 65 +
    66 - def __init__(self): +
    67 self.wsgi = Storage() # hooks to environ and start_response + 68 self.env = Storage() + 69 self.cookies = Cookie.SimpleCookie() + 70 self.get_vars = Storage() + 71 self.post_vars = Storage() + 72 self.vars = Storage() + 73 self.folder = None + 74 self.application = None + 75 self.function = None + 76 self.args = List() + 77 self.extension = None + 78 self.now = datetime.datetime.now() + 79 self.is_restful = False + 80 self.is_https = False + 81 self.is_local = False +
    82 +
    83 - def compute_uuid(self): +
    84 self.uuid = '%s/%s.%s.%s' % ( + 85 self.application, + 86 self.client.replace(':', '_'), + 87 self.now.strftime('%Y-%m-%d.%H-%M-%S'), + 88 web2py_uuid()) + 89 return self.uuid +
    90 +
    91 - def user_agent(self): +
    92 from gluon.contrib import user_agent_parser + 93 session = current.session + 94 session._user_agent = session._user_agent or \ + 95 user_agent_parser.detect(self.env.http_user_agent) + 96 return session._user_agent +
    97 +
    98 - def restful(self): +
    99 def wrapper(action,self=self): +100 def f(_action=action,_self=self,*a,**b): +101 self.is_restful = True +102 method = _self.env.request_method +103 if len(_self.args) and '.' in _self.args[-1]: +104 _self.args[-1],_self.extension = _self.args[-1].rsplit('.',1) +105 if not method in ['GET','POST','DELETE','PUT']: +106 raise HTTP(400,"invalid method") +107 rest_action = _action().get(method,None) +108 if not rest_action: +109 raise HTTP(400,"method not supported") +110 try: +111 return rest_action(*_self.args,**_self.vars) +112 except TypeError, e: +113 exc_type, exc_value, exc_traceback = sys.exc_info() +114 if len(traceback.extract_tb(exc_traceback))==1: +115 raise HTTP(400,"invalid arguments") +116 else: +117 raise e +
    118 f.__doc__ = action.__doc__ +119 f.__name__ = action.__name__ +120 return f +
    121 return wrapper +122 +123 +
    124 -class Response(Storage): +
    125 +126 """ +127 defines the response object and the default values of its members +128 response.write( ) can be used to write in the output html +129 """ +130 +
    131 - def __init__(self): +
    132 self.status = 200 +133 self.headers = Storage() +134 self.headers['X-Powered-By'] = 'web2py' +135 self.body = cStringIO.StringIO() +136 self.session_id = None +137 self.cookies = Cookie.SimpleCookie() +138 self.postprocessing = [] +139 self.flash = '' # used by the default view layout +140 self.meta = Storage() # used by web2py_ajax.html +141 self.menu = [] # used by the default view layout +142 self.files = [] # used by web2py_ajax.html +143 self.generic_patterns = [] # patterns to allow generic views +144 self._vars = None +145 self._caller = lambda f: f() +146 self._view_environment = None +147 self._custom_commit = None +148 self._custom_rollback = None +
    149 +
    150 - def write(self, data, escape=True): +
    151 if not escape: +152 self.body.write(str(data)) +153 else: +154 self.body.write(xmlescape(data)) +
    155 +
    156 - def render(self, *a, **b): +
    157 from compileapp import run_view_in +158 if len(a) > 2: +159 raise SyntaxError, 'Response.render can be called with two arguments, at most' +160 elif len(a) == 2: +161 (view, self._vars) = (a[0], a[1]) +162 elif len(a) == 1 and isinstance(a[0], str): +163 (view, self._vars) = (a[0], {}) +164 elif len(a) == 1 and hasattr(a[0], 'read') and callable(a[0].read): +165 (view, self._vars) = (a[0], {}) +166 elif len(a) == 1 and isinstance(a[0], dict): +167 (view, self._vars) = (None, a[0]) +168 else: +169 (view, self._vars) = (None, {}) +170 self._vars.update(b) +171 self._view_environment.update(self._vars) +172 if view: +173 import cStringIO +174 (obody, oview) = (self.body, self.view) +175 (self.body, self.view) = (cStringIO.StringIO(), view) +176 run_view_in(self._view_environment) +177 page = self.body.getvalue() +178 self.body.close() +179 (self.body, self.view) = (obody, oview) +180 else: +181 run_view_in(self._view_environment) +182 page = self.body.getvalue() +183 return page +
    184 +
    185 - def stream( +186 self, +187 stream, +188 chunk_size = DEFAULT_CHUNK_SIZE, +189 request=None, +190 ): +
    191 """ +192 if a controller function:: +193 +194 return response.stream(file, 100) +195 +196 the file content will be streamed at 100 bytes at the time +197 """ +198 +199 if isinstance(stream, (str, unicode)): +200 stream_file_or_304_or_206(stream, +201 chunk_size=chunk_size, +202 request=request, +203 headers=self.headers) +204 +205 # ## the following is for backward compatibility +206 +207 if hasattr(stream, 'name'): +208 filename = stream.name +209 else: +210 filename = None +211 keys = [item.lower() for item in self.headers] +212 if filename and not 'content-type' in keys: +213 self.headers['Content-Type'] = contenttype(filename) +214 if filename and not 'content-length' in keys: +215 try: +216 self.headers['Content-Length'] = \ +217 os.path.getsize(filename) +218 except OSError: +219 pass +220 if request and request.env.web2py_use_wsgi_file_wrapper: +221 wrapped = request.env.wsgi_file_wrapper(stream, chunk_size) +222 else: +223 wrapped = streamer(stream, chunk_size=chunk_size) +224 return wrapped +
    225 +
    226 - def download(self, request, db, chunk_size = DEFAULT_CHUNK_SIZE, attachment=True): +
    227 """ +228 example of usage in controller:: +229 +230 def download(): +231 return response.download(request, db) +232 +233 downloads from http://..../download/filename +234 """ +235 +236 import contenttype as c +237 if not request.args: +238 raise HTTP(404) +239 name = request.args[-1] +240 items = re.compile('(?P<table>.*?)\.(?P<field>.*?)\..*')\ +241 .match(name) +242 if not items: +243 raise HTTP(404) +244 (t, f) = (items.group('table'), items.group('field')) +245 field = db[t][f] +246 try: +247 (filename, stream) = field.retrieve(name) +248 except IOError: +249 raise HTTP(404) +250 self.headers['Content-Type'] = c.contenttype(name) +251 if attachment: +252 self.headers['Content-Disposition'] = \ +253 "attachment; filename=%s" % filename +254 return self.stream(stream, chunk_size = chunk_size, request=request) +
    255 +
    256 - def json(self, data, default=None): +
    257 return json(data, default = default or custom_json) +
    258 +
    259 - def xmlrpc(self, request, methods): +
    260 """ +261 assuming:: +262 +263 def add(a, b): +264 return a+b +265 +266 if a controller function \"func\":: +267 +268 return response.xmlrpc(request, [add]) +269 +270 the controller will be able to handle xmlrpc requests for +271 the add function. Example:: +272 +273 import xmlrpclib +274 connection = xmlrpclib.ServerProxy('http://hostname/app/contr/func') +275 print connection.add(3, 4) +276 +277 """ +278 +279 return handler(request, self, methods) +
    280 +
    281 - def toolbar(self): +
    282 from html import DIV, SCRIPT, BEAUTIFY, TAG, URL +283 BUTTON = TAG.button +284 admin = URL("admin","default","design", +285 args=current.request.application) +286 from gluon.dal import thread +287 dbstats = [TABLE(*[TR(PRE(row[0]),'%.2fms' % (row[1]*1000)) \ +288 for row in i.db._timings]) \ +289 for i in thread.instances] +290 u = web2py_uuid() +291 return DIV( +292 BUTTON('design',_onclick="document.location='%s'" % admin), +293 BUTTON('request',_onclick="jQuery('#request-%s').slideToggle()"%u), +294 DIV(BEAUTIFY(current.request),_class="hidden",_id="request-%s"%u), +295 BUTTON('session',_onclick="jQuery('#session-%s').slideToggle()"%u), +296 DIV(BEAUTIFY(current.session),_class="hidden",_id="session-%s"%u), +297 BUTTON('response',_onclick="jQuery('#response-%s').slideToggle()"%u), +298 DIV(BEAUTIFY(current.response),_class="hidden",_id="response-%s"%u), +299 BUTTON('db stats',_onclick="jQuery('#db-stats-%s').slideToggle()"%u), +300 DIV(BEAUTIFY(dbstats),_class="hidden",_id="db-stats-%s"%u), +301 SCRIPT("jQuery('.hidden').hide()") +302 ) +
    303 +
    304 -class Session(Storage): +
    305 +306 """ +307 defines the session object and the default values of its members (None) +308 """ +309 +
    310 - def connect( +311 self, +312 request, +313 response, +314 db=None, +315 tablename='web2py_session', +316 masterapp=None, +317 migrate=True, +318 separate = None, +319 check_client=False, +320 ): +
    321 """ +322 separate can be separate=lambda(session_name): session_name[-2:] +323 and it is used to determine a session prefix. +324 separate can be True and it is set to session_name[-2:] +325 """ +326 if separate == True: +327 separate = lambda session_name: session_name[-2:] +328 self._unlock(response) +329 if not masterapp: +330 masterapp = request.application +331 response.session_id_name = 'session_id_%s' % masterapp.lower() +332 +333 if not db: +334 if global_settings.db_sessions is True or masterapp in global_settings.db_sessions: +335 return +336 response.session_new = False +337 client = request.client.replace(':', '.') +338 if response.session_id_name in request.cookies: +339 response.session_id = \ +340 request.cookies[response.session_id_name].value +341 if regex_session_id.match(response.session_id): +342 response.session_filename = \ +343 os.path.join(up(request.folder), masterapp, +344 'sessions', response.session_id) +345 else: +346 response.session_id = None +347 if response.session_id: +348 try: +349 response.session_file = \ +350 open(response.session_filename, 'rb+') +351 try: +352 portalocker.lock(response.session_file, +353 portalocker.LOCK_EX) +354 response.session_locked = True +355 self.update(cPickle.load(response.session_file)) +356 response.session_file.seek(0) +357 oc = response.session_filename.split('/')[-1].split('-')[0] +358 if check_client and client!=oc: +359 raise Exception, "cookie attack" +360 finally: +361 pass +362 #This causes admin login to break. Must find out why. +363 #self._close(response) +364 except: +365 response.session_id = None +366 if not response.session_id: +367 uuid = web2py_uuid() +368 response.session_id = '%s-%s' % (client, uuid) +369 if separate: +370 prefix = separate(response.session_id) +371 response.session_id = '%s/%s' % (prefix,response.session_id) +372 response.session_filename = \ +373 os.path.join(up(request.folder), masterapp, +374 'sessions', response.session_id) +375 response.session_new = True +376 else: +377 if global_settings.db_sessions is not True: +378 global_settings.db_sessions.add(masterapp) +379 response.session_db = True +380 if response.session_file: +381 self._close(response) +382 if settings.global_settings.web2py_runtime_gae: +383 # in principle this could work without GAE +384 request.tickets_db = db +385 if masterapp == request.application: +386 table_migrate = migrate +387 else: +388 table_migrate = False +389 tname = tablename + '_' + masterapp +390 table = db.get(tname, None) +391 if table is None: +392 table = db.define_table( +393 tname, +394 db.Field('locked', 'boolean', default=False), +395 db.Field('client_ip', length=64), +396 db.Field('created_datetime', 'datetime', +397 default=request.now), +398 db.Field('modified_datetime', 'datetime'), +399 db.Field('unique_key', length=64), +400 db.Field('session_data', 'blob'), +401 migrate=table_migrate, +402 ) +403 try: +404 key = request.cookies[response.session_id_name].value +405 (record_id, unique_key) = key.split(':') +406 if record_id == '0': +407 raise Exception, 'record_id == 0' +408 rows = db(table.id == record_id).select() +409 if len(rows) == 0 or rows[0].unique_key != unique_key: +410 raise Exception, 'No record' +411 +412 # rows[0].update_record(locked=True) +413 +414 session_data = cPickle.loads(rows[0].session_data) +415 self.update(session_data) +416 except Exception: +417 record_id = None +418 unique_key = web2py_uuid() +419 session_data = {} +420 response._dbtable_and_field = \ +421 (response.session_id_name, table, record_id, unique_key) +422 response.session_id = '%s:%s' % (record_id, unique_key) +423 response.cookies[response.session_id_name] = response.session_id +424 response.cookies[response.session_id_name]['path'] = '/' +425 self.__hash = hashlib.md5(str(self)).digest() +426 if self.flash: +427 (response.flash, self.flash) = (self.flash, None) +
    428 +
    429 - def is_new(self): +
    430 if self._start_timestamp: +431 return False +432 else: +433 self._start_timestamp = datetime.datetime.today() +434 return True +
    435 +
    436 - def is_expired(self, seconds = 3600): +
    437 now = datetime.datetime.today() +438 if not self._last_timestamp or \ +439 self._last_timestamp + datetime.timedelta(seconds = seconds) > now: +440 self._last_timestamp = now +441 return False +442 else: +443 return True +
    444 +
    445 - def secure(self): +
    446 self._secure = True +
    447 +
    448 - def forget(self, response=None): +
    449 self._close(response) +450 self._forget = True +
    451 +
    452 - def _try_store_in_db(self, request, response): +
    453 +454 # don't save if file-based sessions, no session id, or session being forgotten +455 if not response.session_db or not response.session_id or self._forget: +456 return +457 +458 # don't save if no change to session +459 __hash = self.__hash +460 if __hash is not None: +461 del self.__hash +462 if __hash == hashlib.md5(str(self)).digest(): +463 return +464 +465 (record_id_name, table, record_id, unique_key) = \ +466 response._dbtable_and_field +467 dd = dict(locked=False, client_ip=request.env.remote_addr, +468 modified_datetime=request.now, +469 session_data=cPickle.dumps(dict(self)), +470 unique_key=unique_key) +471 if record_id: +472 table._db(table.id == record_id).update(**dd) +473 else: +474 record_id = table.insert(**dd) +475 response.cookies[response.session_id_name] = '%s:%s'\ +476 % (record_id, unique_key) +477 response.cookies[response.session_id_name]['path'] = '/' +
    478 +
    479 - def _try_store_on_disk(self, request, response): +
    480 +481 # don't save if sessions not not file-based +482 if response.session_db: +483 return +484 +485 # don't save if no change to session +486 __hash = self.__hash +487 if __hash is not None: +488 del self.__hash +489 if __hash == hashlib.md5(str(self)).digest(): +490 self._close(response) +491 return +492 +493 if not response.session_id or self._forget: +494 self._close(response) +495 return +496 +497 if response.session_new: +498 # Tests if the session sub-folder exists, if not, create it +499 session_folder = os.path.dirname(response.session_filename) +500 if not os.path.exists(session_folder): +501 os.mkdir(session_folder) +502 response.session_file = open(response.session_filename, 'wb') +503 portalocker.lock(response.session_file, portalocker.LOCK_EX) +504 response.session_locked = True +505 +506 if response.session_file: +507 cPickle.dump(dict(self), response.session_file) +508 response.session_file.truncate() +509 self._close(response) +
    510 +
    511 - def _unlock(self, response): +
    512 if response and response.session_file and response.session_locked: +513 try: +514 portalocker.unlock(response.session_file) +515 response.session_locked = False +516 except: ### this should never happen but happens in Windows +517 pass +
    518 +
    519 - def _close(self, response): +
    520 if response and response.session_file: +521 self._unlock(response) +522 try: +523 response.session_file.close() +524 del response.session_file +525 except: +526 pass +
    527 +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.globals.Request-class.html Index: applications/examples/static/epydoc/web2py.gluon.globals.Request-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.globals.Request-class.html +++ applications/examples/static/epydoc/web2py.gluon.globals.Request-class.html @@ -0,0 +1,357 @@ + + + + + web2py.gluon.globals.Request + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module globals :: + Class Request + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class Request

    source code

    +
    + object --+        
    +          |        
    +       dict --+    
    +              |    
    +storage.Storage --+
    +                  |
    +                 Request
    +
    + +
    +
    +
    +defines the request object and the default values of its members
    +
    +- env: environment variables, by gluon.main.wsgibase()
    +- cookies
    +- get_vars
    +- post_vars
    +- vars
    +- folder
    +- application
    +- function
    +- args
    +- extension
    +- now: datetime.datetime.today()
    +- restful()
    +
    +


    + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self)
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    compute_uuid(self) + source code + +
    + +
    +   + + + + + + +
    user_agent(self) + source code + +
    + +
    +   + + + + + + +
    restful(self) + source code + +
    + +
    +

    Inherited from storage.Storage: + __delattr__, + __getattr__, + __getstate__, + __repr__, + __setattr__, + __setstate__, + getfirst, + getlast, + getlist +

    +

    Inherited from dict: + __cmp__, + __contains__, + __delitem__, + __eq__, + __ge__, + __getattribute__, + __getitem__, + __gt__, + __hash__, + __iter__, + __le__, + __len__, + __lt__, + __ne__, + __new__, + __setitem__, + clear, + copy, + fromkeys, + get, + has_key, + items, + iteritems, + iterkeys, + itervalues, + keys, + pop, + popitem, + setdefault, + update, + values +

    +

    Inherited from object: + __reduce__, + __reduce_ex__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Returns:
    +
    +new empty dictionary
    +
    +
    +
    Overrides: + dict.__init__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.globals.Response-class.html Index: applications/examples/static/epydoc/web2py.gluon.globals.Response-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.globals.Response-class.html +++ applications/examples/static/epydoc/web2py.gluon.globals.Response-class.html @@ -0,0 +1,513 @@ + + + + + web2py.gluon.globals.Response + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module globals :: + Class Response + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class Response

    source code

    +
    + object --+        
    +          |        
    +       dict --+    
    +              |    
    +storage.Storage --+
    +                  |
    +                 Response
    +
    + +
    +defines the response object and the default values of its members + response.write( ) can be used to write in the output html

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self)
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    write(self, + data, + escape=True) + source code + +
    + +
    +   + + + + + + +
    render(self, + *a, + **b) + source code + +
    + +
    +   + + + + + + +
    stream(self, + stream, + chunk_size=65536, + request=1)
    + if a controller function:
    + source code + +
    + +
    +   + + + + + + +
    download(self, + request, + db, + chunk_size=65536, + attachment=True)
    + example of usage in controller:
    + source code + +
    + +
    +   + + + + + + +
    json(self, + data, + default=1) + source code + +
    + +
    +   + + + + + + +
    xmlrpc(self, + request, + methods)
    + assuming:
    + source code + +
    + +
    +   + + + + + + +
    toolbar(self) + source code + +
    + +
    +

    Inherited from storage.Storage: + __delattr__, + __getattr__, + __getstate__, + __repr__, + __setattr__, + __setstate__, + getfirst, + getlast, + getlist +

    +

    Inherited from dict: + __cmp__, + __contains__, + __delitem__, + __eq__, + __ge__, + __getattribute__, + __getitem__, + __gt__, + __hash__, + __iter__, + __le__, + __len__, + __lt__, + __ne__, + __new__, + __setitem__, + clear, + copy, + fromkeys, + get, + has_key, + items, + iteritems, + iterkeys, + itervalues, + keys, + pop, + popitem, + setdefault, + update, + values +

    +

    Inherited from object: + __reduce__, + __reduce_ex__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Returns:
    +
    +new empty dictionary
    +
    +
    +
    Overrides: + dict.__init__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    stream(self, + stream, + chunk_size=65536, + request=1) +

    +
    source code  +
    + + if a controller function: +
    +   return response.stream(file, 100)
    +
    + the file content will be streamed at 100 bytes at the time +
    +
    +
    +
    + +
    + +
    + + +
    +

    download(self, + request, + db, + chunk_size=65536, + attachment=True) +

    +
    source code  +
    + + example of usage in controller: +
    +   def download():
    +       return response.download(request, db)
    +
    + downloads from http://..../download/filename +
    +
    +
    +
    + +
    + +
    + + +
    +

    xmlrpc(self, + request, + methods) +

    +
    source code  +
    + + assuming: +
    +   def add(a, b):
    +       return a+b
    +
    + if a controller function "func": +
    +   return response.xmlrpc(request, [add])
    +
    + the controller will be able to handle xmlrpc requests for the add + function. Example: +
    +   import xmlrpclib
    +   connection = xmlrpclib.ServerProxy('http://hostname/app/contr/func')
    +   print connection.add(3, 4)
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.globals.Session-class.html Index: applications/examples/static/epydoc/web2py.gluon.globals.Session-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.globals.Session-class.html +++ applications/examples/static/epydoc/web2py.gluon.globals.Session-class.html @@ -0,0 +1,437 @@ + + + + + web2py.gluon.globals.Session + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module globals :: + Class Session + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class Session

    source code

    +
    + object --+        
    +          |        
    +       dict --+    
    +              |    
    +storage.Storage --+
    +                  |
    +                 Session
    +
    + +
    +defines the session object and the default values of its members + (None)

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    connect(self, + request, + response, + db=1, + tablename='web2py_session', + masterapp=1, + migrate=True, + separate=1, + check_client=True)
    + separate can be separate=lambda(session_name): session_name[-2:] + and it is used to determine a session prefix.
    + source code + +
    + +
    +   + + + + + + +
    is_new(self) + source code + +
    + +
    +   + + + + + + +
    is_expired(self, + seconds=3600) + source code + +
    + +
    +   + + + + + + +
    secure(self) + source code + +
    + +
    +   + + + + + + +
    forget(self, + response=1) + source code + +
    + +
    +   + + + + + + +
    _try_store_in_db(self, + request, + response) + source code + +
    + +
    +   + + + + + + +
    _try_store_on_disk(self, + request, + response) + source code + +
    + +
    +   + + + + + + +
    _unlock(self, + response) + source code + +
    + +
    +   + + + + + + +
    _close(self, + response) + source code + +
    + +
    +

    Inherited from storage.Storage: + __delattr__, + __getattr__, + __getstate__, + __repr__, + __setattr__, + __setstate__, + getfirst, + getlast, + getlist +

    +

    Inherited from dict: + __cmp__, + __contains__, + __delitem__, + __eq__, + __ge__, + __getattribute__, + __getitem__, + __gt__, + __hash__, + __init__, + __iter__, + __le__, + __len__, + __lt__, + __ne__, + __new__, + __setitem__, + clear, + copy, + fromkeys, + get, + has_key, + items, + iteritems, + iterkeys, + itervalues, + keys, + pop, + popitem, + setdefault, + update, + values +

    +

    Inherited from object: + __reduce__, + __reduce_ex__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    connect(self, + request, + response, + db=1, + tablename='web2py_session', + masterapp=1, + migrate=True, + separate=1, + check_client=True) +

    +
    source code  +
    + + separate can be separate=lambda(session_name): session_name[-2:] and + it is used to determine a session prefix. separate can be True and it is + set to session_name[-2:] +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.highlight-module.html Index: applications/examples/static/epydoc/web2py.gluon.highlight-module.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.highlight-module.html +++ applications/examples/static/epydoc/web2py.gluon.highlight-module.html @@ -0,0 +1,185 @@ + + + + + web2py.gluon.highlight + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module highlight + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Module highlight

    source code

    +This file is part of the web2py Web Framework Copyrighted by Massimo + Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)

    + + + + + + + + + + +
    + + + + + +
    Classes[hide private]
    +
    +   + + Highlighter
    + Do syntax highlighting. +
    + + + + + + + + + +
    + + + + + +
    Functions[hide private]
    +
    +   + + + + + + +
    highlight(code, + language, + link='/examples/globals/vars/', + counter=1, + styles={}, + highlight_line=1, + attributes={}) + source code + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.highlight-pysrc.html Index: applications/examples/static/epydoc/web2py.gluon.highlight-pysrc.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.highlight-pysrc.html +++ applications/examples/static/epydoc/web2py.gluon.highlight-pysrc.html @@ -0,0 +1,493 @@ + + + + + web2py.gluon.highlight + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module highlight + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Module web2py.gluon.highlight

    +
    +  1  #!/usr/bin/env python 
    +  2  # -*- coding: utf-8 -*- 
    +  3   
    +  4  """ 
    +  5  This file is part of the web2py Web Framework 
    +  6  Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> 
    +  7  License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) 
    +  8  """ 
    +  9   
    + 10  import re 
    + 11  import cgi 
    + 12   
    + 13  __all__ = ['highlight'] 
    + 14   
    + 15   
    +
    16 -class Highlighter(object): +
    17 + 18 """ + 19 Do syntax highlighting. + 20 """ + 21 +
    22 - def __init__( + 23 self, + 24 mode, + 25 link=None, + 26 styles={}, + 27 ): +
    28 """ + 29 Initialise highlighter: + 30 mode = language (PYTHON, WEB2PY,C, CPP, HTML, HTML_PLAIN) + 31 """ + 32 + 33 mode = mode.upper() + 34 if link and link[-1] != '/': + 35 link = link + '/' + 36 self.link = link + 37 self.styles = styles + 38 self.output = [] + 39 self.span_style = None + 40 if mode == 'WEB2PY': + 41 (mode, self.suppress_tokens) = ('PYTHON', []) + 42 elif mode == 'PYTHON': + 43 self.suppress_tokens = ['GOTOHTML'] + 44 elif mode == 'CPP': + 45 (mode, self.suppress_tokens) = ('C', []) + 46 elif mode == 'C': + 47 self.suppress_tokens = ['CPPKEYWORD'] + 48 elif mode == 'HTML_PLAIN': + 49 (mode, self.suppress_tokens) = ('HTML', ['GOTOPYTHON']) + 50 elif mode == 'HTML': + 51 self.suppress_tokens = [] + 52 else: + 53 raise SyntaxError, 'Unknown mode: %s' % mode + 54 self.mode = mode +
    55 +
    56 - def c_tokenizer( + 57 self, + 58 token, + 59 match, + 60 style, + 61 ): +
    62 """ + 63 Callback for C specific highlighting. + 64 """ + 65 + 66 value = cgi.escape(match.group()) + 67 self.change_style(token, style) + 68 self.output.append(value) +
    69 +
    70 - def python_tokenizer( + 71 self, + 72 token, + 73 match, + 74 style, + 75 ): +
    76 """ + 77 Callback for python specific highlighting. + 78 """ + 79 + 80 value = cgi.escape(match.group()) + 81 if token == 'MULTILINESTRING': + 82 self.change_style(token, style) + 83 self.output.append(value) + 84 self.strMultilineString = match.group(1) + 85 return 'PYTHONMultilineString' + 86 elif token == 'ENDMULTILINESTRING': + 87 if match.group(1) == self.strMultilineString: + 88 self.output.append(value) + 89 self.strMultilineString = '' + 90 return 'PYTHON' + 91 if style and style[:5] == 'link:': + 92 self.change_style(None, None) + 93 (url, style) = style[5:].split(';', 1) + 94 if url == 'None' or url == '': + 95 self.output.append('<span style="%s">%s</span>' + 96 % (style, value)) + 97 else: + 98 self.output.append('<a href="%s%s" style="%s">%s</a>' + 99 % (url, value, style, value)) +100 else: +101 self.change_style(token, style) +102 self.output.append(value) +103 if token == 'GOTOHTML': +104 return 'HTML' +105 return None +
    106 +
    107 - def html_tokenizer( +108 self, +109 token, +110 match, +111 style, +112 ): +
    113 """ +114 Callback for HTML specific highlighting. +115 """ +116 +117 value = cgi.escape(match.group()) +118 self.change_style(token, style) +119 self.output.append(value) +120 if token == 'GOTOPYTHON': +121 return 'PYTHON' +122 return None +
    123 +124 all_styles = { +125 'C': (c_tokenizer, ( +126 ('COMMENT', re.compile(r'//.*\r?\n'), +127 'color: green; font-style: italic'), +128 ('MULTILINECOMMENT', re.compile(r'/\*.*?\*/', re.DOTALL), +129 'color: green; font-style: italic'), +130 ('PREPROCESSOR', re.compile(r'\s*#.*?[^\\]\s*\n', +131 re.DOTALL), 'color: magenta; font-style: italic'), +132 ('PUNC', re.compile(r'[-+*!&|^~/%\=<>\[\]{}(),.:]'), +133 'font-weight: bold'), +134 ('NUMBER', +135 re.compile(r'0x[0-9a-fA-F]+|[+-]?\d+(\.\d+)?([eE][+-]\d+)?|\d+'), +136 'color: red'), +137 ('KEYWORD', re.compile(r'(sizeof|int|long|short|char|void|' +138 + r'signed|unsigned|float|double|' +139 + r'goto|break|return|continue|asm|' +140 + r'case|default|if|else|switch|while|for|do|' +141 + r'struct|union|enum|typedef|' +142 + r'static|register|auto|volatile|extern|const)(?![a-zA-Z0-9_])'), +143 'color:#185369; font-weight: bold'), +144 ('CPPKEYWORD', +145 re.compile(r'(class|private|protected|public|template|new|delete|' +146 + r'this|friend|using|inline|export|bool|throw|try|catch|' +147 + r'operator|typeid|virtual)(?![a-zA-Z0-9_])'), +148 'color: blue; font-weight: bold'), +149 ('STRING', re.compile(r'r?u?\'(.*?)(?<!\\)\'|"(.*?)(?<!\\)"'), +150 'color: #FF9966'), +151 ('IDENTIFIER', re.compile(r'[a-zA-Z_][a-zA-Z0-9_]*'), +152 None), +153 ('WHITESPACE', re.compile(r'[ \r\n]+'), 'Keep'), +154 )), +155 'PYTHON': (python_tokenizer, ( +156 ('GOTOHTML', re.compile(r'\}\}'), 'color: red'), +157 ('PUNC', re.compile(r'[-+*!|&^~/%\=<>\[\]{}(),.:]'), +158 'font-weight: bold'), +159 ('NUMBER', +160 re.compile(r'0x[0-9a-fA-F]+|[+-]?\d+(\.\d+)?([eE][+-]\d+)?|\d+' +161 ), 'color: red'), +162 ('KEYWORD', +163 re.compile(r'(def|class|break|continue|del|exec|finally|pass|' +164 + r'print|raise|return|try|except|global|assert|lambda|' +165 + r'yield|for|while|if|elif|else|and|in|is|not|or|import|' +166 + r'from|True|False)(?![a-zA-Z0-9_])'), +167 'color:#185369; font-weight: bold'), +168 ('WEB2PY', +169 re.compile(r'(request|response|session|cache|redirect|local_import|HTTP|TR|XML|URL|BEAUTIFY|A|BODY|BR|B|CAT|CENTER|CODE|DIV|EM|EMBED|FIELDSET|LEGEND|FORM|H1|H2|H3|H4|H5|H6|IFRAME|HEAD|HR|HTML|I|IMG|INPUT|LABEL|LI|LINK|MARKMIN|MENU|META|OBJECT|OL|ON|OPTION|P|PRE|SCRIPT|SELECT|SPAN|STYLE|TABLE|THEAD|TBODY|TFOOT|TAG|TD|TEXTAREA|TH|TITLE|TT|T|UL|XHTML|IS_SLUG|IS_STRONG|IS_LOWER|IS_UPPER|IS_ALPHANUMERIC|IS_DATETIME|IS_DATETIME_IN_RANGE|IS_DATE|IS_DATE_IN_RANGE|IS_DECIMAL_IN_RANGE|IS_EMAIL|IS_EXPR|IS_FLOAT_IN_RANGE|IS_IMAGE|IS_INT_IN_RANGE|IS_IN_SET|IS_IPV4|IS_LIST_OF|IS_LENGTH|IS_MATCH|IS_EQUAL_TO|IS_EMPTY_OR|IS_NULL_OR|IS_NOT_EMPTY|IS_TIME|IS_UPLOAD_FILENAME|IS_URL|CLEANUP|CRYPT|IS_IN_DB|IS_NOT_IN_DB|DAL|Field|SQLFORM|SQLTABLE|xmlescape|embed64)(?![a-zA-Z0-9_])' +170 ), 'link:%(link)s;text-decoration:None;color:#FF5C1F;'), +171 ('MAGIC', re.compile(r'self|None'), +172 'color:#185369; font-weight: bold'), +173 ('MULTILINESTRING', re.compile(r'r?u?(\'\'\'|""")'), +174 'color: #FF9966'), +175 ('STRING', re.compile(r'r?u?\'(.*?)(?<!\\)\'|"(.*?)(?<!\\)"' +176 ), 'color: #FF9966'), +177 ('IDENTIFIER', re.compile(r'[a-zA-Z_][a-zA-Z0-9_]*'), +178 None), +179 ('COMMENT', re.compile(r'\#.*\r?\n'), +180 'color: green; font-style: italic'), +181 ('WHITESPACE', re.compile(r'[ \r\n]+'), 'Keep'), +182 )), +183 'PYTHONMultilineString': (python_tokenizer, +184 (('ENDMULTILINESTRING', +185 re.compile(r'.*?("""|\'\'\')', +186 re.DOTALL), 'color: darkred'), )), +187 'HTML': (html_tokenizer, ( +188 ('GOTOPYTHON', re.compile(r'\{\{'), 'color: red'), +189 ('COMMENT', re.compile(r'<!--[^>]*-->|<!>'), +190 'color: green; font-style: italic'), +191 ('XMLCRAP', re.compile(r'<![^>]*>'), +192 'color: blue; font-style: italic'), +193 ('SCRIPT', re.compile(r'<script .*?</script>', re.IGNORECASE +194 + re.DOTALL), 'color: black'), +195 ('TAG', re.compile(r'</?\s*[a-zA-Z0-9]+'), +196 'color: darkred; font-weight: bold'), +197 ('ENDTAG', re.compile(r'/?>'), +198 'color: darkred; font-weight: bold'), +199 )), +200 } +201 +
    202 - def highlight(self, data): +
    203 """ +204 Syntax highlight some python code. +205 Returns html version of code. +206 """ +207 +208 i = 0 +209 mode = self.mode +210 while i < len(data): +211 for (token, o_re, style) in Highlighter.all_styles[mode][1]: +212 if not token in self.suppress_tokens: +213 match = o_re.match(data, i) +214 if match: +215 if style: +216 new_mode = \ +217 Highlighter.all_styles[mode][0](self, +218 token, match, style +219 % dict(link=self.link)) +220 else: +221 new_mode = \ +222 Highlighter.all_styles[mode][0](self, +223 token, match, style) +224 if new_mode != None: +225 mode = new_mode +226 i += max(1, len(match.group())) +227 break +228 else: +229 self.change_style(None, None) +230 self.output.append(data[i]) +231 i += 1 +232 self.change_style(None, None) +233 return ''.join(self.output).expandtabs(4) +
    234 +
    235 - def change_style(self, token, style): +
    236 """ +237 Generate output to change from existing style to another style only. +238 """ +239 +240 if token in self.styles: +241 style = self.styles[token] +242 if self.span_style != style: +243 if style != 'Keep': +244 if self.span_style != None: +245 self.output.append('</span>') +246 if style != None: +247 self.output.append('<span style="%s">' % style) +248 self.span_style = style +
    249 +250 +
    251 -def highlight( +252 code, +253 language, +254 link='/examples/globals/vars/', +255 counter=1, +256 styles={}, +257 highlight_line=None, +258 attributes={}, +259 ): +
    260 if not 'CODE' in styles: +261 code_style = """ +262 font-size: 11px; +263 font-family: Bitstream Vera Sans Mono,monospace; +264 background-color: transparent; +265 margin: 0; +266 padding: 5px; +267 border: none; +268 overflow: auto; +269 white-space: pre !important;\n""" +270 else: +271 code_style = styles['CODE'] +272 if not 'LINENUMBERS' in styles: +273 linenumbers_style = """ +274 font-size: 11px; +275 font-family: Bitstream Vera Sans Mono,monospace; +276 background-color: transparent; +277 margin: 0; +278 padding: 5px; +279 border: none; +280 color: #A0A0A0;\n""" +281 else: +282 linenumbers_style = styles['LINENUMBERS'] +283 if not 'LINEHIGHLIGHT' in styles: +284 linehighlight_style = "background-color: #EBDDE2;" +285 else: +286 linehighlight_style = styles['LINEHIGHLIGHT'] +287 +288 if language and language.upper() in ['PYTHON', 'C', 'CPP', 'HTML', +289 'WEB2PY']: +290 code = Highlighter(language, link, styles).highlight(code) +291 else: +292 code = cgi.escape(code) +293 lines = code.split('\n') +294 +295 if counter is None: +296 linenumbers = [''] * len(lines) +297 elif isinstance(counter, str): +298 linenumbers = [cgi.escape(counter)] * len(lines) +299 else: +300 linenumbers = [str(i + counter) + '.' for i in +301 xrange(len(lines))] +302 +303 if highlight_line: +304 if counter and not isinstance(counter, str): +305 lineno = highlight_line - counter +306 else: +307 lineno = highlight_line +308 if lineno<len(lines): +309 lines[lineno] = '<div style="%s">%s</div>' % (linehighlight_style, lines[lineno]) +310 linenumbers[lineno] = '<div style="%s">%s</div>' % (linehighlight_style, linenumbers[lineno]) +311 +312 code = '<br/>'.join(lines) +313 numbers = '<br/>'.join(linenumbers) +314 +315 items = attributes.items() +316 fa = ' '.join([key[1:].lower() for (key, value) in items if key[:1] +317 == '_' and value == None] + ['%s="%s"' +318 % (key[1:].lower(), str(value).replace('"', "'")) +319 for (key, value) in attributes.items() if key[:1] +320 == '_' and value]) +321 if fa: +322 fa = ' ' + fa +323 return '<table%s><tr valign="top"><td style="width:40px; text-align: right;"><pre style="%s">%s</pre></td><td><pre style="%s">%s</pre></td></tr></table>'\ +324 % (fa, linenumbers_style, numbers, code_style, code) +
    325 +326 +327 if __name__ == '__main__': +328 import sys +329 argfp = open(sys.argv[1]) +330 data = argfp.read() +331 argfp.close() +332 print '<html><body>' + highlight(data, sys.argv[2])\ +333 + '</body></html>' +334 +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.highlight.Highlighter-class.html Index: applications/examples/static/epydoc/web2py.gluon.highlight.Highlighter-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.highlight.Highlighter-class.html +++ applications/examples/static/epydoc/web2py.gluon.highlight.Highlighter-class.html @@ -0,0 +1,449 @@ + + + + + web2py.gluon.highlight.Highlighter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module highlight :: + Class Highlighter + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class Highlighter

    source code

    +
    +object --+
    +         |
    +        Highlighter
    +
    + +
    +Do syntax highlighting.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + mode, + link=1, + styles={})
    + Initialise highlighter:...
    + source code + +
    + +
    +   + + + + + + +
    c_tokenizer(self, + token, + match, + style)
    + Callback for C specific highlighting.
    + source code + +
    + +
    +   + + + + + + +
    python_tokenizer(self, + token, + match, + style)
    + Callback for python specific highlighting.
    + source code + +
    + +
    +   + + + + + + +
    html_tokenizer(self, + token, + match, + style)
    + Callback for HTML specific highlighting.
    + source code + +
    + +
    +   + + + + + + +
    highlight(self, + data)
    + Syntax highlight some python code.
    + source code + +
    + +
    +   + + + + + + +
    change_style(self, + token, + style)
    + Generate output to change from existing style to another style + only.
    + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + all_styles = {'C': (<function c_tokenizer at 0xaf97d0>, (('COM... +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + mode, + link=1, + styles={}) +
    (Constructor) +

    +
    source code  +
    + +
    +
    +Initialise highlighter:
    +    mode = language (PYTHON, WEB2PY,C, CPP, HTML, HTML_PLAIN)
    +
    +
    +
    +
    Overrides: + object.__init__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    highlight(self, + data) +

    +
    source code  +
    + + Syntax highlight some python code. Returns html version of code. +
    +
    +
    +
    +
    + + + + + + +
    + + + + + +
    Class Variable Details[hide private]
    +
    + +
    + +
    +

    all_styles

    + +
    +
    +
    +
    Value:
    +
    +{'C': (<function c_tokenizer at 0xaf97d0>,
    +       (('COMMENT',
    +         re.compile(r'//.*\r?\n'),
    +         'color: green; font-style: italic'),
    +        ('MULTILINECOMMENT',
    +         re.compile(r'(?s)/\*.*?\*/'),
    +         'color: green; font-style: italic'),
    +        ('PREPROCESSOR', re.compile(r'(?s)\s*#.*?[^\\]\s*\n'), 'color:\
    +...
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html-module.html Index: applications/examples/static/epydoc/web2py.gluon.html-module.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html-module.html +++ applications/examples/static/epydoc/web2py.gluon.html-module.html @@ -0,0 +1,1200 @@ + + + + + web2py.gluon.html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Module html

    source code

    +This file is part of the web2py Web Framework Copyrighted by Massimo + Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Classes[hide private]
    +
    +   + + XmlComponent
    + Abstract root for all Html components +
    +   + + XML
    + use it to wrap a string that contains XML/HTML so that it will + not be escaped by the template +
    +   + + DIV
    + HTML helper, for easy generating and manipulating a DOM + structure. +
    +   + + CAT +
    +   + + __TAG__
    + TAG factory example: +
    +   + + HTML
    + There are four predefined document type definitions. +
    +   + + XHTML
    + This is XHTML version of the HTML helper. +
    +   + + HEAD +
    +   + + TITLE +
    +   + + META +
    +   + + LINK +
    +   + + SCRIPT +
    +   + + STYLE +
    +   + + IMG +
    +   + + SPAN +
    +   + + BODY +
    +   + + H1 +
    +   + + H2 +
    +   + + H3 +
    +   + + H4 +
    +   + + H5 +
    +   + + H6 +
    +   + + P
    + Will replace ``\n`` by ``<br />`` if the `cr2br` attribute + is provided. +
    +   + + B +
    +   + + BR +
    +   + + HR +
    +   + + A +
    +   + + BUTTON +
    +   + + EM +
    +   + + EMBED +
    +   + + TT +
    +   + + PRE +
    +   + + CENTER +
    +   + + CODE
    + displays code in HTML with syntax highlighting. +
    +   + + LABEL +
    +   + + LI +
    +   + + UL
    + UL Component. +
    +   + + OL +
    +   + + TD +
    +   + + TH +
    +   + + TR
    + TR Component. +
    +   + + THEAD +
    +   + + TBODY +
    +   + + TFOOT +
    +   + + COL +
    +   + + COLGROUP +
    +   + + TABLE
    + TABLE Component. +
    +   + + I +
    +   + + IFRAME +
    +   + + INPUT
    + INPUT Component + +examples:: + + >>> INPUT(_type='text', _name='name', value='Max').xml() + '<input name="name" type="text" value="Max" />' + + >>> INPUT(_type='checkbox', _name='checkbox', value='on').xml() + '<input checked="checked" name="checkbox" type="checkbox" value="on" />' + + >>> INPUT(_type='radio', _name='radio', _value='yes', value='yes').xml() + '<input checked="checked" name="radio" type="radio" value="yes" />' + + >>> INPUT(_type='radio', _name='radio', _value='no', value='yes').xml() + '<input name="radio" type="radio" value="no" />' + +the input helper takes two special attributes value= and requires=. +
    +   + + TEXTAREA
    + example: +
    +   + + OPTION +
    +   + + OBJECT +
    +   + + OPTGROUP +
    +   + + SELECT
    + example: +
    +   + + FIELDSET +
    +   + + LEGEND +
    +   + + FORM
    + example: +
    +   + + BEAUTIFY
    + example:: + + >>> BEAUTIFY(['a', 'b', {'hello': 'world'}]).xml() + '<div><table><tr><td><div>a</div></td></tr><tr><td><div>b</div></td></tr><tr><td><div><table><tr><td style="font-weight:bold;">hello</td><td valign="top">:</td><td><div>world</div></td></tr></table></div></td></tr></table></div>' + +turns any list, dictionary, etc into decent looking html. +
    +   + + MENU
    + Used to build menus... +
    +   + + web2pyHTMLParser
    + obj = web2pyHTMLParser(text) parses and html/xml text into + web2py helpers. +
    +   + + MARKMIN
    + For documentation: + http://web2py.com/examples/static/markmin.html +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Functions[hide private]
    +
    +   + + + + + + +
    join(S, + sequence)
    + Return a string which is the concatenation of the strings in the + sequence.
    + source code + +
    + +
    +   + + + + + + +
    xmlescape(data, + quote=True)
    + returns an escaped string of the provided data
    + source code + +
    + +
    +   + + + + + + +
    URL(a=1, + c=1, + f=1, + r=1, + args=[], + vars={}, + anchor='', + extension=1, + env=1, + hmac_key=1, + hash_vars=True, + salt=1, + user_signature=1, + scheme=1, + host=1, + port=1)
    + generate a URL + +example:: + + >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'], + ...
    + source code + +
    + +
    +   + + + + + + +
    verifyURL(request, + hmac_key=1, + hash_vars=True, + salt=1, + user_signature=1)
    + Verifies that a request's args & vars have not been tampered with by the user + +:param request: web2py's request object +:param hmac_key: the key to authenticate with, must be the same one previously + used when calling URL() +:param hash_vars: which vars to include in our hashing.
    + source code + +
    + +
    +   + + + + + + +
    XML_unpickle(data) + source code + +
    + +
    +   + + + + + + +
    XML_pickle(data) + source code + +
    + +
    +   + + + + + + +
    TAG_unpickler(data) + source code + +
    + +
    +   + + + + + + +
    TAG_pickler(data) + source code + +
    + +
    +   + + + + + + +
    embed64(filename=1, + file=1, + data=1, + extension='image/gif')
    + helper to encode the provided (binary) data into base64.
    + source code + +
    + +
    +   + + + + + + +
    test()
    + Example:
    + source code + +
    + +
    +   + + + + + + +
    markdown_serializer(text, + tag=1, + attr={}) + source code + +
    + +
    +   + + + + + + +
    markmin_serializer(text, + tag=1, + attr={}) + source code + +
    + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    Variables[hide private]
    +
    +   + + regex_crlf = re.compile(r'[\r\n]') +
    +   + + ON = True +
    +   + + TAG = __TAG__() +
    + + + + + + +
    + + + + + +
    Function Details[hide private]
    +
    + +
    + +
    + + +
    +

    join(S, + sequence) +

    +
    source code  +
    + + Return a string which is the concatenation of the strings in the + sequence. The separator between elements is S. +
    +
    Returns:
    +
    +string
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    xmlescape(data, + quote=True) +

    +
    source code  +
    + +

    returns an escaped string of the provided data

    + :param data: the data to be escaped :param quote: optional (default + False) +
    +
    +
    +
    + +
    + +
    + + +
    +

    URL(a=1, + c=1, + f=1, + r=1, + args=[], + vars={}, + anchor='', + extension=1, + env=1, + hmac_key=1, + hash_vars=True, + salt=1, + user_signature=1, + scheme=1, + host=1, + port=1) +

    +
    source code  +
    + +
    +
    +generate a URL
    +
    +example::
    +
    +    >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'],
    +    ...     vars={'p':1, 'q':2}, anchor='1'))
    +    '/a/c/f/x/y/z?p=1&q=2#1'
    +
    +    >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'],
    +    ...     vars={'p':(1,3), 'q':2}, anchor='1'))
    +    '/a/c/f/x/y/z?p=1&p=3&q=2#1'
    +
    +    >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'],
    +    ...     vars={'p':(3,1), 'q':2}, anchor='1'))
    +    '/a/c/f/x/y/z?p=3&p=1&q=2#1'
    +
    +    >>> str(URL(a='a', c='c', f='f', anchor='1+2'))
    +    '/a/c/f#1%2B2'
    +
    +    >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'],
    +    ...     vars={'p':(1,3), 'q':2}, anchor='1', hmac_key='key'))
    +    '/a/c/f/x/y/z?p=1&p=3&q=2&_signature=5d06bb8a4a6093dd325da2ee591c35c61afbd3c6#1'
    +
    +generates a url '/a/c/f' corresponding to application a, controller c
    +and function f. If r=request is passed, a, c, f are set, respectively,
    +to r.application, r.controller, r.function.
    +
    +The more typical usage is:
    +
    +URL(r=request, f='index') that generates a url for the index function
    +within the present application and controller.
    +
    +:param a: application (default to current if r is given)
    +:param c: controller (default to current if r is given)
    +:param f: function (default to current if r is given)
    +:param r: request (optional)
    +:param args: any arguments (optional)
    +:param vars: any variables (optional)
    +:param anchor: anchorname, without # (optional)
    +:param hmac_key: key to use when generating hmac signature (optional)
    +:param hash_vars: which of the vars to include in our hmac signature
    +    True (default) - hash all vars, False - hash none of the vars,
    +    iterable - hash only the included vars ['key1','key2']
    +:param scheme: URI scheme (True, 'http' or 'https', etc); forces absolute URL (optional)
    +:param host: string to force absolute URL with host (True means http_host)
    +:param port: optional port number (forces absolute URL)
    +
    +:raises SyntaxError: when no application, controller or function is
    +    available
    +:raises SyntaxError: when a CRLF is found in the generated url
    +
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    verifyURL(request, + hmac_key=1, + hash_vars=True, + salt=1, + user_signature=1) +

    +
    source code  +
    + +
    +
    +Verifies that a request's args & vars have not been tampered with by the user
    +
    +:param request: web2py's request object
    +:param hmac_key: the key to authenticate with, must be the same one previously
    +                used when calling URL()
    +:param hash_vars: which vars to include in our hashing. (Optional)
    +                Only uses the 1st value currently
    +                True (or undefined) means all, False none,
    +                an iterable just the specified keys
    +
    +do not call directly. Use instead:
    +
    +URL.verify(hmac_key='...')
    +
    +the key has to match the one used to generate the URL.
    +
    +    >>> r = Storage()
    +    >>> gv = Storage(p=(1,3),q=2,_signature='5d06bb8a4a6093dd325da2ee591c35c61afbd3c6')
    +    >>> r.update(dict(application='a', controller='c', function='f'))
    +    >>> r['args'] = ['x', 'y', 'z']
    +    >>> r['get_vars'] = gv
    +    >>> verifyURL(r, 'key')
    +    True
    +    >>> verifyURL(r, 'kay')
    +    False
    +    >>> r.get_vars.p = (3, 1)
    +    >>> verifyURL(r, 'key')
    +    True
    +    >>> r.get_vars.p = (3, 2)
    +    >>> verifyURL(r, 'key')
    +    False
    +
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    embed64(filename=1, + file=1, + data=1, + extension='image/gif') +

    +
    source code  +
    + +

    helper to encode the provided (binary) data into base64.

    + :param filename: if provided, opens and reads this file in 'rb' mode + :param file: if provided, reads this file :param data: if provided, uses + the provided data +
    +
    +
    +
    + +
    + +
    + + +
    +

    test() +

    +
    source code  +
    + + Example: +
    +>>> from validators import *
    +>>> print DIV(A('click me', _href=URL(a='a', c='b', f='c')), BR(), HR(), DIV(SPAN("World"), _class='unknown')).xml()
    +<div><a href="/a/b/c">click me</a><br /><hr /><div class="unknown"><span>World</span></div></div>
    +>>> print DIV(UL("doc","cat","mouse")).xml()
    +<div><ul><li>doc</li><li>cat</li><li>mouse</li></ul></div>
    +>>> print DIV(UL("doc", LI("cat", _class='feline'), 18)).xml()
    +<div><ul><li>doc</li><li class="feline">cat</li><li>18</li></ul></div>
    +>>> print TABLE(['a', 'b', 'c'], TR('d', 'e', 'f'), TR(TD(1), TD(2), TD(3))).xml()
    +<table><tr><td>a</td><td>b</td><td>c</td></tr><tr><td>d</td><td>e</td><td>f</td></tr><tr><td>1</td><td>2</td><td>3</td></tr></table>
    +>>> form=FORM(INPUT(_type='text', _name='myvar', requires=IS_EXPR('int(value)<10')))
    +>>> print form.xml()
    +<form action="" enctype="multipart/form-data" method="post"><input name="myvar" type="text" /></form>
    +>>> print form.accepts({'myvar':'34'}, formname=None)
    +False
    +>>> print form.xml()
    +<form action="" enctype="multipart/form-data" method="post"><input name="myvar" type="text" value="34" /><div class="error" id="myvar__error">invalid expression</div></form>
    +>>> print form.accepts({'myvar':'4'}, formname=None, keepvalues=True)
    +True
    +>>> print form.xml()
    +<form action="" enctype="multipart/form-data" method="post"><input name="myvar" type="text" value="4" /></form>
    +>>> form=FORM(SELECT('cat', 'dog', _name='myvar'))
    +>>> print form.accepts({'myvar':'dog'}, formname=None, keepvalues=True)
    +True
    +>>> print form.xml()
    +<form action="" enctype="multipart/form-data" method="post"><select name="myvar"><option value="cat">cat</option><option selected="selected" value="dog">dog</option></select></form>
    +>>> form=FORM(INPUT(_type='text', _name='myvar', requires=IS_MATCH('^\w+$', 'only alphanumeric!')))
    +>>> print form.accepts({'myvar':'as df'}, formname=None)
    +False
    +>>> print form.xml()
    +<form action="" enctype="multipart/form-data" method="post"><input name="myvar" type="text" value="as df" /><div class="error" id="myvar__error">only alphanumeric!</div></form>
    +>>> session={}
    +>>> form=FORM(INPUT(value="Hello World", _name="var", requires=IS_MATCH('^\w+$')))
    +>>> if form.accepts({}, session,formname=None): print 'passed'
    +>>> if form.accepts({'var':'test ', '_formkey': session['_formkey[None]']}, session, formname=None): print 'passed'
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html-pysrc.html Index: applications/examples/static/epydoc/web2py.gluon.html-pysrc.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html-pysrc.html +++ applications/examples/static/epydoc/web2py.gluon.html-pysrc.html @@ -0,0 +1,9736 @@ + + + + + web2py.gluon.html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Module web2py.gluon.html

    +
    +   1  #!/usr/bin/env python 
    +   2  # -*- coding: utf-8 -*- 
    +   3   
    +   4  """ 
    +   5  This file is part of the web2py Web Framework 
    +   6  Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> 
    +   7  License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) 
    +   8  """ 
    +   9   
    +  10  import cgi 
    +  11  import os 
    +  12  import re 
    +  13  import copy 
    +  14  import types 
    +  15  import urllib 
    +  16  import base64 
    +  17  import sanitizer 
    +  18  import rewrite 
    +  19  import itertools 
    +  20  import decoder 
    +  21  import copy_reg 
    +  22  import cPickle 
    +  23  import marshal 
    +  24  from HTMLParser import HTMLParser 
    +  25  from htmlentitydefs import name2codepoint 
    +  26  from contrib.markmin.markmin2html import render 
    +  27   
    +  28  from storage import Storage 
    +  29  from highlight import highlight 
    +  30  from utils import web2py_uuid, hmac_hash 
    +  31   
    +  32  import hmac 
    +  33  import hashlib 
    +  34   
    +  35  regex_crlf = re.compile('\r|\n') 
    +  36   
    +  37  join = ''.join 
    +  38   
    +  39  __all__ = [ 
    +  40      'A', 
    +  41      'B', 
    +  42      'BEAUTIFY', 
    +  43      'BODY', 
    +  44      'BR', 
    +  45      'BUTTON', 
    +  46      'CENTER', 
    +  47      'CAT', 
    +  48      'CODE', 
    +  49      'DIV', 
    +  50      'EM', 
    +  51      'EMBED', 
    +  52      'FIELDSET', 
    +  53      'FORM', 
    +  54      'H1', 
    +  55      'H2', 
    +  56      'H3', 
    +  57      'H4', 
    +  58      'H5', 
    +  59      'H6', 
    +  60      'HEAD', 
    +  61      'HR', 
    +  62      'HTML', 
    +  63      'I', 
    +  64      'IFRAME', 
    +  65      'IMG', 
    +  66      'INPUT', 
    +  67      'LABEL', 
    +  68      'LEGEND', 
    +  69      'LI', 
    +  70      'LINK', 
    +  71      'OL', 
    +  72      'UL', 
    +  73      'MARKMIN', 
    +  74      'MENU', 
    +  75      'META', 
    +  76      'OBJECT', 
    +  77      'ON', 
    +  78      'OPTION', 
    +  79      'P', 
    +  80      'PRE', 
    +  81      'SCRIPT', 
    +  82      'OPTGROUP', 
    +  83      'SELECT', 
    +  84      'SPAN', 
    +  85      'STYLE', 
    +  86      'TABLE', 
    +  87      'TAG', 
    +  88      'TD', 
    +  89      'TEXTAREA', 
    +  90      'TH', 
    +  91      'THEAD', 
    +  92      'TBODY', 
    +  93      'TFOOT', 
    +  94      'TITLE', 
    +  95      'TR', 
    +  96      'TT', 
    +  97      'URL', 
    +  98      'XHTML', 
    +  99      'XML', 
    + 100      'xmlescape', 
    + 101      'embed64', 
    + 102      ] 
    + 103   
    + 104   
    +
    105 -def xmlescape(data, quote = True): +
    106 """ + 107 returns an escaped string of the provided data + 108 + 109 :param data: the data to be escaped + 110 :param quote: optional (default False) + 111 """ + 112 + 113 # first try the xml function + 114 if hasattr(data,'xml') and callable(data.xml): + 115 return data.xml() + 116 + 117 # otherwise, make it a string + 118 if not isinstance(data, (str, unicode)): + 119 data = str(data) + 120 elif isinstance(data, unicode): + 121 data = data.encode('utf8', 'xmlcharrefreplace') + 122 + 123 # ... and do the escaping + 124 data = cgi.escape(data, quote).replace("'","&#x27;") + 125 return data +
    126 + 127 +
    128 -def URL( + 129 a=None, + 130 c=None, + 131 f=None, + 132 r=None, + 133 args=[], + 134 vars={}, + 135 anchor='', + 136 extension=None, + 137 env=None, + 138 hmac_key=None, + 139 hash_vars=True, + 140 salt=None, + 141 user_signature=None, + 142 scheme=None, + 143 host=None, + 144 port=None, + 145 ): +
    146 """ + 147 generate a URL + 148 + 149 example:: + 150 + 151 >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'], + 152 ... vars={'p':1, 'q':2}, anchor='1')) + 153 '/a/c/f/x/y/z?p=1&q=2#1' + 154 + 155 >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'], + 156 ... vars={'p':(1,3), 'q':2}, anchor='1')) + 157 '/a/c/f/x/y/z?p=1&p=3&q=2#1' + 158 + 159 >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'], + 160 ... vars={'p':(3,1), 'q':2}, anchor='1')) + 161 '/a/c/f/x/y/z?p=3&p=1&q=2#1' + 162 + 163 >>> str(URL(a='a', c='c', f='f', anchor='1+2')) + 164 '/a/c/f#1%2B2' + 165 + 166 >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'], + 167 ... vars={'p':(1,3), 'q':2}, anchor='1', hmac_key='key')) + 168 '/a/c/f/x/y/z?p=1&p=3&q=2&_signature=5d06bb8a4a6093dd325da2ee591c35c61afbd3c6#1' + 169 + 170 generates a url '/a/c/f' corresponding to application a, controller c + 171 and function f. If r=request is passed, a, c, f are set, respectively, + 172 to r.application, r.controller, r.function. + 173 + 174 The more typical usage is: + 175 + 176 URL(r=request, f='index') that generates a url for the index function + 177 within the present application and controller. + 178 + 179 :param a: application (default to current if r is given) + 180 :param c: controller (default to current if r is given) + 181 :param f: function (default to current if r is given) + 182 :param r: request (optional) + 183 :param args: any arguments (optional) + 184 :param vars: any variables (optional) + 185 :param anchor: anchorname, without # (optional) + 186 :param hmac_key: key to use when generating hmac signature (optional) + 187 :param hash_vars: which of the vars to include in our hmac signature + 188 True (default) - hash all vars, False - hash none of the vars, + 189 iterable - hash only the included vars ['key1','key2'] + 190 :param scheme: URI scheme (True, 'http' or 'https', etc); forces absolute URL (optional) + 191 :param host: string to force absolute URL with host (True means http_host) + 192 :param port: optional port number (forces absolute URL) + 193 + 194 :raises SyntaxError: when no application, controller or function is + 195 available + 196 :raises SyntaxError: when a CRLF is found in the generated url + 197 """ + 198 + 199 if args in (None,[]): args = [] + 200 vars = vars or {} + 201 application = None + 202 controller = None + 203 function = None + 204 + 205 if not r: + 206 if a and not c and not f: (f,a,c)=(a,c,f) + 207 elif a and c and not f: (c,f,a)=(a,c,f) + 208 from globals import current + 209 if hasattr(current,'request'): + 210 r = current.request + 211 if r: + 212 application = r.application + 213 controller = r.controller + 214 function = r.function + 215 env = r.env + 216 if extension is None and r.extension != 'html': + 217 extension = r.extension + 218 if a: + 219 application = a + 220 if c: + 221 controller = c + 222 if f: + 223 if not isinstance(f, str): + 224 function = f.__name__ + 225 elif '.' in f: + 226 function, extension = f.split('.', 1) + 227 else: + 228 function = f + 229 + 230 function2 = '%s.%s' % (function,extension or 'html') + 231 + 232 if not (application and controller and function): + 233 raise SyntaxError, 'not enough information to build the url' + 234 + 235 if not isinstance(args, (list, tuple)): + 236 args = [args] + 237 other = args and urllib.quote('/' + '/'.join([str(x) for x in args])) or '' + 238 if other.endswith('/'): + 239 other += '/' # add trailing slash to make last trailing empty arg explicit + 240 + 241 if vars.has_key('_signature'): vars.pop('_signature') + 242 list_vars = [] + 243 for (key, vals) in sorted(vars.items()): + 244 if not isinstance(vals, (list, tuple)): + 245 vals = [vals] + 246 for val in vals: + 247 list_vars.append((key, val)) + 248 + 249 if user_signature: + 250 from globals import current + 251 if current.session.auth: + 252 hmac_key = current.session.auth.hmac_key + 253 + 254 if hmac_key: + 255 # generate an hmac signature of the vars & args so can later + 256 # verify the user hasn't messed with anything + 257 + 258 h_args = '/%s/%s/%s%s' % (application, controller, function2, other) + 259 + 260 # how many of the vars should we include in our hash? + 261 if hash_vars is True: # include them all + 262 h_vars = list_vars + 263 elif hash_vars is False: # include none of them + 264 h_vars = '' + 265 else: # include just those specified + 266 if hash_vars and not isinstance(hash_vars, (list, tuple)): + 267 hash_vars = [hash_vars] + 268 h_vars = [(k, v) for (k, v) in list_vars if k in hash_vars] + 269 + 270 # re-assembling the same way during hash authentication + 271 message = h_args + '?' + urllib.urlencode(sorted(h_vars)) + 272 + 273 sig = hmac_hash(message,hmac_key,salt=salt) + 274 # add the signature into vars + 275 list_vars.append(('_signature', sig)) + 276 + 277 if list_vars: + 278 other += '?%s' % urllib.urlencode(list_vars) + 279 if anchor: + 280 other += '#' + urllib.quote(str(anchor)) + 281 if extension: + 282 function += '.' + extension + 283 + 284 if regex_crlf.search(join([application, controller, function, other])): + 285 raise SyntaxError, 'CRLF Injection Detected' + 286 url = rewrite.url_out(r, env, application, controller, function, + 287 args, other, scheme, host, port) + 288 return url +
    289 + 290 +
    291 -def verifyURL(request, hmac_key=None, hash_vars=True, salt=None, user_signature=None): +
    292 """ + 293 Verifies that a request's args & vars have not been tampered with by the user + 294 + 295 :param request: web2py's request object + 296 :param hmac_key: the key to authenticate with, must be the same one previously + 297 used when calling URL() + 298 :param hash_vars: which vars to include in our hashing. (Optional) + 299 Only uses the 1st value currently + 300 True (or undefined) means all, False none, + 301 an iterable just the specified keys + 302 + 303 do not call directly. Use instead: + 304 + 305 URL.verify(hmac_key='...') + 306 + 307 the key has to match the one used to generate the URL. + 308 + 309 >>> r = Storage() + 310 >>> gv = Storage(p=(1,3),q=2,_signature='5d06bb8a4a6093dd325da2ee591c35c61afbd3c6') + 311 >>> r.update(dict(application='a', controller='c', function='f')) + 312 >>> r['args'] = ['x', 'y', 'z'] + 313 >>> r['get_vars'] = gv + 314 >>> verifyURL(r, 'key') + 315 True + 316 >>> verifyURL(r, 'kay') + 317 False + 318 >>> r.get_vars.p = (3, 1) + 319 >>> verifyURL(r, 'key') + 320 True + 321 >>> r.get_vars.p = (3, 2) + 322 >>> verifyURL(r, 'key') + 323 False + 324 + 325 """ + 326 + 327 if not request.get_vars.has_key('_signature'): + 328 return False # no signature in the request URL + 329 + 330 # check if user_signature requires + 331 if user_signature: + 332 from globals import current + 333 if not current.session: + 334 return False + 335 hmac_key = current.session.auth.hmac_key + 336 if not hmac_key: + 337 return False + 338 + 339 # get our sig from request.get_vars for later comparison + 340 original_sig = request.get_vars._signature + 341 + 342 # now generate a new hmac for the remaining args & vars + 343 vars, args = request.get_vars, request.args + 344 + 345 # remove the signature var since it was not part of our signed message + 346 request.get_vars.pop('_signature') + 347 + 348 # join all the args & vars into one long string + 349 + 350 # always include all of the args + 351 other = args and urllib.quote('/' + '/'.join([str(x) for x in args])) or '' + 352 h_args = '/%s/%s/%s.%s%s' % (request.application, + 353 request.controller, + 354 request.function, + 355 request.extension, + 356 other) + 357 + 358 # but only include those vars specified (allows more flexibility for use with + 359 # forms or ajax) + 360 + 361 list_vars = [] + 362 for (key, vals) in sorted(vars.items()): + 363 if not isinstance(vals, (list, tuple)): + 364 vals = [vals] + 365 for val in vals: + 366 list_vars.append((key, val)) + 367 + 368 # which of the vars are to be included? + 369 if hash_vars is True: # include them all + 370 h_vars = list_vars + 371 elif hash_vars is False: # include none of them + 372 h_vars = '' + 373 else: # include just those specified + 374 # wrap in a try - if the desired vars have been removed it'll fail + 375 try: + 376 if hash_vars and not isinstance(hash_vars, (list, tuple)): + 377 hash_vars = [hash_vars] + 378 h_vars = [(k, v) for (k, v) in list_vars if k in hash_vars] + 379 except: + 380 # user has removed one of our vars! Immediate fail + 381 return False + 382 # build the full message string with both args & vars + 383 message = h_args + '?' + urllib.urlencode(sorted(h_vars)) + 384 + 385 # hash with the hmac_key provided + 386 sig = hmac_hash(message,str(hmac_key),salt=salt) + 387 + 388 # put _signature back in get_vars just in case a second call to URL.verify is performed + 389 # (otherwise it'll immediately return false) + 390 request.get_vars['_signature'] = original_sig + 391 + 392 # return whether or not the signature in the request matched the one we just generated + 393 # (I.E. was the message the same as the one we originally signed) + 394 return original_sig == sig +
    395 + 396 URL.verify = verifyURL + 397 + 398 ON = True + 399 + 400 +
    401 -class XmlComponent(object): +
    402 """ + 403 Abstract root for all Html components + 404 """ + 405 + 406 # TODO: move some DIV methods to here + 407 +
    408 - def xml(self): +
    409 raise NotImplementedError +
    410 + 411 +
    412 -class XML(XmlComponent): +
    413 """ + 414 use it to wrap a string that contains XML/HTML so that it will not be + 415 escaped by the template + 416 + 417 example: + 418 + 419 >>> XML('<h1>Hello</h1>').xml() + 420 '<h1>Hello</h1>' + 421 """ + 422 +
    423 - def __init__( + 424 self, + 425 text, + 426 sanitize = False, + 427 permitted_tags = [ + 428 'a', + 429 'b', + 430 'blockquote', + 431 'br/', + 432 'i', + 433 'li', + 434 'ol', + 435 'ul', + 436 'p', + 437 'cite', + 438 'code', + 439 'pre', + 440 'img/', + 441 'h1','h2','h3','h4','h5','h6', + 442 'table','tr','td','div', + 443 ], + 444 allowed_attributes = { + 445 'a': ['href', 'title'], + 446 'img': ['src', 'alt'], + 447 'blockquote': ['type'], + 448 'td': ['colspan'], + 449 }, + 450 ): +
    451 """ + 452 :param text: the XML text + 453 :param sanitize: sanitize text using the permitted tags and allowed + 454 attributes (default False) + 455 :param permitted_tags: list of permitted tags (default: simple list of + 456 tags) + 457 :param allowed_attributes: dictionary of allowed attributed (default + 458 for A, IMG and BlockQuote). + 459 The key is the tag; the value is a list of allowed attributes. + 460 """ + 461 + 462 if sanitize: + 463 text = sanitizer.sanitize(text, permitted_tags, + 464 allowed_attributes) + 465 if isinstance(text, unicode): + 466 text = text.encode('utf8', 'xmlcharrefreplace') + 467 elif not isinstance(text, str): + 468 text = str(text) + 469 self.text = text +
    470 +
    471 - def xml(self): +
    472 return self.text +
    473 +
    474 - def __str__(self): +
    475 return self.xml() +
    476 +
    477 - def __add__(self,other): +
    478 return '%s%s' % (self,other) +
    479 +
    480 - def __radd__(self,other): +
    481 return '%s%s' % (other,self) +
    482 +
    483 - def __cmp__(self,other): +
    484 return cmp(str(self),str(other)) +
    485 +
    486 - def __hash__(self): +
    487 return hash(str(self)) +
    488 +
    489 - def __getattr__(self,name): +
    490 return getattr(str(self),name) +
    491 +
    492 - def __getitem__(self,i): +
    493 return str(self)[i] +
    494 +
    495 - def __getslice__(self,i,j): +
    496 return str(self)[i:j] +
    497 +
    498 - def __iter__(self): +
    499 for c in str(self): yield c +
    500 +
    501 - def __len__(self): +
    502 return len(str(self)) +
    503 +
    504 - def flatten(self,render=None): +
    505 """ + 506 return the text stored by the XML object rendered by the render function + 507 """ + 508 if render: + 509 return render(self.text,None,{}) + 510 return self.text +
    511 +
    512 - def elements(self, *args, **kargs): +
    513 """ + 514 to be considered experimental since the behavior of this method is questionable + 515 another options could be TAG(self.text).elements(*args,**kargs) + 516 """ + 517 return [] +
    518 + 519 ### important to allow safe session.flash=T(....) +
    520 -def XML_unpickle(data): +
    521 return marshal.loads(data) +
    522 -def XML_pickle(data): +
    523 return XML_unpickle, (marshal.dumps(str(data)),) +
    524 copy_reg.pickle(XML, XML_pickle, XML_unpickle) + 525 + 526 + 527 +
    528 -class DIV(XmlComponent): +
    529 """ + 530 HTML helper, for easy generating and manipulating a DOM structure. + 531 Little or no validation is done. + 532 + 533 Behaves like a dictionary regarding updating of attributes. + 534 Behaves like a list regarding inserting/appending components. + 535 + 536 example:: + 537 + 538 >>> DIV('hello', 'world', _style='color:red;').xml() + 539 '<div style=\"color:red;\">helloworld</div>' + 540 + 541 all other HTML helpers are derived from DIV. + 542 + 543 _something=\"value\" attributes are transparently translated into + 544 something=\"value\" HTML attributes + 545 """ + 546 + 547 # name of the tag, subclasses should update this + 548 # tags ending with a '/' denote classes that cannot + 549 # contain components + 550 tag = 'div' + 551 +
    552 - def __init__(self, *components, **attributes): +
    553 """ + 554 :param *components: any components that should be nested in this element + 555 :param **attributes: any attributes you want to give to this element + 556 + 557 :raises SyntaxError: when a stand alone tag receives components + 558 """ + 559 + 560 if self.tag[-1:] == '/' and components: + 561 raise SyntaxError, '<%s> tags cannot have components'\ + 562 % self.tag + 563 if len(components) == 1 and isinstance(components[0], (list,tuple)): + 564 self.components = list(components[0]) + 565 else: + 566 self.components = list(components) + 567 self.attributes = attributes + 568 self._fixup() + 569 # converts special attributes in components attributes + 570 self._postprocessing() + 571 self.parent = None + 572 for c in self.components: + 573 self._setnode(c) +
    574 +
    575 - def update(self, **kargs): +
    576 """ + 577 dictionary like updating of the tag attributes + 578 """ + 579 + 580 for (key, value) in kargs.items(): + 581 self[key] = value + 582 return self +
    583 +
    584 - def append(self, value): +
    585 """ + 586 list style appending of components + 587 + 588 >>> a=DIV() + 589 >>> a.append(SPAN('x')) + 590 >>> print a + 591 <div><span>x</span></div> + 592 """ + 593 self._setnode(value) + 594 ret = self.components.append(value) + 595 self._fixup() + 596 return ret +
    597 +
    598 - def insert(self, i, value): +
    599 """ + 600 list style inserting of components + 601 + 602 >>> a=DIV() + 603 >>> a.insert(0,SPAN('x')) + 604 >>> print a + 605 <div><span>x</span></div> + 606 """ + 607 self._setnode(value) + 608 ret = self.components.insert(i, value) + 609 self._fixup() + 610 return ret +
    611 +
    612 - def __getitem__(self, i): +
    613 """ + 614 gets attribute with name 'i' or component #i. + 615 If attribute 'i' is not found returns None + 616 + 617 :param i: index + 618 if i is a string: the name of the attribute + 619 otherwise references to number of the component + 620 """ + 621 + 622 if isinstance(i, str): + 623 try: + 624 return self.attributes[i] + 625 except KeyError: + 626 return None + 627 else: + 628 return self.components[i] +
    629 +
    630 - def __setitem__(self, i, value): +
    631 """ + 632 sets attribute with name 'i' or component #i. + 633 + 634 :param i: index + 635 if i is a string: the name of the attribute + 636 otherwise references to number of the component + 637 :param value: the new value + 638 """ + 639 self._setnode(value) + 640 if isinstance(i, (str, unicode)): + 641 self.attributes[i] = value + 642 else: + 643 self.components[i] = value +
    644 +
    645 - def __delitem__(self, i): +
    646 """ + 647 deletes attribute with name 'i' or component #i. + 648 + 649 :param i: index + 650 if i is a string: the name of the attribute + 651 otherwise references to number of the component + 652 """ + 653 + 654 if isinstance(i, str): + 655 del self.attributes[i] + 656 else: + 657 del self.components[i] +
    658 +
    659 - def __len__(self): +
    660 """ + 661 returns the number of included components + 662 """ + 663 return len(self.components) +
    664 +
    665 - def __nonzero__(self): +
    666 """ + 667 always return True + 668 """ + 669 return True +
    670 +
    671 - def _fixup(self): +
    672 """ + 673 Handling of provided components. + 674 + 675 Nothing to fixup yet. May be overridden by subclasses, + 676 eg for wrapping some components in another component or blocking them. + 677 """ + 678 return +
    679 +
    680 - def _wrap_components(self, allowed_parents, + 681 wrap_parent = None, + 682 wrap_lambda = None): +
    683 """ + 684 helper for _fixup. Checks if a component is in allowed_parents, + 685 otherwise wraps it in wrap_parent + 686 + 687 :param allowed_parents: (tuple) classes that the component should be an + 688 instance of + 689 :param wrap_parent: the class to wrap the component in, if needed + 690 :param wrap_lambda: lambda to use for wrapping, if needed + 691 + 692 """ + 693 components = [] + 694 for c in self.components: + 695 if isinstance(c, allowed_parents): + 696 pass + 697 elif wrap_lambda: + 698 c = wrap_lambda(c) + 699 else: + 700 c = wrap_parent(c) + 701 if isinstance(c,DIV): + 702 c.parent = self + 703 components.append(c) + 704 self.components = components +
    705 +
    706 - def _postprocessing(self): +
    707 """ + 708 Handling of attributes (normally the ones not prefixed with '_'). + 709 + 710 Nothing to postprocess yet. May be overridden by subclasses + 711 """ + 712 return +
    713 +
    714 - def _traverse(self, status, hideerror=False): +
    715 # TODO: docstring + 716 newstatus = status + 717 for c in self.components: + 718 if hasattr(c, '_traverse') and callable(c._traverse): + 719 c.vars = self.vars + 720 c.request_vars = self.request_vars + 721 c.errors = self.errors + 722 c.latest = self.latest + 723 c.session = self.session + 724 c.formname = self.formname + 725 c['hideerror']=hideerror + 726 newstatus = c._traverse(status,hideerror) and newstatus + 727 + 728 # for input, textarea, select, option + 729 # deal with 'value' and 'validation' + 730 + 731 name = self['_name'] + 732 if newstatus: + 733 newstatus = self._validate() + 734 self._postprocessing() + 735 elif 'old_value' in self.attributes: + 736 self['value'] = self['old_value'] + 737 self._postprocessing() + 738 elif name and name in self.vars: + 739 self['value'] = self.vars[name] + 740 self._postprocessing() + 741 if name: + 742 self.latest[name] = self['value'] + 743 return newstatus +
    744 +
    745 - def _validate(self): +
    746 """ + 747 nothing to validate yet. May be overridden by subclasses + 748 """ + 749 return True +
    750 +
    751 - def _setnode(self,value): +
    752 if isinstance(value,DIV): + 753 value.parent = self +
    754 +
    755 - def _xml(self): +
    756 """ + 757 helper for xml generation. Returns separately: + 758 - the component attributes + 759 - the generated xml of the inner components + 760 + 761 Component attributes start with an underscore ('_') and + 762 do not have a False or None value. The underscore is removed. + 763 A value of True is replaced with the attribute name. + 764 + 765 :returns: tuple: (attributes, components) + 766 """ + 767 + 768 # get the attributes for this component + 769 # (they start with '_', others may have special meanings) + 770 fa = '' + 771 for key in sorted(self.attributes): + 772 value = self[key] + 773 if key[:1] != '_': + 774 continue + 775 name = key[1:] + 776 if value is True: + 777 value = name + 778 elif value is False or value is None: + 779 continue + 780 fa += ' %s="%s"' % (name, xmlescape(value, True)) + 781 + 782 # get the xml for the inner components + 783 co = join([xmlescape(component) for component in + 784 self.components]) + 785 + 786 return (fa, co) +
    787 +
    788 - def xml(self): +
    789 """ + 790 generates the xml for this component. + 791 """ + 792 + 793 (fa, co) = self._xml() + 794 + 795 if not self.tag: + 796 return co + 797 + 798 if self.tag[-1:] == '/': + 799 # <tag [attributes] /> + 800 return '<%s%s />' % (self.tag[:-1], fa) + 801 + 802 # else: <tag [attributes]> inner components xml </tag> + 803 return '<%s%s>%s</%s>' % (self.tag, fa, co, self.tag) +
    804 +
    805 - def __str__(self): +
    806 """ + 807 str(COMPONENT) returns equals COMPONENT.xml() + 808 """ + 809 + 810 return self.xml() +
    811 +
    812 - def flatten(self, render=None): +
    813 """ + 814 return the text stored by the DIV object rendered by the render function + 815 the render function must take text, tagname, and attributes + 816 render=None is equivalent to render=lambda text, tag, attr: text + 817 + 818 >>> markdown = lambda text,tag=None,attributes={}: \ + 819 {None: re.sub('\s+',' ',text), \ + 820 'h1':'#'+text+'\\n\\n', \ + 821 'p':text+'\\n'}.get(tag,text) + 822 >>> a=TAG('<h1>Header</h1><p>this is a test</p>') + 823 >>> a.flatten(markdown) + 824 '#Header\\n\\nthis is a test\\n' + 825 """ + 826 + 827 text = '' + 828 for c in self.components: + 829 if isinstance(c,XmlComponent): + 830 s=c.flatten(render) + 831 elif render: + 832 s=render(str(c)) + 833 else: + 834 s=str(c) + 835 text+=s + 836 if render: + 837 text = render(text,self.tag,self.attributes) + 838 return text +
    839 + 840 regex_tag=re.compile('^[\w\-\:]+') + 841 regex_id=re.compile('#([\w\-]+)') + 842 regex_class=re.compile('\.([\w\-]+)') + 843 regex_attr=re.compile('\[([\w\-\:]+)=(.*?)\]') + 844 + 845 +
    846 - def elements(self, *args, **kargs): +
    847 """ + 848 find all component that match the supplied attribute dictionary, + 849 or None if nothing could be found + 850 + 851 All components of the components are searched. + 852 + 853 >>> a = DIV(DIV(SPAN('x'),3,DIV(SPAN('y')))) + 854 >>> for c in a.elements('span',first_only=True): c[0]='z' + 855 >>> print a + 856 <div><div><span>z</span>3<div><span>y</span></div></div></div> + 857 >>> for c in a.elements('span'): c[0]='z' + 858 >>> print a + 859 <div><div><span>z</span>3<div><span>z</span></div></div></div> + 860 + 861 It also supports a syntax compatible with jQuery + 862 + 863 >>> a=TAG('<div><span><a id="1-1" u:v=$>hello</a></span><p class="this is a test">world</p></div>') + 864 >>> for e in a.elements('div a#1-1, p.is'): print e.flatten() + 865 hello + 866 world + 867 >>> for e in a.elements('#1-1'): print e.flatten() + 868 hello + 869 >>> a.elements('a[u:v=$]')[0].xml() + 870 '<a id="1-1" u:v="$">hello</a>' + 871 + 872 >>> a=FORM( INPUT(_type='text'), SELECT(range(1)), TEXTAREA() ) + 873 >>> for c in a.elements('input, select, textarea'): c['_disabled'] = 'disabled' + 874 >>> a.xml() + 875 '<form action="" enctype="multipart/form-data" method="post"><input disabled="disabled" type="text" /><select disabled="disabled"><option value="0">0</option></select><textarea cols="40" disabled="disabled" rows="10"></textarea></form>' + 876 """ + 877 if len(args)==1: + 878 args = [a.strip() for a in args[0].split(',')] + 879 if len(args)>1: + 880 subset = [self.elements(a,**kargs) for a in args] + 881 return reduce(lambda a,b:a+b,subset,[]) + 882 elif len(args)==1: + 883 items = args[0].split() + 884 if len(items)>1: + 885 subset=[a.elements(' '.join(items[1:]),**kargs) for a in self.elements(items[0])] + 886 return reduce(lambda a,b:a+b,subset,[]) + 887 else: + 888 item=items[0] + 889 if '#' in item or '.' in item or '[' in item: + 890 match_tag = self.regex_tag.search(item) + 891 match_id = self.regex_id.search(item) + 892 match_class = self.regex_class.search(item) + 893 match_attr = self.regex_attr.finditer(item) + 894 args = [] + 895 if match_tag: args = [match_tag.group()] + 896 if match_id: kargs['_id'] = match_id.group(1) + 897 if match_class: kargs['_class'] = re.compile('(?<!\w)%s(?!\w)' % \ + 898 match_class.group(1).replace('-','\\-').replace(':','\\:')) + 899 for item in match_attr: + 900 kargs['_'+item.group(1)]=item.group(2) + 901 return self.elements(*args,**kargs) + 902 # make a copy of the components + 903 matches = [] + 904 first_only = False + 905 if kargs.has_key("first_only"): + 906 first_only = kargs["first_only"] + 907 del kargs["first_only"] + 908 # check if the component has an attribute with the same + 909 # value as provided + 910 check = True + 911 tag = getattr(self,'tag').replace("/","") + 912 if args and tag not in args: + 913 check = False + 914 for (key, value) in kargs.items(): + 915 if isinstance(value,(str,int)): + 916 if self[key] != str(value): + 917 check = False + 918 elif key in self.attributes: + 919 if not value.search(str(self[key])): + 920 check = False + 921 else: + 922 check = False + 923 if 'find' in kargs: + 924 find = kargs['find'] + 925 for c in self.components: + 926 if isinstance(find,(str,int)): + 927 if isinstance(c,str) and str(find) in c: + 928 check = True + 929 else: + 930 if isinstance(c,str) and find.search(c): + 931 check = True + 932 # if found, return the component + 933 if check: + 934 matches.append(self) + 935 if first_only: + 936 return matches + 937 # loop the copy + 938 for c in self.components: + 939 if isinstance(c, XmlComponent): + 940 kargs['first_only'] = first_only + 941 child_matches = c.elements( *args, **kargs ) + 942 if first_only and len(child_matches) != 0: + 943 return child_matches + 944 matches.extend( child_matches ) + 945 return matches +
    946 + 947 +
    948 - def element(self, *args, **kargs): +
    949 """ + 950 find the first component that matches the supplied attribute dictionary, + 951 or None if nothing could be found + 952 + 953 Also the components of the components are searched. + 954 """ + 955 kargs['first_only'] = True + 956 elements = self.elements(*args, **kargs) + 957 if not elements: + 958 # we found nothing + 959 return None + 960 return elements[0] +
    961 +
    962 - def siblings(self,*args,**kargs): +
    963 """ + 964 find all sibling components that match the supplied argument list + 965 and attribute dictionary, or None if nothing could be found + 966 """ + 967 sibs = [s for s in self.parent.components if not s == self] + 968 matches = [] + 969 first_only = False + 970 if kargs.has_key("first_only"): + 971 first_only = kargs["first_only"] + 972 del kargs["first_only"] + 973 for c in sibs: + 974 try: + 975 check = True + 976 tag = getattr(c,'tag').replace("/","") + 977 if args and tag not in args: + 978 check = False + 979 for (key, value) in kargs.items(): + 980 if c[key] != value: + 981 check = False + 982 if check: + 983 matches.append(c) + 984 if first_only: break + 985 except: + 986 pass + 987 return matches +
    988 +
    989 - def sibling(self,*args,**kargs): +
    990 """ + 991 find the first sibling component that match the supplied argument list + 992 and attribute dictionary, or None if nothing could be found + 993 """ + 994 kargs['first_only'] = True + 995 sibs = self.siblings(*args, **kargs) + 996 if not sibs: + 997 return None + 998 return sibs[0] +
    999 +
    1000 -class CAT(DIV): +
    1001 +1002 tag = '' +
    1003 +
    1004 -def TAG_unpickler(data): +
    1005 return cPickle.loads(data) +
    1006 +
    1007 -def TAG_pickler(data): +
    1008 d = DIV() +1009 d.__dict__ = data.__dict__ +1010 marshal_dump = cPickle.dumps(d) +1011 return (TAG_unpickler, (marshal_dump,)) +
    1012 +
    1013 -class __TAG__(XmlComponent): +
    1014 +1015 """ +1016 TAG factory example:: +1017 +1018 >>> print TAG.first(TAG.second('test'), _key = 3) +1019 <first key=\"3\"><second>test</second></first> +1020 +1021 """ +1022 +
    1023 - def __getitem__(self, name): +
    1024 return self.__getattr__(name) +
    1025 +
    1026 - def __getattr__(self, name): +
    1027 if name[-1:] == '_': +1028 name = name[:-1] + '/' +1029 if isinstance(name,unicode): +1030 name = name.encode('utf-8') +1031 class __tag__(DIV): +1032 tag = name +
    1033 copy_reg.pickle(__tag__, TAG_pickler, TAG_unpickler) +1034 return lambda *a, **b: __tag__(*a, **b) +
    1035 +
    1036 - def __call__(self,html): +
    1037 return web2pyHTMLParser(decoder.decoder(html)).tree +
    1038 +1039 TAG = __TAG__() +1040 +1041 +
    1042 -class HTML(DIV): +
    1043 """ +1044 There are four predefined document type definitions. +1045 They can be specified in the 'doctype' parameter: +1046 +1047 -'strict' enables strict doctype +1048 -'transitional' enables transitional doctype (default) +1049 -'frameset' enables frameset doctype +1050 -'html5' enables HTML 5 doctype +1051 -any other string will be treated as user's own doctype +1052 +1053 'lang' parameter specifies the language of the document. +1054 Defaults to 'en'. +1055 +1056 See also :class:`DIV` +1057 """ +1058 +1059 tag = 'html' +1060 +1061 strict = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">\n' +1062 transitional = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n' +1063 frameset = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">\n' +1064 html5 = '<!DOCTYPE HTML>\n' +1065 +
    1066 - def xml(self): +
    1067 lang = self['lang'] +1068 if not lang: +1069 lang = 'en' +1070 self.attributes['_lang'] = lang +1071 doctype = self['doctype'] +1072 if doctype: +1073 if doctype == 'strict': +1074 doctype = self.strict +1075 elif doctype == 'transitional': +1076 doctype = self.transitional +1077 elif doctype == 'frameset': +1078 doctype = self.frameset +1079 elif doctype == 'html5': +1080 doctype = self.html5 +1081 else: +1082 doctype = '%s\n' % doctype +1083 else: +1084 doctype = self.transitional +1085 (fa, co) = self._xml() +1086 return '%s<%s%s>%s</%s>' % (doctype, self.tag, fa, co, self.tag) +
    1087 +
    1088 -class XHTML(DIV): +
    1089 """ +1090 This is XHTML version of the HTML helper. +1091 +1092 There are three predefined document type definitions. +1093 They can be specified in the 'doctype' parameter: +1094 +1095 -'strict' enables strict doctype +1096 -'transitional' enables transitional doctype (default) +1097 -'frameset' enables frameset doctype +1098 -any other string will be treated as user's own doctype +1099 +1100 'lang' parameter specifies the language of the document and the xml document. +1101 Defaults to 'en'. +1102 +1103 'xmlns' parameter specifies the xml namespace. +1104 Defaults to 'http://www.w3.org/1999/xhtml'. +1105 +1106 See also :class:`DIV` +1107 """ +1108 +1109 tag = 'html' +1110 +1111 strict = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n' +1112 transitional = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n' +1113 frameset = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">\n' +1114 xmlns = 'http://www.w3.org/1999/xhtml' +1115 +
    1116 - def xml(self): +
    1117 xmlns = self['xmlns'] +1118 if xmlns: +1119 self.attributes['_xmlns'] = xmlns +1120 else: +1121 self.attributes['_xmlns'] = self.xmlns +1122 lang = self['lang'] +1123 if not lang: +1124 lang = 'en' +1125 self.attributes['_lang'] = lang +1126 self.attributes['_xml:lang'] = lang +1127 doctype = self['doctype'] +1128 if doctype: +1129 if doctype == 'strict': +1130 doctype = self.strict +1131 elif doctype == 'transitional': +1132 doctype = self.transitional +1133 elif doctype == 'frameset': +1134 doctype = self.frameset +1135 else: +1136 doctype = '%s\n' % doctype +1137 else: +1138 doctype = self.transitional +1139 (fa, co) = self._xml() +1140 return '%s<%s%s>%s</%s>' % (doctype, self.tag, fa, co, self.tag) +
    1141 +1142 +
    1143 -class HEAD(DIV): +
    1144 +1145 tag = 'head' +
    1146 +
    1147 -class TITLE(DIV): +
    1148 +1149 tag = 'title' +
    1150 +1151 +
    1152 -class META(DIV): +
    1153 +1154 tag = 'meta/' +
    1155 +1156 +1160 +1161 +
    1162 -class SCRIPT(DIV): +
    1163 +1164 tag = 'script' +1165 +
    1166 - def xml(self): +
    1167 (fa, co) = self._xml() +1168 # no escaping of subcomponents +1169 co = '\n'.join([str(component) for component in +1170 self.components]) +1171 if co: +1172 # <script [attributes]><!--//--><![CDATA[//><!-- +1173 # script body +1174 # //--><!]]></script> +1175 # return '<%s%s><!--//--><![CDATA[//><!--\n%s\n//--><!]]></%s>' % (self.tag, fa, co, self.tag) +1176 return '<%s%s><!--\n%s\n//--></%s>' % (self.tag, fa, co, self.tag) +1177 else: +1178 return DIV.xml(self) +
    1179 +1180 +
    1181 -class STYLE(DIV): +
    1182 +1183 tag = 'style' +1184 +
    1185 - def xml(self): +
    1186 (fa, co) = self._xml() +1187 # no escaping of subcomponents +1188 co = '\n'.join([str(component) for component in +1189 self.components]) +1190 if co: +1191 # <style [attributes]><!--/*--><![CDATA[/*><!--*/ +1192 # style body +1193 # /*]]>*/--></style> +1194 return '<%s%s><!--/*--><![CDATA[/*><!--*/\n%s\n/*]]>*/--></%s>' % (self.tag, fa, co, self.tag) +1195 else: +1196 return DIV.xml(self) +
    1197 +1198 +
    1199 -class IMG(DIV): +
    1200 +1201 tag = 'img/' +
    1202 +1203 +
    1204 -class SPAN(DIV): +
    1205 +1206 tag = 'span' +
    1207 +1208 +
    1209 -class BODY(DIV): +
    1210 +1211 tag = 'body' +
    1212 +1213 +
    1214 -class H1(DIV): +
    1215 +1216 tag = 'h1' +
    1217 +1218 +
    1219 -class H2(DIV): +
    1220 +1221 tag = 'h2' +
    1222 +1223 +
    1224 -class H3(DIV): +
    1225 +1226 tag = 'h3' +
    1227 +1228 +
    1229 -class H4(DIV): +
    1230 +1231 tag = 'h4' +
    1232 +1233 +
    1234 -class H5(DIV): +
    1235 +1236 tag = 'h5' +
    1237 +1238 +
    1239 -class H6(DIV): +
    1240 +1241 tag = 'h6' +
    1242 +1243 +
    1244 -class P(DIV): +
    1245 """ +1246 Will replace ``\\n`` by ``<br />`` if the `cr2br` attribute is provided. +1247 +1248 see also :class:`DIV` +1249 """ +1250 +1251 tag = 'p' +1252 +
    1253 - def xml(self): +
    1254 text = DIV.xml(self) +1255 if self['cr2br']: +1256 text = text.replace('\n', '<br />') +1257 return text +
    1258 +1259 +
    1260 -class B(DIV): +
    1261 +1262 tag = 'b' +
    1263 +1264 +
    1265 -class BR(DIV): +
    1266 +1267 tag = 'br/' +
    1268 +1269 +
    1270 -class HR(DIV): +
    1271 +1272 tag = 'hr/' +
    1273 +1274 +
    1275 -class A(DIV): +
    1276 +1277 tag = 'a' +1278 +
    1279 - def xml(self): +
    1280 if self['callback']: +1281 self['_onclick']="ajax('%s',[],'%s');return false;" % \ +1282 (self['callback'],self['target'] or '') +1283 self['_href'] = self['_href'] or '#null' +1284 elif self['cid']: +1285 self['_onclick']='web2py_component("%s","%s");return false;' % \ +1286 (self['_href'],self['cid']) +1287 return DIV.xml(self) +
    1288 +1289 +
    1290 -class BUTTON(DIV): +
    1291 +1292 tag = 'button' +
    1293 +1294 +
    1295 -class EM(DIV): +
    1296 +1297 tag = 'em' +
    1298 +1299 +
    1300 -class EMBED(DIV): +
    1301 +1302 tag = 'embed/' +
    1303 +1304 +
    1305 -class TT(DIV): +
    1306 +1307 tag = 'tt' +
    1308 +1309 +
    1310 -class PRE(DIV): +
    1311 +1312 tag = 'pre' +
    1313 +1314 +
    1315 -class CENTER(DIV): +
    1316 +1317 tag = 'center' +
    1318 +1319 +
    1320 -class CODE(DIV): +
    1321 +1322 """ +1323 displays code in HTML with syntax highlighting. +1324 +1325 :param attributes: optional attributes: +1326 +1327 - language: indicates the language, otherwise PYTHON is assumed +1328 - link: can provide a link +1329 - styles: for styles +1330 +1331 Example:: +1332 +1333 {{=CODE(\"print 'hello world'\", language='python', link=None, +1334 counter=1, styles={}, highlight_line=None)}} +1335 +1336 +1337 supported languages are \"python\", \"html_plain\", \"c\", \"cpp\", +1338 \"web2py\", \"html\". +1339 The \"html\" language interprets {{ and }} tags as \"web2py\" code, +1340 \"html_plain\" doesn't. +1341 +1342 if a link='/examples/global/vars/' is provided web2py keywords are linked to +1343 the online docs. +1344 +1345 the counter is used for line numbering, counter can be None or a prompt +1346 string. +1347 """ +1348 +
    1349 - def xml(self): +
    1350 language = self['language'] or 'PYTHON' +1351 link = self['link'] +1352 counter = self.attributes.get('counter', 1) +1353 highlight_line = self.attributes.get('highlight_line', None) +1354 styles = self['styles'] or {} +1355 return highlight( +1356 join(self.components), +1357 language=language, +1358 link=link, +1359 counter=counter, +1360 styles=styles, +1361 attributes=self.attributes, +1362 highlight_line=highlight_line, +1363 ) +
    1364 +1365 +
    1366 -class LABEL(DIV): +
    1367 +1368 tag = 'label' +
    1369 +1370 +
    1371 -class LI(DIV): +
    1372 +1373 tag = 'li' +
    1374 +1375 +
    1376 -class UL(DIV): +
    1377 """ +1378 UL Component. +1379 +1380 If subcomponents are not LI-components they will be wrapped in a LI +1381 +1382 see also :class:`DIV` +1383 """ +1384 +1385 tag = 'ul' +1386 +
    1387 - def _fixup(self): +
    1388 self._wrap_components(LI, LI) +
    1389 +1390 +
    1391 -class OL(UL): +
    1392 +1393 tag = 'ol' +
    1394 +1395 +
    1396 -class TD(DIV): +
    1397 +1398 tag = 'td' +
    1399 +1400 +
    1401 -class TH(DIV): +
    1402 +1403 tag = 'th' +
    1404 +1405 +
    1406 -class TR(DIV): +
    1407 """ +1408 TR Component. +1409 +1410 If subcomponents are not TD/TH-components they will be wrapped in a TD +1411 +1412 see also :class:`DIV` +1413 """ +1414 +1415 tag = 'tr' +1416 +
    1417 - def _fixup(self): +
    1418 self._wrap_components((TD, TH), TD) +
    1419 +
    1420 -class THEAD(DIV): +
    1421 +1422 tag = 'thead' +1423 +
    1424 - def _fixup(self): +
    1425 self._wrap_components(TR, TR) +
    1426 +1427 +
    1428 -class TBODY(DIV): +
    1429 +1430 tag = 'tbody' +1431 +
    1432 - def _fixup(self): +
    1433 self._wrap_components(TR, TR) +
    1434 +1435 +
    1436 -class TFOOT(DIV): +
    1437 +1438 tag = 'tfoot' +1439 +
    1440 - def _fixup(self): +
    1441 self._wrap_components(TR, TR) +
    1442 +1443 +
    1444 -class COL(DIV): +
    1445 +1446 tag = 'col' +
    1447 +1448 +
    1449 -class COLGROUP(DIV): +
    1450 +1451 tag = 'colgroup' +
    1452 +1453 +
    1454 -class TABLE(DIV): +
    1455 """ +1456 TABLE Component. +1457 +1458 If subcomponents are not TR/TBODY/THEAD/TFOOT-components +1459 they will be wrapped in a TR +1460 +1461 see also :class:`DIV` +1462 """ +1463 +1464 tag = 'table' +1465 +
    1466 - def _fixup(self): +
    1468 +
    1469 -class I(DIV): +
    1470 +1471 tag = 'i' +
    1472 +
    1473 -class IFRAME(DIV): +
    1474 +1475 tag = 'iframe' +
    1476 +1477 +
    1478 -class INPUT(DIV): +
    1479 +1480 """ +1481 INPUT Component +1482 +1483 examples:: +1484 +1485 >>> INPUT(_type='text', _name='name', value='Max').xml() +1486 '<input name=\"name\" type=\"text\" value=\"Max\" />' +1487 +1488 >>> INPUT(_type='checkbox', _name='checkbox', value='on').xml() +1489 '<input checked=\"checked\" name=\"checkbox\" type=\"checkbox\" value=\"on\" />' +1490 +1491 >>> INPUT(_type='radio', _name='radio', _value='yes', value='yes').xml() +1492 '<input checked=\"checked\" name=\"radio\" type=\"radio\" value=\"yes\" />' +1493 +1494 >>> INPUT(_type='radio', _name='radio', _value='no', value='yes').xml() +1495 '<input name=\"radio\" type=\"radio\" value=\"no\" />' +1496 +1497 the input helper takes two special attributes value= and requires=. +1498 +1499 :param value: used to pass the initial value for the input field. +1500 value differs from _value because it works for checkboxes, radio, +1501 textarea and select/option too. +1502 +1503 - for a checkbox value should be '' or 'on'. +1504 - for a radio or select/option value should be the _value +1505 of the checked/selected item. +1506 +1507 :param requires: should be None, or a validator or a list of validators +1508 for the value of the field. +1509 """ +1510 +1511 tag = 'input/' +1512 +
    1513 - def _validate(self): +
    1514 +1515 # # this only changes value, not _value +1516 +1517 name = self['_name'] +1518 if name is None or name == '': +1519 return True +1520 name = str(name) +1521 +1522 if self['_type'] != 'checkbox': +1523 self['old_value'] = self['value'] or self['_value'] or '' +1524 value = self.request_vars.get(name, '') +1525 self['value'] = value +1526 else: +1527 self['old_value'] = self['value'] or False +1528 value = self.request_vars.get(name) +1529 if isinstance(value, (tuple, list)): +1530 self['value'] = self['_value'] in value +1531 else: +1532 self['value'] = self['_value'] == value +1533 requires = self['requires'] +1534 if requires: +1535 if not isinstance(requires, (list, tuple)): +1536 requires = [requires] +1537 for validator in requires: +1538 (value, errors) = validator(value) +1539 if errors != None: +1540 self.vars[name] = value +1541 self.errors[name] = errors +1542 break +1543 if not name in self.errors: +1544 self.vars[name] = value +1545 return True +1546 return False +
    1547 +
    1548 - def _postprocessing(self): +
    1549 t = self['_type'] +1550 if not t: +1551 t = self['_type'] = 'text' +1552 t = t.lower() +1553 value = self['value'] +1554 if self['_value'] == None: +1555 _value = None +1556 else: +1557 _value = str(self['_value']) +1558 if t == 'checkbox': +1559 if not _value: +1560 _value = self['_value'] = 'on' +1561 if not value: +1562 value = [] +1563 elif value is True: +1564 value = [_value] +1565 elif not isinstance(value,(list,tuple)): +1566 value = str(value).split('|') +1567 self['_checked'] = _value in value and 'checked' or None +1568 elif t == 'radio': +1569 if str(value) == str(_value): +1570 self['_checked'] = 'checked' +1571 else: +1572 self['_checked'] = None +1573 elif t == 'text' or t == 'hidden': +1574 if value != None: +1575 self['_value'] = value +1576 else: +1577 self['value'] = _value +
    1578 +
    1579 - def xml(self): +
    1580 name = self.attributes.get('_name', None) +1581 if name and hasattr(self, 'errors') \ +1582 and self.errors.get(name, None) \ +1583 and self['hideerror'] != True: +1584 return DIV.xml(self) + DIV(self.errors[name], _class='error', +1585 errors=None, _id='%s__error' % name).xml() +1586 else: +1587 return DIV.xml(self) +
    1588 +1589 +
    1590 -class TEXTAREA(INPUT): +
    1591 +1592 """ +1593 example:: +1594 +1595 TEXTAREA(_name='sometext', value='blah '*100, requires=IS_NOT_EMPTY()) +1596 +1597 'blah blah blah ...' will be the content of the textarea field. +1598 """ +1599 +1600 tag = 'textarea' +1601 +
    1602 - def _postprocessing(self): +
    1603 if not '_rows' in self.attributes: +1604 self['_rows'] = 10 +1605 if not '_cols' in self.attributes: +1606 self['_cols'] = 40 +1607 if self['value'] != None: +1608 self.components = [self['value']] +1609 elif self.components: +1610 self['value'] = self.components[0] +
    1611 +1612 +
    1613 -class OPTION(DIV): +
    1614 +1615 tag = 'option' +1616 +
    1617 - def _fixup(self): +
    1618 if not '_value' in self.attributes: +1619 self.attributes['_value'] = str(self.components[0]) +
    1620 +1621 +
    1622 -class OBJECT(DIV): +
    1623 +1624 tag = 'object' +
    1625 +
    1626 -class OPTGROUP(DIV): +
    1627 +1628 tag = 'optgroup' +1629 +
    1630 - def _fixup(self): +
    1631 components = [] +1632 for c in self.components: +1633 if isinstance(c, OPTION): +1634 components.append(c) +1635 else: +1636 components.append(OPTION(c, _value=str(c))) +1637 self.components = components +
    1638 +1639 +
    1640 -class SELECT(INPUT): +
    1641 +1642 """ +1643 example:: +1644 +1645 >>> from validators import IS_IN_SET +1646 >>> SELECT('yes', 'no', _name='selector', value='yes', +1647 ... requires=IS_IN_SET(['yes', 'no'])).xml() +1648 '<select name=\"selector\"><option selected=\"selected\" value=\"yes\">yes</option><option value=\"no\">no</option></select>' +1649 +1650 """ +1651 +1652 tag = 'select' +1653 +
    1654 - def _fixup(self): +
    1655 components = [] +1656 for c in self.components: +1657 if isinstance(c, (OPTION, OPTGROUP)): +1658 components.append(c) +1659 else: +1660 components.append(OPTION(c, _value=str(c))) +1661 self.components = components +
    1662 +
    1663 - def _postprocessing(self): +
    1664 component_list = [] +1665 for c in self.components: +1666 if isinstance(c, OPTGROUP): +1667 component_list.append(c.components) +1668 else: +1669 component_list.append([c]) +1670 options = itertools.chain(*component_list) +1671 +1672 value = self['value'] +1673 if value != None: +1674 if not self['_multiple']: +1675 for c in options: # my patch +1676 if value and str(c['_value'])==str(value): +1677 c['_selected'] = 'selected' +1678 else: +1679 c['_selected'] = None +1680 else: +1681 if isinstance(value,(list,tuple)): +1682 values = [str(item) for item in value] +1683 else: +1684 values = [str(value)] +1685 for c in options: # my patch +1686 if value and str(c['_value']) in values: +1687 c['_selected'] = 'selected' +1688 else: +1689 c['_selected'] = None +
    1690 +1691 +
    1692 -class FIELDSET(DIV): +
    1693 +1694 tag = 'fieldset' +
    1695 +1696 +
    1697 -class LEGEND(DIV): +
    1698 +1699 tag = 'legend' +
    1700 +1701 +
    1702 -class FORM(DIV): +
    1703 +1704 """ +1705 example:: +1706 +1707 >>> from validators import IS_NOT_EMPTY +1708 >>> form=FORM(INPUT(_name=\"test\", requires=IS_NOT_EMPTY())) +1709 >>> form.xml() +1710 '<form action=\"\" enctype=\"multipart/form-data\" method=\"post\"><input name=\"test\" type=\"text\" /></form>' +1711 +1712 a FORM is container for INPUT, TEXTAREA, SELECT and other helpers +1713 +1714 form has one important method:: +1715 +1716 form.accepts(request.vars, session) +1717 +1718 if form is accepted (and all validators pass) form.vars contains the +1719 accepted vars, otherwise form.errors contains the errors. +1720 in case of errors the form is modified to present the errors to the user. +1721 """ +1722 +1723 tag = 'form' +1724 +
    1725 - def __init__(self, *components, **attributes): +
    1726 DIV.__init__(self, *components, **attributes) +1727 self.vars = Storage() +1728 self.errors = Storage() +1729 self.latest = Storage() +
    1730 +
    1731 - def accepts( +1732 self, +1733 vars, +1734 session=None, +1735 formname='default', +1736 keepvalues=False, +1737 onvalidation=None, +1738 hideerror=False, +1739 ): +
    1740 if vars.__class__.__name__ == 'Request': +1741 vars=vars.post_vars +1742 self.errors.clear() +1743 self.request_vars = Storage() +1744 self.request_vars.update(vars) +1745 self.session = session +1746 self.formname = formname +1747 self.keepvalues = keepvalues +1748 +1749 # if this tag is a form and we are in accepting mode (status=True) +1750 # check formname and formkey +1751 +1752 status = True +1753 if self.session: +1754 formkey = self.session.get('_formkey[%s]' % self.formname, None) +1755 # check if user tampering with form and void CSRF +1756 if formkey != self.request_vars._formkey: +1757 status = False +1758 if self.formname != self.request_vars._formname: +1759 status = False +1760 if status and self.session: +1761 # check if editing a record that has been modified by the server +1762 if hasattr(self,'record_hash') and self.record_hash != formkey: +1763 status = False +1764 self.record_changed = True +1765 status = self._traverse(status,hideerror) +1766 if onvalidation: +1767 if isinstance(onvalidation, dict): +1768 onsuccess = onvalidation.get('onsuccess', None) +1769 onfailure = onvalidation.get('onfailure', None) +1770 if onsuccess and status: +1771 onsuccess(self) +1772 if onfailure and vars and not status: +1773 onfailure(self) +1774 status = len(self.errors) == 0 +1775 elif status: +1776 if isinstance(onvalidation, (list, tuple)): +1777 [f(self) for f in onvalidation] +1778 else: +1779 onvalidation(self) +1780 if self.errors: +1781 status = False +1782 if session != None: +1783 if hasattr(self,'record_hash'): +1784 formkey = self.record_hash +1785 else: +1786 formkey = web2py_uuid() +1787 self.formkey = session['_formkey[%s]' % formname] = formkey +1788 if status and not keepvalues: +1789 self._traverse(False,hideerror) +1790 return status +
    1791 +
    1792 - def _postprocessing(self): +
    1793 if not '_action' in self.attributes: +1794 self['_action'] = '' +1795 if not '_method' in self.attributes: +1796 self['_method'] = 'post' +1797 if not '_enctype' in self.attributes: +1798 self['_enctype'] = 'multipart/form-data' +
    1799 +
    1800 - def hidden_fields(self): +
    1801 c = [] +1802 if 'hidden' in self.attributes: +1803 for (key, value) in self.attributes.get('hidden',{}).items(): +1804 c.append(INPUT(_type='hidden', _name=key, _value=value)) +1805 +1806 if hasattr(self, 'formkey') and self.formkey: +1807 c.append(INPUT(_type='hidden', _name='_formkey', +1808 _value=self.formkey)) +1809 if hasattr(self, 'formname') and self.formname: +1810 c.append(INPUT(_type='hidden', _name='_formname', +1811 _value=self.formname)) +1812 return DIV(c, _class="hidden") +
    1813 +
    1814 - def xml(self): +
    1815 newform = FORM(*self.components, **self.attributes) +1816 hidden_fields = self.hidden_fields() +1817 if hidden_fields.components: +1818 newform.append(hidden_fields) +1819 return DIV.xml(newform) +
    1820 +
    1821 - def validate(self, +1822 values=None, +1823 session=None, +1824 formname='default', +1825 keepvalues=False, +1826 onvalidation=None, +1827 hideerror=False, +1828 onsuccess='flash', +1829 onfailure='flash', +1830 message_onsuccess=None, +1831 message_onfailure=None, +1832 ): +
    1833 """ +1834 This function validates the form, +1835 you can use it instead of directly form.accepts. +1836 +1837 Usage: +1838 In controller +1839 +1840 def action(): +1841 form=FORM(INPUT(_name=\"test\", requires=IS_NOT_EMPTY())) +1842 form.validate() #you can pass some args here - see below +1843 return dict(form=form) +1844 +1845 This can receive a bunch of arguments +1846 +1847 onsuccess = 'flash' - will show message_onsuccess in response.flash +1848 None - will do nothing +1849 can be a function (lambda form: pass) +1850 onfailure = 'flash' - will show message_onfailure in response.flash +1851 None - will do nothing +1852 can be a function (lambda form: pass) +1853 +1854 values = values to test the validation - dictionary, response.vars, session or other - Default to (request.vars, session) +1855 message_onsuccess +1856 message_onfailure +1857 """ +1858 from gluon import current +1859 if not session: session = current.session +1860 if not values: values = current.request.post_vars +1861 +1862 message_onsuccess = message_onsuccess or current.T("Success!") +1863 message_onfailure = message_onfailure or \ +1864 current.T("Errors in form, please check it out.") +1865 +1866 if self.accepts(values, session): +1867 if onsuccess == 'flash': +1868 current.response.flash = message_onsuccess +1869 elif callable(onsuccess): +1870 onsuccess(self) +1871 return True +1872 elif self.errors: +1873 if onfailure == 'flash': +1874 current.response.flash = message_onfailure +1875 elif callable(onfailure): +1876 onfailure(self) +1877 return False +
    1878 +
    1879 - def process(self, values=None, session=None, **args): +
    1880 """ +1881 Perform the .validate() method but returns the form +1882 +1883 Usage in controllers: +1884 # directly on return +1885 def action(): +1886 #some code here +1887 return dict(form=FORM(...).process(...)) +1888 +1889 You can use it with FORM, SQLFORM or FORM based plugins +1890 +1891 Examples: +1892 #response.flash messages +1893 def action(): +1894 form = SQLFORM(db.table).process(message_onsuccess='Sucess!') +1895 retutn dict(form=form) +1896 +1897 # callback function +1898 # callback receives True or False as first arg, and a list of args. +1899 def my_callback(status, msg): +1900 response.flash = "Success! "+msg if status else "Errors occured" +1901 +1902 # after argument can be 'flash' to response.flash messages +1903 # or a function name to use as callback or None to do nothing. +1904 def action(): +1905 return dict(form=SQLFORM(db.table).process(onsuccess=my_callback) +1906 """ +1907 self.validate(values=values, session=session, **args) +1908 return self +
    1909 +1910 +
    1911 -class BEAUTIFY(DIV): +
    1912 +1913 """ +1914 example:: +1915 +1916 >>> BEAUTIFY(['a', 'b', {'hello': 'world'}]).xml() +1917 '<div><table><tr><td><div>a</div></td></tr><tr><td><div>b</div></td></tr><tr><td><div><table><tr><td style="font-weight:bold;">hello</td><td valign="top">:</td><td><div>world</div></td></tr></table></div></td></tr></table></div>' +1918 +1919 turns any list, dictionary, etc into decent looking html. +1920 Two special attributes are +1921 :sorted: a function that takes the dict and returned sorted keys +1922 :keyfilter: a funciton that takes a key and returns its representation +1923 or None if the key is to be skipped. By default key[:1]=='_' is skipped. +1924 """ +1925 +1926 tag = 'div' +1927 +1928 @staticmethod +
    1929 - def no_underscore(key): +
    1930 if key[:1]=='_': +1931 return None +1932 return key +
    1933 +
    1934 - def __init__(self, component, **attributes): +
    1935 self.components = [component] +1936 self.attributes = attributes +1937 sorter = attributes.get('sorted',sorted) +1938 keyfilter = attributes.get('keyfilter',BEAUTIFY.no_underscore) +1939 components = [] +1940 attributes = copy.copy(self.attributes) +1941 level = attributes['level'] = attributes.get('level',6) - 1 +1942 if '_class' in attributes: +1943 attributes['_class'] += 'i' +1944 if level == 0: +1945 return +1946 for c in self.components: +1947 if hasattr(c,'xml') and callable(c.xml): +1948 components.append(c) +1949 continue +1950 elif hasattr(c,'keys') and callable(c.keys): +1951 rows = [] +1952 try: +1953 keys = (sorter and sorter(c)) or c +1954 for key in keys: +1955 if isinstance(key,(str,unicode)) and keyfilter: +1956 filtered_key = keyfilter(key) +1957 else: +1958 filtered_key = str(key) +1959 if filtered_key is None: +1960 continue +1961 value = c[key] +1962 if type(value) == types.LambdaType: +1963 continue +1964 rows.append(TR(TD(filtered_key, _style='font-weight:bold;'), +1965 TD(':',_valign='top'), +1966 TD(BEAUTIFY(value, **attributes)))) +1967 components.append(TABLE(*rows, **attributes)) +1968 continue +1969 except: +1970 pass +1971 if isinstance(c, str): +1972 components.append(str(c)) +1973 elif isinstance(c, unicode): +1974 components.append(c.encode('utf8')) +1975 elif isinstance(c, (list, tuple)): +1976 items = [TR(TD(BEAUTIFY(item, **attributes))) +1977 for item in c] +1978 components.append(TABLE(*items, **attributes)) +1979 elif isinstance(c, cgi.FieldStorage): +1980 components.append('FieldStorage object') +1981 else: +1982 components.append(repr(c)) +1983 self.components = components +
    1984 +1985 +2043 +2044 +
    2045 -def embed64( +2046 filename = None, +2047 file = None, +2048 data = None, +2049 extension = 'image/gif', +2050 ): +
    2051 """ +2052 helper to encode the provided (binary) data into base64. +2053 +2054 :param filename: if provided, opens and reads this file in 'rb' mode +2055 :param file: if provided, reads this file +2056 :param data: if provided, uses the provided data +2057 """ +2058 +2059 if filename and os.path.exists(file): +2060 fp = open(filename, 'rb') +2061 data = fp.read() +2062 fp.close() +2063 data = base64.b64encode(data) +2064 return 'data:%s;base64,%s' % (extension, data) +
    2065 +2066 +
    2067 -def test(): +
    2068 """ +2069 Example: +2070 +2071 >>> from validators import * +2072 >>> print DIV(A('click me', _href=URL(a='a', c='b', f='c')), BR(), HR(), DIV(SPAN(\"World\"), _class='unknown')).xml() +2073 <div><a href=\"/a/b/c\">click me</a><br /><hr /><div class=\"unknown\"><span>World</span></div></div> +2074 >>> print DIV(UL(\"doc\",\"cat\",\"mouse\")).xml() +2075 <div><ul><li>doc</li><li>cat</li><li>mouse</li></ul></div> +2076 >>> print DIV(UL(\"doc\", LI(\"cat\", _class='feline'), 18)).xml() +2077 <div><ul><li>doc</li><li class=\"feline\">cat</li><li>18</li></ul></div> +2078 >>> print TABLE(['a', 'b', 'c'], TR('d', 'e', 'f'), TR(TD(1), TD(2), TD(3))).xml() +2079 <table><tr><td>a</td><td>b</td><td>c</td></tr><tr><td>d</td><td>e</td><td>f</td></tr><tr><td>1</td><td>2</td><td>3</td></tr></table> +2080 >>> form=FORM(INPUT(_type='text', _name='myvar', requires=IS_EXPR('int(value)<10'))) +2081 >>> print form.xml() +2082 <form action=\"\" enctype=\"multipart/form-data\" method=\"post\"><input name=\"myvar\" type=\"text\" /></form> +2083 >>> print form.accepts({'myvar':'34'}, formname=None) +2084 False +2085 >>> print form.xml() +2086 <form action="" enctype="multipart/form-data" method="post"><input name="myvar" type="text" value="34" /><div class="error" id="myvar__error">invalid expression</div></form> +2087 >>> print form.accepts({'myvar':'4'}, formname=None, keepvalues=True) +2088 True +2089 >>> print form.xml() +2090 <form action=\"\" enctype=\"multipart/form-data\" method=\"post\"><input name=\"myvar\" type=\"text\" value=\"4\" /></form> +2091 >>> form=FORM(SELECT('cat', 'dog', _name='myvar')) +2092 >>> print form.accepts({'myvar':'dog'}, formname=None, keepvalues=True) +2093 True +2094 >>> print form.xml() +2095 <form action=\"\" enctype=\"multipart/form-data\" method=\"post\"><select name=\"myvar\"><option value=\"cat\">cat</option><option selected=\"selected\" value=\"dog\">dog</option></select></form> +2096 >>> form=FORM(INPUT(_type='text', _name='myvar', requires=IS_MATCH('^\w+$', 'only alphanumeric!'))) +2097 >>> print form.accepts({'myvar':'as df'}, formname=None) +2098 False +2099 >>> print form.xml() +2100 <form action=\"\" enctype=\"multipart/form-data\" method=\"post\"><input name=\"myvar\" type=\"text\" value=\"as df\" /><div class=\"error\" id=\"myvar__error\">only alphanumeric!</div></form> +2101 >>> session={} +2102 >>> form=FORM(INPUT(value=\"Hello World\", _name=\"var\", requires=IS_MATCH('^\w+$'))) +2103 >>> if form.accepts({}, session,formname=None): print 'passed' +2104 >>> if form.accepts({'var':'test ', '_formkey': session['_formkey[None]']}, session, formname=None): print 'passed' +2105 """ +2106 pass +
    2107 +2108 +
    2109 -class web2pyHTMLParser(HTMLParser): +
    2110 """ +2111 obj = web2pyHTMLParser(text) parses and html/xml text into web2py helpers. +2112 obj.tree contains the root of the tree, and tree can be manipulated +2113 +2114 >>> str(web2pyHTMLParser('hello<div a="b" c=3>wor&lt;ld<span>xxx</span>y<script/>yy</div>zzz').tree) +2115 'hello<div a="b" c="3">wor&lt;ld<span>xxx</span>y<script></script>yy</div>zzz' +2116 >>> str(web2pyHTMLParser('<div>a<span>b</div>c').tree) +2117 '<div>a<span>b</span></div>c' +2118 >>> tree = web2pyHTMLParser('hello<div a="b">world</div>').tree +2119 >>> tree.element(_a='b')['_c']=5 +2120 >>> str(tree) +2121 'hello<div a="b" c="5">world</div>' +2122 """ +
    2123 - def __init__(self,text,closed=('input','link')): +
    2124 HTMLParser.__init__(self) +2125 self.tree = self.parent = TAG['']() +2126 self.closed = closed +2127 self.tags = [x for x in __all__ if isinstance(eval(x),DIV)] +2128 self.last = None +2129 self.feed(text) +
    2130 - def handle_starttag(self, tagname, attrs): +
    2131 if tagname.upper() in self.tags: +2132 tag=eval(tagname.upper()) +2133 else: +2134 if tagname in self.closed: tagname+='/' +2135 tag = TAG[tagname]() +2136 for key,value in attrs: tag['_'+key]=value +2137 tag.parent = self.parent +2138 self.parent.append(tag) +2139 if not tag.tag.endswith('/'): +2140 self.parent=tag +2141 else: +2142 self.last = tag.tag[:-1] +
    2143 - def handle_data(self,data): +
    2144 try: +2145 self.parent.append(data.encode('utf8','xmlcharref')) +2146 except: +2147 self.parent.append(data.decode('latin1').encode('utf8','xmlcharref')) +
    2148 - def handle_charref(self,name): +
    2149 if name[1].lower()=='x': +2150 self.parent.append(unichr(int(name[2:], 16)).encode('utf8')) +2151 else: +2152 self.parent.append(unichr(int(name[1:], 10)).encode('utf8')) +
    2153 - def handle_entityref(self,name): +
    2154 self.parent.append(unichr(name2codepoint[name]).encode('utf8')) +
    2155 - def handle_endtag(self, tagname): +
    2156 # this deals with unbalanced tags +2157 if tagname==self.last: +2158 return +2159 while True: +2160 try: +2161 parent_tagname=self.parent.tag +2162 self.parent = self.parent.parent +2163 except: +2164 raise RuntimeError, "unable to balance tag %s" % tagname +2165 if parent_tagname[:len(tagname)]==tagname: break +
    2166 +
    2167 -def markdown_serializer(text,tag=None,attr={}): +
    2168 if tag is None: return re.sub('\s+',' ',text) +2169 if tag=='br': return '\n\n' +2170 if tag=='h1': return '#'+text+'\n\n' +2171 if tag=='h2': return '#'*2+text+'\n\n' +2172 if tag=='h3': return '#'*3+text+'\n\n' +2173 if tag=='h4': return '#'*4+text+'\n\n' +2174 if tag=='p': return text+'\n\n' +2175 if tag=='b' or tag=='strong': return '**%s**' % text +2176 if tag=='em' or tag=='i': return '*%s*' % text +2177 if tag=='tt' or tag=='code': return '`%s`' % text +2178 if tag=='a': return '[%s](%s)' % (text,attr.get('_href','')) +2179 if tag=='img': return '![%s](%s)' % (attr.get('_alt',''),attr.get('_src','')) +2180 return text +
    2181 +
    2182 -def markmin_serializer(text,tag=None,attr={}): +
    2183 # if tag is None: return re.sub('\s+',' ',text) +2184 if tag=='br': return '\n\n' +2185 if tag=='h1': return '# '+text+'\n\n' +2186 if tag=='h2': return '#'*2+' '+text+'\n\n' +2187 if tag=='h3': return '#'*3+' '+text+'\n\n' +2188 if tag=='h4': return '#'*4+' '+text+'\n\n' +2189 if tag=='p': return text+'\n\n' +2190 if tag=='li': return '\n- '+text.replace('\n',' ') +2191 if tag=='tr': return text[3:].replace('\n',' ')+'\n' +2192 if tag in ['table','blockquote']: return '\n-----\n'+text+'\n------\n' +2193 if tag in ['td','th']: return ' | '+text +2194 if tag in ['b','strong','label']: return '**%s**' % text +2195 if tag in ['em','i']: return "''%s''" % text +2196 if tag in ['tt']: return '``%s``' % text.strip() +2197 if tag in ['code']: return '``\n%s``' % text +2198 if tag=='a': return '[[%s %s]]' % (text,attr.get('_href','')) +2199 if tag=='img': return '[[%s %s left]]' % (attr.get('_alt','no title'),attr.get('_src','')) +2200 return text +
    2201 +2202 +
    2203 -class MARKMIN(XmlComponent): +
    2204 """ +2205 For documentation: http://web2py.com/examples/static/markmin.html +2206 """ +
    2207 - def __init__(self, text, extra={}, allowed={}, sep='p'): +
    2208 self.text = text +2209 self.extra = extra +2210 self.allowed = allowed +2211 self.sep = sep +
    2212 +
    2213 - def xml(self): +
    2214 """ +2215 calls the gluon.contrib.markmin render function to convert the wiki syntax +2216 """ +2217 return render(self.text,extra=self.extra,allowed=self.allowed,sep=self.sep) +
    2218 +
    2219 - def __str__(self): +
    2220 return self.xml() +
    2221 +
    2222 - def flatten(self,render=None): +
    2223 """ +2224 return the text stored by the MARKMIN object rendered by the render function +2225 """ +2226 return self.text +
    2227 +
    2228 - def elements(self, *args, **kargs): +
    2229 """ +2230 to be considered experimental since the behavior of this method is questionable +2231 another options could be TAG(self.text).elements(*args,**kargs) +2232 """ +2233 return [self.text] +
    2234 +2235 +2236 if __name__ == '__main__': +2237 import doctest +2238 doctest.testmod() +2239 +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.A-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.A-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.A-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.A-class.html @@ -0,0 +1,304 @@ + + + + + web2py.gluon.html.A + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class A + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class A

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  A
    +
    + +
    + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    xml(self)
    + generates the xml for this component.
    + source code + +
    + +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update +

    +

    Inherited from DIV (private): + _fixup, + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'a' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    xml(self) +

    +
    source code  +
    + + generates the xml for this component. +
    +
    Overrides: + DIV.xml +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.B-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.B-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.B-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.B-class.html @@ -0,0 +1,246 @@ + + + + + web2py.gluon.html.B + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class B + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class B

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  B
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update, + xml +

    +

    Inherited from DIV (private): + _fixup, + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'b' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.BEAUTIFY-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.BEAUTIFY-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.BEAUTIFY-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.BEAUTIFY-class.html @@ -0,0 +1,363 @@ + + + + + web2py.gluon.html.BEAUTIFY + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class BEAUTIFY + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class BEAUTIFY

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  BEAUTIFY
    +
    + +
    +
    +
    +example::
    +
    +    >>> BEAUTIFY(['a', 'b', {'hello': 'world'}]).xml()
    +    '<div><table><tr><td><div>a</div></td></tr><tr><td><div>b</div></td></tr><tr><td><div><table><tr><td style="font-weight:bold;">hello</td><td valign="top">:</td><td><div>world</div></td></tr></table></div></td></tr></table></div>'
    +
    +turns any list, dictionary, etc into decent looking html.
    +Two special attributes are
    +:sorted: a function that takes the dict and returned sorted keys
    +:keyfilter: a funciton that takes a key and returns its representation
    +            or None if the key is to be skipped. By default key[:1]=='_' is skipped.
    +
    +


    + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + component, + **attributes)
    + :param *components: any components that should be nested in this + element :param **attributes: any attributes you want to give to this + element
    + source code + +
    + +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update, + xml +

    +

    Inherited from DIV (private): + _fixup, + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +   + + + + + + +
    no_underscore(key) + source code + +
    + +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'div' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + component, + **attributes) +
    (Constructor) +

    +
    source code  +
    + +

    :param *components: any components that should be nested in this + element :param **attributes: any attributes you want to give to this + element

    + :raises SyntaxError: when a stand alone tag receives components +
    +
    Overrides: + DIV.__init__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.BODY-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.BODY-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.BODY-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.BODY-class.html @@ -0,0 +1,246 @@ + + + + + web2py.gluon.html.BODY + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class BODY + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class BODY

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  BODY
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update, + xml +

    +

    Inherited from DIV (private): + _fixup, + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'body' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.BR-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.BR-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.BR-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.BR-class.html @@ -0,0 +1,246 @@ + + + + + web2py.gluon.html.BR + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class BR + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class BR

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  BR
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update, + xml +

    +

    Inherited from DIV (private): + _fixup, + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'br/' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.BUTTON-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.BUTTON-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.BUTTON-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.BUTTON-class.html @@ -0,0 +1,246 @@ + + + + + web2py.gluon.html.BUTTON + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class BUTTON + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class BUTTON

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  BUTTON
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update, + xml +

    +

    Inherited from DIV (private): + _fixup, + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'button' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.CAT-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.CAT-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.CAT-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.CAT-class.html @@ -0,0 +1,246 @@ + + + + + web2py.gluon.html.CAT + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class CAT + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class CAT

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  CAT
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update, + xml +

    +

    Inherited from DIV (private): + _fixup, + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = '' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.CENTER-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.CENTER-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.CENTER-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.CENTER-class.html @@ -0,0 +1,246 @@ + + + + + web2py.gluon.html.CENTER + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class CENTER + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class CENTER

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  CENTER
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update, + xml +

    +

    Inherited from DIV (private): + _fixup, + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'center' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.CODE-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.CODE-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.CODE-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.CODE-class.html @@ -0,0 +1,325 @@ + + + + + web2py.gluon.html.CODE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class CODE + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class CODE

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  CODE
    +
    + +
    +

    displays code in HTML with syntax highlighting.

    + :param attributes: optional attributes: +
      +
    • + language: indicates the language, otherwise PYTHON is assumed +
    • +
    • + link: can provide a link +
    • +
    • + styles: for styles +
    • +
    + Example: +
    +   {{=CODE("print 'hello world'", language='python', link=None,
    +       counter=1, styles={}, highlight_line=None)}}
    +
    +

    supported languages are "python", "html_plain", + "c", "cpp", "web2py", "html". The + "html" language interprets {{ and }} tags as "web2py" + code, "html_plain" doesn't.

    +

    if a link='/examples/global/vars/' is provided web2py keywords are + linked to the online docs.

    + the counter is used for line numbering, counter can be None or a + prompt string.

    + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    xml(self)
    + generates the xml for this component.
    + source code + +
    + +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update +

    +

    Inherited from DIV (private): + _fixup, + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag, + tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    xml(self) +

    +
    source code  +
    + + generates the xml for this component. +
    +
    Overrides: + DIV.xml +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.COL-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.COL-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.COL-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.COL-class.html @@ -0,0 +1,246 @@ + + + + + web2py.gluon.html.COL + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class COL + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class COL

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  COL
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update, + xml +

    +

    Inherited from DIV (private): + _fixup, + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'col' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.COLGROUP-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.COLGROUP-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.COLGROUP-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.COLGROUP-class.html @@ -0,0 +1,246 @@ + + + + + web2py.gluon.html.COLGROUP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class COLGROUP + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class COLGROUP

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  COLGROUP
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update, + xml +

    +

    Inherited from DIV (private): + _fixup, + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'colgroup' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.DIV-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.DIV-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.DIV-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.DIV-class.html @@ -0,0 +1,1180 @@ + + + + + web2py.gluon.html.DIV + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class DIV + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class DIV

    source code

    +
    +  object --+    
    +           |    
    +XmlComponent --+
    +               |
    +              DIV
    +
    + +
    Known Subclasses:
    +
    + A, + B, + BEAUTIFY, + BODY, + BR, + CAT, + CENTER, + CODE, + EM, + EMBED, + FIELDSET, + FORM, + H1, + H2, + H3, + H4, + H5, + H6, + HEAD, + HR, + HTML, + I, + IFRAME, + IMG, + INPUT, + LABEL, + LEGEND, + LI, + LINK, + MENU, + META, + OBJECT, + UL, + OPTGROUP, + OPTION, + P, + PRE, + SCRIPT, + SPAN, + TABLE, + STYLE, + TBODY, + TD, + TFOOT, + TH, + THEAD, + TITLE, + TR, + TT, + XHTML, + BUTTON, + COL, + COLGROUP +
    + +
    +

    HTML helper, for easy generating and manipulating a DOM structure. + Little or no validation is done.

    +

    Behaves like a dictionary regarding updating of attributes. Behaves + like a list regarding inserting/appending components.

    + example: +
    +   >>> DIV('hello', 'world', _style='color:red;').xml()
    +   '<div style="color:red;">helloworld</div>'
    +
    +

    all other HTML helpers are derived from DIV.

    + _something="value" attributes are transparently translated + into something="value" HTML attributes

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + *components, + **attributes)
    + :param *components: any components that should be nested in this + element :param **attributes: any attributes you want to give to this + element
    + source code + +
    + +
    +   + + + + + + +
    update(self, + **kargs)
    + dictionary like updating of the tag attributes
    + source code + +
    + +
    +   + + + + + + +
    append(self, + value)
    + list style appending of components
    + source code + +
    + +
    +   + + + + + + +
    insert(self, + i, + value)
    + list style inserting of components
    + source code + +
    + +
    +   + + + + + + +
    __getitem__(self, + i)
    + gets attribute with name 'i' or component #i.
    + source code + +
    + +
    +   + + + + + + +
    __setitem__(self, + i, + value)
    + sets attribute with name 'i' or component #i.
    + source code + +
    + +
    +   + + + + + + +
    __delitem__(self, + i)
    + deletes attribute with name 'i' or component #i.
    + source code + +
    + +
    +   + + + + + + +
    __len__(self)
    + returns the number of included components
    + source code + +
    + +
    +   + + + + + + +
    __nonzero__(self)
    + always return True
    + source code + +
    + +
    +   + + + + + + +
    _fixup(self)
    + Handling of provided components.
    + source code + +
    + +
    +   + + + + + + +
    _wrap_components(self, + allowed_parents, + wrap_parent=1, + wrap_lambda=1)
    + helper for _fixup.
    + source code + +
    + +
    +   + + + + + + +
    _postprocessing(self)
    + Handling of attributes (normally the ones not prefixed with + '_').
    + source code + +
    + +
    +   + + + + + + +
    _traverse(self, + status, + hideerror=True) + source code + +
    + +
    +   + + + + + + +
    _validate(self)
    + nothing to validate yet.
    + source code + +
    + +
    +   + + + + + + +
    _setnode(self, + value) + source code + +
    + +
    +   + + + + + + +
    _xml(self)
    + helper for xml generation.
    + source code + +
    + +
    +   + + + + + + +
    xml(self)
    + generates the xml for this component.
    + source code + +
    + +
    +   + + + + + + +
    __str__(self)
    + str(COMPONENT) returns equals COMPONENT.xml()
    + source code + +
    + +
    +   + + + + + + +
    flatten(self, + render=1)
    + return the text stored by the DIV object rendered by the render + function the render function must take text, tagname, and attributes + render=None is equivalent to render=lambda text, tag, attr: text
    + source code + +
    + +
    +   + + + + + + +
    elements(self, + *args, + **kargs)
    + find all component that match the supplied attribute dictionary, + or None if nothing could be found
    + source code + +
    + +
    +   + + + + + + +
    element(self, + *args, + **kargs)
    + find the first component that matches the supplied attribute + dictionary, or None if nothing could be found
    + source code + +
    + +
    +   + + + + + + +
    siblings(self, + *args, + **kargs)
    + find all sibling components that match the supplied argument list + and attribute dictionary, or None if nothing could be found
    + source code + +
    + +
    +   + + + + + + +
    sibling(self, + *args, + **kargs)
    + find the first sibling component that match the supplied argument + list and attribute dictionary, or None if nothing could be found
    + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'div' +
    +   + + regex_tag = re.compile(r'^[\w-:]+') +
    +   + + regex_id = re.compile(r'#([\w-]+)') +
    +   + + regex_class = re.compile(r'\.([\w-]+)') +
    +   + + regex_attr = re.compile(r'\[([\w-:]+)=(.*?)\]') +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + *components, + **attributes) +
    (Constructor) +

    +
    source code  +
    + +

    :param *components: any components that should be nested in this + element :param **attributes: any attributes you want to give to this + element

    + :raises SyntaxError: when a stand alone tag receives components +
    +
    Overrides: + object.__init__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    append(self, + value) +

    +
    source code  +
    + + list style appending of components +
    +>>> a=DIV()
    +>>> a.append(SPAN('x'))
    +>>> print a
    +<div><span>x</span></div>
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    insert(self, + i, + value) +

    +
    source code  +
    + + list style inserting of components +
    +>>> a=DIV()
    +>>> a.insert(0,SPAN('x'))
    +>>> print a
    +<div><span>x</span></div>
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    __getitem__(self, + i) +
    (Indexing operator) +

    +
    source code  +
    + +
    +
    +gets attribute with name 'i' or component #i.
    +If attribute 'i' is not found returns None
    +
    +:param i: index
    +   if i is a string: the name of the attribute
    +   otherwise references to number of the component
    +
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    __setitem__(self, + i, + value) +
    (Index assignment operator) +

    +
    source code  +
    + +
    +
    +sets attribute with name 'i' or component #i.
    +
    +:param i: index
    +   if i is a string: the name of the attribute
    +   otherwise references to number of the component
    +:param value: the new value
    +
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    __delitem__(self, + i) +
    (Index deletion operator) +

    +
    source code  +
    + +
    +
    +deletes attribute with name 'i' or component #i.
    +
    +:param i: index
    +   if i is a string: the name of the attribute
    +   otherwise references to number of the component
    +
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    _fixup(self) +

    +
    source code  +
    + +

    Handling of provided components.

    + Nothing to fixup yet. May be overridden by subclasses, eg for wrapping + some components in another component or blocking them. +
    +
    +
    +
    + +
    + +
    + + +
    +

    _wrap_components(self, + allowed_parents, + wrap_parent=1, + wrap_lambda=1) +

    +
    source code  +
    + +
    +
    +helper for _fixup. Checks if a component is in allowed_parents,
    +otherwise wraps it in wrap_parent
    +
    +:param allowed_parents: (tuple) classes that the component should be an
    +    instance of
    +:param wrap_parent: the class to wrap the component in, if needed
    +:param wrap_lambda: lambda to use for wrapping, if needed
    +
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    _postprocessing(self) +

    +
    source code  +
    + +

    Handling of attributes (normally the ones not prefixed with '_').

    + Nothing to postprocess yet. May be overridden by subclasses +
    +
    +
    +
    + +
    + +
    + + +
    +

    _validate(self) +

    +
    source code  +
    + + nothing to validate yet. May be overridden by subclasses +
    +
    +
    +
    + +
    + +
    + + +
    +

    _xml(self) +

    +
    source code  +
    + +
    +
    +helper for xml generation. Returns separately:
    +- the component attributes
    +- the generated xml of the inner components
    +
    +Component attributes start with an underscore ('_') and
    +do not have a False or None value. The underscore is removed.
    +A value of True is replaced with the attribute name.
    +
    +:returns: tuple: (attributes, components)
    +
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    xml(self) +

    +
    source code  +
    + + generates the xml for this component. +
    +
    Overrides: + XmlComponent.xml +
    +
    +
    +
    + +
    + +
    + + +
    +

    __str__(self) +
    (Informal representation operator) +

    +
    source code  +
    + + str(COMPONENT) returns equals COMPONENT.xml() +
    +
    Overrides: + object.__str__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    flatten(self, + render=1) +

    +
    source code  +
    + + return the text stored by the DIV object rendered by the render + function the render function must take text, tagname, and attributes + render=None is equivalent to render=lambda text, tag, attr: text +
    +>>> markdown = lambda text,tag=None,attributes={}:                         {None: re.sub('\s+',' ',text),                          'h1':'#'+text+'\n\n',                          'p':text+'\n'}.get(tag,text)
    +>>> a=TAG('<h1>Header</h1><p>this is a     test</p>')
    +>>> a.flatten(markdown)
    +'#Header\n\nthis is a test\n'
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    elements(self, + *args, + **kargs) +

    +
    source code  +
    + +

    find all component that match the supplied attribute dictionary, or + None if nothing could be found

    + All components of the components are searched. +
    +>>> a = DIV(DIV(SPAN('x'),3,DIV(SPAN('y'))))
    +>>> for c in a.elements('span',first_only=True): c[0]='z'
    +>>> print a
    +<div><div><span>z</span>3<div><span>y</span></div></div></div>
    +>>> for c in a.elements('span'): c[0]='z'
    +>>> print a
    +<div><div><span>z</span>3<div><span>z</span></div></div></div>
    + It also supports a syntax compatible with jQuery +
    +>>> a=TAG('<div><span><a id="1-1" u:v=$>hello</a></span><p class="this is a test">world</p></div>')
    +>>> for e in a.elements('div a#1-1, p.is'): print e.flatten()
    +hello
    +world
    +>>> for e in a.elements('#1-1'): print e.flatten()
    +hello
    +>>> a.elements('a[u:v=$]')[0].xml()
    +'<a id="1-1" u:v="$">hello</a>'
    +
    +>>> a=FORM( INPUT(_type='text'), SELECT(range(1)), TEXTAREA() )
    +>>> for c in a.elements('input, select, textarea'): c['_disabled'] = 'disabled'
    +>>> a.xml()
    +'<form action="" enctype="multipart/form-data" method="post"><input disabled="disabled" type="text" /><select disabled="disabled"><option value="0">0</option></select><textarea cols="40" disabled="disabled" rows="10"></textarea></form>'
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    element(self, + *args, + **kargs) +

    +
    source code  +
    + +

    find the first component that matches the supplied attribute + dictionary, or None if nothing could be found

    + Also the components of the components are searched. +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.EM-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.EM-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.EM-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.EM-class.html @@ -0,0 +1,246 @@ + + + + + web2py.gluon.html.EM + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class EM + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class EM

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  EM
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update, + xml +

    +

    Inherited from DIV (private): + _fixup, + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'em' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.EMBED-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.EMBED-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.EMBED-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.EMBED-class.html @@ -0,0 +1,246 @@ + + + + + web2py.gluon.html.EMBED + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class EMBED + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class EMBED

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  EMBED
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update, + xml +

    +

    Inherited from DIV (private): + _fixup, + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'embed/' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.FIELDSET-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.FIELDSET-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.FIELDSET-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.FIELDSET-class.html @@ -0,0 +1,246 @@ + + + + + web2py.gluon.html.FIELDSET + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class FIELDSET + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class FIELDSET

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  FIELDSET
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update, + xml +

    +

    Inherited from DIV (private): + _fixup, + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'fieldset' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.FORM-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.FORM-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.FORM-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.FORM-class.html @@ -0,0 +1,623 @@ + + + + + web2py.gluon.html.FORM + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class FORM + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class FORM

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  FORM
    +
    + +
    Known Subclasses:
    +
    + sqlhtml.SQLFORM +
    + +
    +example: +
    +   >>> from validators import IS_NOT_EMPTY
    +   >>> form=FORM(INPUT(_name="test", requires=IS_NOT_EMPTY()))
    +   >>> form.xml()
    +   '<form action="" enctype="multipart/form-data" method="post"><input name="test" type="text" /></form>'
    +
    +

    a FORM is container for INPUT, TEXTAREA, SELECT and other helpers

    + form has one important method: +
    +   form.accepts(request.vars, session)
    +
    + if form is accepted (and all validators pass) form.vars contains the + accepted vars, otherwise form.errors contains the errors. in case of + errors the form is modified to present the errors to the user.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + *components, + **attributes)
    + :param *components: any components that should be nested in this + element :param **attributes: any attributes you want to give to this + element
    + source code + +
    + +
    +   + + + + + + +
    _postprocessing(self)
    + Handling of attributes (normally the ones not prefixed with + '_').
    + source code + +
    + +
    +   + + + + + + +
    accepts(self, + vars, + session=1, + formname='default', + keepvalues=True, + onvalidation=1, + hideerror=True) + source code + +
    + +
    +   + + + + + + +
    hidden_fields(self) + source code + +
    + +
    +   + + + + + + +
    process(self, + values=1, + session=1, + **args)
    + Perform the .validate() method but returns the form + +Usage in controllers: +# directly on return +def action(): + #some code here + return dict(form=FORM(...).process(...)) + +You can use it with FORM, SQLFORM or FORM based plugins + +Examples: +#response.flash messages +def action(): + form = SQLFORM(db.table).process(message_onsuccess='Sucess!') + retutn dict(form=form) + +# callback function +# callback receives True or False as first arg, and a list of args.
    + source code + +
    + +
    +   + + + + + + +
    validate(self, + values=1, + session=1, + formname='default', + keepvalues=True, + onvalidation=1, + hideerror=True, + onsuccess='flash', + onfailure='flash', + message_onsuccess=1, + message_onfailure=1)
    + This function validates the form, +you can use it instead of directly form.accepts.
    + source code + +
    + +
    +   + + + + + + +
    xml(self)
    + generates the xml for this component.
    + source code + +
    + +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update +

    +

    Inherited from DIV (private): + _fixup, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'form' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + *components, + **attributes) +
    (Constructor) +

    +
    source code  +
    + +

    :param *components: any components that should be nested in this + element :param **attributes: any attributes you want to give to this + element

    + :raises SyntaxError: when a stand alone tag receives components +
    +
    Overrides: + DIV.__init__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    _postprocessing(self) +

    +
    source code  +
    + +

    Handling of attributes (normally the ones not prefixed with '_').

    + Nothing to postprocess yet. May be overridden by subclasses +
    +
    Overrides: + DIV._postprocessing +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    process(self, + values=1, + session=1, + **args) +

    +
    source code  +
    + +
    +
    +Perform the .validate() method but returns the form
    +
    +Usage in controllers:
    +# directly on return
    +def action():
    +    #some code here
    +    return dict(form=FORM(...).process(...))
    +
    +You can use it with FORM, SQLFORM or FORM based plugins
    +
    +Examples:
    +#response.flash messages
    +def action():
    +    form = SQLFORM(db.table).process(message_onsuccess='Sucess!')
    +    retutn dict(form=form)
    +
    +# callback function
    +# callback receives True or False as first arg, and a list of args.
    +def my_callback(status, msg):
    +   response.flash = "Success! "+msg if status else "Errors occured"
    +
    +# after argument can be 'flash' to response.flash messages
    +# or a function name to use as callback or None to do nothing.
    +def action():
    +    return dict(form=SQLFORM(db.table).process(onsuccess=my_callback)
    +
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    validate(self, + values=1, + session=1, + formname='default', + keepvalues=True, + onvalidation=1, + hideerror=True, + onsuccess='flash', + onfailure='flash', + message_onsuccess=1, + message_onfailure=1) +

    +
    source code  +
    + +
    +
    +This function validates the form, 
    +you can use it instead of directly form.accepts.
    +
    +Usage:
    +In controller
    +
    +def action():
    +    form=FORM(INPUT(_name="test", requires=IS_NOT_EMPTY()))
    +    form.validate() #you can pass some args here - see below
    +    return dict(form=form)
    +
    +This can receive a bunch of arguments        
    +
    +onsuccess = 'flash' - will show message_onsuccess in response.flash
    +            None - will do nothing
    +            can be a function (lambda form: pass)
    +onfailure = 'flash' - will show message_onfailure in response.flash
    +            None - will do nothing
    +            can be a function (lambda form: pass)
    +
    +values = values to test the validation - dictionary, response.vars, session or other - Default to (request.vars, session)
    +message_onsuccess
    +message_onfailure
    +
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    xml(self) +

    +
    source code  +
    + + generates the xml for this component. +
    +
    Overrides: + DIV.xml +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.H1-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.H1-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.H1-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.H1-class.html @@ -0,0 +1,246 @@ + + + + + web2py.gluon.html.H1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class H1 + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class H1

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  H1
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update, + xml +

    +

    Inherited from DIV (private): + _fixup, + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'h1' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.H2-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.H2-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.H2-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.H2-class.html @@ -0,0 +1,246 @@ + + + + + web2py.gluon.html.H2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class H2 + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class H2

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  H2
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update, + xml +

    +

    Inherited from DIV (private): + _fixup, + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'h2' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.H3-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.H3-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.H3-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.H3-class.html @@ -0,0 +1,246 @@ + + + + + web2py.gluon.html.H3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class H3 + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class H3

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  H3
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update, + xml +

    +

    Inherited from DIV (private): + _fixup, + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'h3' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.H4-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.H4-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.H4-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.H4-class.html @@ -0,0 +1,246 @@ + + + + + web2py.gluon.html.H4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class H4 + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class H4

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  H4
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update, + xml +

    +

    Inherited from DIV (private): + _fixup, + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'h4' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.H5-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.H5-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.H5-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.H5-class.html @@ -0,0 +1,246 @@ + + + + + web2py.gluon.html.H5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class H5 + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class H5

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  H5
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update, + xml +

    +

    Inherited from DIV (private): + _fixup, + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'h5' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.H6-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.H6-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.H6-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.H6-class.html @@ -0,0 +1,246 @@ + + + + + web2py.gluon.html.H6 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class H6 + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class H6

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  H6
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update, + xml +

    +

    Inherited from DIV (private): + _fixup, + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'h6' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.HEAD-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.HEAD-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.HEAD-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.HEAD-class.html @@ -0,0 +1,246 @@ + + + + + web2py.gluon.html.HEAD + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class HEAD + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class HEAD

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  HEAD
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update, + xml +

    +

    Inherited from DIV (private): + _fixup, + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'head' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.HR-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.HR-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.HR-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.HR-class.html @@ -0,0 +1,246 @@ + + + + + web2py.gluon.html.HR + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class HR + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class HR

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  HR
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update, + xml +

    +

    Inherited from DIV (private): + _fixup, + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'hr/' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.HTML-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.HTML-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.HTML-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.HTML-class.html @@ -0,0 +1,428 @@ + + + + + web2py.gluon.html.HTML + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class HTML + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class HTML

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  HTML
    +
    + +
    +

    There are four predefined document type definitions. They can be + specified in the 'doctype' parameter:

    +

    -'strict' enables strict doctype -'transitional' enables transitional + doctype (default) -'frameset' enables frameset doctype -'html5' enables + HTML 5 doctype -any other string will be treated as user's own + doctype

    +

    'lang' parameter specifies the language of the document. Defaults to + 'en'.

    + See also :class:`DIV`

    + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    xml(self)
    + generates the xml for this component.
    + source code + +
    + +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update +

    +

    Inherited from DIV (private): + _fixup, + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'html' +
    +   + + strict = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "h... +
    +   + + transitional = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 T... +
    +   + + frameset = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frame... +
    +   + + html5 = '<!DOCTYPE HTML>\n' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    xml(self) +

    +
    source code  +
    + + generates the xml for this component. +
    +
    Overrides: + DIV.xml +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + +
    + + + + + +
    Class Variable Details[hide private]
    +
    + +
    + +
    +

    strict

    + +
    +
    +
    +
    Value:
    +
    +'''<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.or\
    +g/TR/html4/strict.dtd">
    +'''
    +
    +
    +
    +
    +
    + +
    + +
    +

    transitional

    + +
    +
    +
    +
    Value:
    +
    +'''<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "htt\
    +p://www.w3.org/TR/html4/loose.dtd">
    +'''
    +
    +
    +
    +
    +
    + +
    + +
    +

    frameset

    + +
    +
    +
    +
    Value:
    +
    +'''<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://\
    +www.w3.org/TR/html4/frameset.dtd">
    +'''
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.I-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.I-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.I-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.I-class.html @@ -0,0 +1,246 @@ + + + + + web2py.gluon.html.I + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class I + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class I

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  I
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update, + xml +

    +

    Inherited from DIV (private): + _fixup, + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'i' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.IFRAME-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.IFRAME-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.IFRAME-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.IFRAME-class.html @@ -0,0 +1,246 @@ + + + + + web2py.gluon.html.IFRAME + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class IFRAME + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class IFRAME

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  IFRAME
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update, + xml +

    +

    Inherited from DIV (private): + _fixup, + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'iframe' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.IMG-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.IMG-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.IMG-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.IMG-class.html @@ -0,0 +1,246 @@ + + + + + web2py.gluon.html.IMG + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class IMG + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class IMG

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  IMG
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update, + xml +

    +

    Inherited from DIV (private): + _fixup, + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'img/' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.INPUT-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.INPUT-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.INPUT-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.INPUT-class.html @@ -0,0 +1,423 @@ + + + + + web2py.gluon.html.INPUT + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class INPUT + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class INPUT

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  INPUT
    +
    + +
    Known Subclasses:
    +
    + SELECT, + TEXTAREA +
    + +
    +
    +
    +INPUT Component
    +
    +examples::
    +
    +    >>> INPUT(_type='text', _name='name', value='Max').xml()
    +    '<input name="name" type="text" value="Max" />'
    +
    +    >>> INPUT(_type='checkbox', _name='checkbox', value='on').xml()
    +    '<input checked="checked" name="checkbox" type="checkbox" value="on" />'
    +
    +    >>> INPUT(_type='radio', _name='radio', _value='yes', value='yes').xml()
    +    '<input checked="checked" name="radio" type="radio" value="yes" />'
    +
    +    >>> INPUT(_type='radio', _name='radio', _value='no', value='yes').xml()
    +    '<input name="radio" type="radio" value="no" />'
    +
    +the input helper takes two special attributes value= and requires=.
    +
    +:param value: used to pass the initial value for the input field.
    +    value differs from _value because it works for checkboxes, radio,
    +    textarea and select/option too.
    +
    +    - for a checkbox value should be '' or 'on'.
    +    - for a radio or select/option value should be the _value
    +        of the checked/selected item.
    +
    +:param requires: should be None, or a validator or a list of validators
    +    for the value of the field.
    +
    +


    + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    _validate(self)
    + nothing to validate yet.
    + source code + +
    + +
    +   + + + + + + +
    _postprocessing(self)
    + Handling of attributes (normally the ones not prefixed with + '_').
    + source code + +
    + +
    +   + + + + + + +
    xml(self)
    + generates the xml for this component.
    + source code + +
    + +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update +

    +

    Inherited from DIV (private): + _fixup, + _setnode, + _traverse, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'input/' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    _validate(self) +

    +
    source code  +
    + + nothing to validate yet. May be overridden by subclasses +
    +
    Overrides: + DIV._validate +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    _postprocessing(self) +

    +
    source code  +
    + +

    Handling of attributes (normally the ones not prefixed with '_').

    + Nothing to postprocess yet. May be overridden by subclasses +
    +
    Overrides: + DIV._postprocessing +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    xml(self) +

    +
    source code  +
    + + generates the xml for this component. +
    +
    Overrides: + DIV.xml +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.LABEL-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.LABEL-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.LABEL-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.LABEL-class.html @@ -0,0 +1,246 @@ + + + + + web2py.gluon.html.LABEL + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class LABEL + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class LABEL

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  LABEL
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update, + xml +

    +

    Inherited from DIV (private): + _fixup, + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'label' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.LEGEND-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.LEGEND-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.LEGEND-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.LEGEND-class.html @@ -0,0 +1,246 @@ + + + + + web2py.gluon.html.LEGEND + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class LEGEND + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class LEGEND

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  LEGEND
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update, + xml +

    +

    Inherited from DIV (private): + _fixup, + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'legend' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.LI-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.LI-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.LI-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.LI-class.html @@ -0,0 +1,246 @@ + + + + + web2py.gluon.html.LI + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class LI + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class LI

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  LI
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update, + xml +

    +

    Inherited from DIV (private): + _fixup, + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'li' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.LINK-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.LINK-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.LINK-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.LINK-class.html @@ -0,0 +1,246 @@ + + + + + web2py.gluon.html.LINK + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class LINK + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class LINK

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  LINK
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update, + xml +

    +

    Inherited from DIV (private): + _fixup, + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'link/' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.MARKMIN-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.MARKMIN-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.MARKMIN-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.MARKMIN-class.html @@ -0,0 +1,376 @@ + + + + + web2py.gluon.html.MARKMIN + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class MARKMIN + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class MARKMIN

    source code

    +
    +  object --+    
    +           |    
    +XmlComponent --+
    +               |
    +              MARKMIN
    +
    + +
    +For documentation: http://web2py.com/examples/static/markmin.html

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + text, + extra={}, + allowed={}, + sep='p')
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    xml(self)
    + calls the gluon.contrib.markmin render function to convert the + wiki syntax
    + source code + +
    + +
    +   + + + + + + +
    __str__(self)
    + str(x)
    + source code + +
    + +
    +   + + + + + + +
    flatten(self, + render=1)
    + return the text stored by the MARKMIN object rendered by the + render function
    + source code + +
    + +
    +   + + + + + + +
    elements(self, + *args, + **kargs)
    + to be considered experimental since the behavior of this method is + questionable another options could be + TAG(self.text).elements(*args,**kargs)
    + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + text, + extra={}, + allowed={}, + sep='p') +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    xml(self) +

    +
    source code  +
    + + calls the gluon.contrib.markmin render function to convert the wiki + syntax +
    +
    Overrides: + XmlComponent.xml +
    +
    +
    +
    + +
    + +
    + + +
    +

    __str__(self) +
    (Informal representation operator) +

    +
    source code  +
    + + str(x) +
    +
    Overrides: + object.__str__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.MENU-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.MENU-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.MENU-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.MENU-class.html @@ -0,0 +1,386 @@ + + + + + web2py.gluon.html.MENU + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class MENU + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class MENU

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  MENU
    +
    + +
    +
    +
    +Used to build menus
    +
    +Optional arguments
    +  _class: defaults to 'web2py-menu web2py-menu-vertical'
    +  ul_class: defaults to 'web2py-menu-vertical'
    +  li_class: defaults to 'web2py-menu-expand'
    +
    +Example:
    +    menu = MENU([['name', False, URL(...), [submenu]], ...])
    +    {{=menu}}
    +
    +


    + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + data, + **args)
    + :param *components: any components that should be nested in this + element :param **attributes: any attributes you want to give to this + element
    + source code + +
    + +
    +   + + + + + + +
    serialize(self, + data, + level=0) + source code + +
    + +
    +   + + + + + + +
    xml(self)
    + generates the xml for this component.
    + source code + +
    + +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update +

    +

    Inherited from DIV (private): + _fixup, + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'ul' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + data, + **args) +
    (Constructor) +

    +
    source code  +
    + +

    :param *components: any components that should be nested in this + element :param **attributes: any attributes you want to give to this + element

    + :raises SyntaxError: when a stand alone tag receives components +
    +
    Overrides: + DIV.__init__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    xml(self) +

    +
    source code  +
    + + generates the xml for this component. +
    +
    Overrides: + DIV.xml +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.META-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.META-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.META-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.META-class.html @@ -0,0 +1,246 @@ + + + + + web2py.gluon.html.META + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class META + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class META

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  META
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update, + xml +

    +

    Inherited from DIV (private): + _fixup, + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'meta/' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.OBJECT-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.OBJECT-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.OBJECT-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.OBJECT-class.html @@ -0,0 +1,246 @@ + + + + + web2py.gluon.html.OBJECT + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class OBJECT + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class OBJECT

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  OBJECT
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update, + xml +

    +

    Inherited from DIV (private): + _fixup, + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'object' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.OL-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.OL-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.OL-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.OL-class.html @@ -0,0 +1,250 @@ + + + + + web2py.gluon.html.OL + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class OL + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class OL

    source code

    +
    +  object --+            
    +           |            
    +XmlComponent --+        
    +               |        
    +             DIV --+    
    +                   |    
    +                  UL --+
    +                       |
    +                      OL
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from UL (private): + _fixup +

    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update, + xml +

    +

    Inherited from DIV (private): + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'ol' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.OPTGROUP-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.OPTGROUP-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.OPTGROUP-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.OPTGROUP-class.html @@ -0,0 +1,306 @@ + + + + + web2py.gluon.html.OPTGROUP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class OPTGROUP + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class OPTGROUP

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  OPTGROUP
    +
    + +
    + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    _fixup(self)
    + Handling of provided components.
    + source code + +
    + +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update, + xml +

    +

    Inherited from DIV (private): + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'optgroup' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    _fixup(self) +

    +
    source code  +
    + +

    Handling of provided components.

    + Nothing to fixup yet. May be overridden by subclasses, eg for wrapping + some components in another component or blocking them. +
    +
    Overrides: + DIV._fixup +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.OPTION-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.OPTION-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.OPTION-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.OPTION-class.html @@ -0,0 +1,306 @@ + + + + + web2py.gluon.html.OPTION + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class OPTION + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class OPTION

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  OPTION
    +
    + +
    + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    _fixup(self)
    + Handling of provided components.
    + source code + +
    + +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update, + xml +

    +

    Inherited from DIV (private): + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'option' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    _fixup(self) +

    +
    source code  +
    + +

    Handling of provided components.

    + Nothing to fixup yet. May be overridden by subclasses, eg for wrapping + some components in another component or blocking them. +
    +
    Overrides: + DIV._fixup +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.P-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.P-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.P-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.P-class.html @@ -0,0 +1,308 @@ + + + + + web2py.gluon.html.P + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class P + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class P

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  P
    +
    + +
    +

    Will replace ``\n`` by ``<br />`` if the `cr2br` attribute is + provided.

    + see also :class:`DIV`

    + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    xml(self)
    + generates the xml for this component.
    + source code + +
    + +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update +

    +

    Inherited from DIV (private): + _fixup, + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'p' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    xml(self) +

    +
    source code  +
    + + generates the xml for this component. +
    +
    Overrides: + DIV.xml +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.PRE-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.PRE-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.PRE-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.PRE-class.html @@ -0,0 +1,246 @@ + + + + + web2py.gluon.html.PRE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class PRE + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class PRE

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  PRE
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update, + xml +

    +

    Inherited from DIV (private): + _fixup, + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'pre' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.SCRIPT-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.SCRIPT-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.SCRIPT-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.SCRIPT-class.html @@ -0,0 +1,304 @@ + + + + + web2py.gluon.html.SCRIPT + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class SCRIPT + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class SCRIPT

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  SCRIPT
    +
    + +
    + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    xml(self)
    + generates the xml for this component.
    + source code + +
    + +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update +

    +

    Inherited from DIV (private): + _fixup, + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'script' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    xml(self) +

    +
    source code  +
    + + generates the xml for this component. +
    +
    Overrides: + DIV.xml +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.SELECT-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.SELECT-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.SELECT-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.SELECT-class.html @@ -0,0 +1,360 @@ + + + + + web2py.gluon.html.SELECT + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class SELECT + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class SELECT

    source code

    +
    +  object --+            
    +           |            
    +XmlComponent --+        
    +               |        
    +             DIV --+    
    +                   |    
    +               INPUT --+
    +                       |
    +                      SELECT
    +
    + +
    +example: +
    +   >>> from validators import IS_IN_SET
    +   >>> SELECT('yes', 'no', _name='selector', value='yes',
    +   ...    requires=IS_IN_SET(['yes', 'no'])).xml()
    +   '<select name="selector"><option selected="selected" value="yes">yes</option><option value="no">no</option></select>'
    +


    + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    _fixup(self)
    + Handling of provided components.
    + source code + +
    + +
    +   + + + + + + +
    _postprocessing(self)
    + Handling of attributes (normally the ones not prefixed with + '_').
    + source code + +
    + +
    +

    Inherited from INPUT: + xml +

    +

    Inherited from INPUT (private): + _validate +

    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update +

    +

    Inherited from DIV (private): + _setnode, + _traverse, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'select' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    _fixup(self) +

    +
    source code  +
    + +

    Handling of provided components.

    + Nothing to fixup yet. May be overridden by subclasses, eg for wrapping + some components in another component or blocking them. +
    +
    Overrides: + DIV._fixup +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    _postprocessing(self) +

    +
    source code  +
    + +

    Handling of attributes (normally the ones not prefixed with '_').

    + Nothing to postprocess yet. May be overridden by subclasses +
    +
    Overrides: + INPUT._postprocessing +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.SPAN-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.SPAN-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.SPAN-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.SPAN-class.html @@ -0,0 +1,246 @@ + + + + + web2py.gluon.html.SPAN + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class SPAN + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class SPAN

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  SPAN
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update, + xml +

    +

    Inherited from DIV (private): + _fixup, + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'span' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.STYLE-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.STYLE-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.STYLE-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.STYLE-class.html @@ -0,0 +1,304 @@ + + + + + web2py.gluon.html.STYLE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class STYLE + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class STYLE

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  STYLE
    +
    + +
    + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    xml(self)
    + generates the xml for this component.
    + source code + +
    + +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update +

    +

    Inherited from DIV (private): + _fixup, + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'style' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    xml(self) +

    +
    source code  +
    + + generates the xml for this component. +
    +
    Overrides: + DIV.xml +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.TABLE-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.TABLE-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.TABLE-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.TABLE-class.html @@ -0,0 +1,316 @@ + + + + + web2py.gluon.html.TABLE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class TABLE + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class TABLE

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  TABLE
    +
    + +
    Known Subclasses:
    +
    + sqlhtml.SQLTABLE +
    + +
    +

    TABLE Component.

    +

    If subcomponents are not TR/TBODY/THEAD/TFOOT-components they will be + wrapped in a TR

    + see also :class:`DIV`

    + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    _fixup(self)
    + Handling of provided components.
    + source code + +
    + +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update, + xml +

    +

    Inherited from DIV (private): + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'table' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    _fixup(self) +

    +
    source code  +
    + +

    Handling of provided components.

    + Nothing to fixup yet. May be overridden by subclasses, eg for wrapping + some components in another component or blocking them. +
    +
    Overrides: + DIV._fixup +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.TBODY-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.TBODY-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.TBODY-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.TBODY-class.html @@ -0,0 +1,306 @@ + + + + + web2py.gluon.html.TBODY + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class TBODY + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class TBODY

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  TBODY
    +
    + +
    + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    _fixup(self)
    + Handling of provided components.
    + source code + +
    + +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update, + xml +

    +

    Inherited from DIV (private): + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'tbody' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    _fixup(self) +

    +
    source code  +
    + +

    Handling of provided components.

    + Nothing to fixup yet. May be overridden by subclasses, eg for wrapping + some components in another component or blocking them. +
    +
    Overrides: + DIV._fixup +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.TD-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.TD-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.TD-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.TD-class.html @@ -0,0 +1,246 @@ + + + + + web2py.gluon.html.TD + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class TD + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class TD

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  TD
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update, + xml +

    +

    Inherited from DIV (private): + _fixup, + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'td' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.TEXTAREA-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.TEXTAREA-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.TEXTAREA-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.TEXTAREA-class.html @@ -0,0 +1,317 @@ + + + + + web2py.gluon.html.TEXTAREA + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class TEXTAREA + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class TEXTAREA

    source code

    +
    +  object --+            
    +           |            
    +XmlComponent --+        
    +               |        
    +             DIV --+    
    +                   |    
    +               INPUT --+
    +                       |
    +                      TEXTAREA
    +
    + +
    +example: +
    +   TEXTAREA(_name='sometext', value='blah '*100, requires=IS_NOT_EMPTY())
    +
    + 'blah blah blah ...' will be the content of the textarea field.

    + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    _postprocessing(self)
    + Handling of attributes (normally the ones not prefixed with + '_').
    + source code + +
    + +
    +

    Inherited from INPUT: + xml +

    +

    Inherited from INPUT (private): + _validate +

    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update +

    +

    Inherited from DIV (private): + _fixup, + _setnode, + _traverse, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'textarea' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    _postprocessing(self) +

    +
    source code  +
    + +

    Handling of attributes (normally the ones not prefixed with '_').

    + Nothing to postprocess yet. May be overridden by subclasses +
    +
    Overrides: + INPUT._postprocessing +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.TFOOT-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.TFOOT-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.TFOOT-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.TFOOT-class.html @@ -0,0 +1,306 @@ + + + + + web2py.gluon.html.TFOOT + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class TFOOT + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class TFOOT

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  TFOOT
    +
    + +
    + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    _fixup(self)
    + Handling of provided components.
    + source code + +
    + +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update, + xml +

    +

    Inherited from DIV (private): + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'tfoot' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    _fixup(self) +

    +
    source code  +
    + +

    Handling of provided components.

    + Nothing to fixup yet. May be overridden by subclasses, eg for wrapping + some components in another component or blocking them. +
    +
    Overrides: + DIV._fixup +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.TH-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.TH-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.TH-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.TH-class.html @@ -0,0 +1,246 @@ + + + + + web2py.gluon.html.TH + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class TH + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class TH

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  TH
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update, + xml +

    +

    Inherited from DIV (private): + _fixup, + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'th' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.THEAD-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.THEAD-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.THEAD-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.THEAD-class.html @@ -0,0 +1,306 @@ + + + + + web2py.gluon.html.THEAD + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class THEAD + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class THEAD

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  THEAD
    +
    + +
    + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    _fixup(self)
    + Handling of provided components.
    + source code + +
    + +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update, + xml +

    +

    Inherited from DIV (private): + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'thead' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    _fixup(self) +

    +
    source code  +
    + +

    Handling of provided components.

    + Nothing to fixup yet. May be overridden by subclasses, eg for wrapping + some components in another component or blocking them. +
    +
    Overrides: + DIV._fixup +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.TITLE-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.TITLE-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.TITLE-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.TITLE-class.html @@ -0,0 +1,246 @@ + + + + + web2py.gluon.html.TITLE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class TITLE + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class TITLE

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  TITLE
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update, + xml +

    +

    Inherited from DIV (private): + _fixup, + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'title' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.TR-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.TR-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.TR-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.TR-class.html @@ -0,0 +1,311 @@ + + + + + web2py.gluon.html.TR + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class TR + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class TR

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  TR
    +
    + +
    +

    TR Component.

    +

    If subcomponents are not TD/TH-components they will be wrapped in a + TD

    + see also :class:`DIV`

    + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    _fixup(self)
    + Handling of provided components.
    + source code + +
    + +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update, + xml +

    +

    Inherited from DIV (private): + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'tr' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    _fixup(self) +

    +
    source code  +
    + +

    Handling of provided components.

    + Nothing to fixup yet. May be overridden by subclasses, eg for wrapping + some components in another component or blocking them. +
    +
    Overrides: + DIV._fixup +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.TT-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.TT-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.TT-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.TT-class.html @@ -0,0 +1,246 @@ + + + + + web2py.gluon.html.TT + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class TT + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class TT

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  TT
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update, + xml +

    +

    Inherited from DIV (private): + _fixup, + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'tt' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.UL-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.UL-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.UL-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.UL-class.html @@ -0,0 +1,316 @@ + + + + + web2py.gluon.html.UL + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class UL + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class UL

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  UL
    +
    + +
    Known Subclasses:
    +
    + OL +
    + +
    +

    UL Component.

    +

    If subcomponents are not LI-components they will be wrapped in a + LI

    + see also :class:`DIV`

    + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    _fixup(self)
    + Handling of provided components.
    + source code + +
    + +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update, + xml +

    +

    Inherited from DIV (private): + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'ul' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    _fixup(self) +

    +
    source code  +
    + +

    Handling of provided components.

    + Nothing to fixup yet. May be overridden by subclasses, eg for wrapping + some components in another component or blocking them. +
    +
    Overrides: + DIV._fixup +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.XHTML-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.XHTML-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.XHTML-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.XHTML-class.html @@ -0,0 +1,429 @@ + + + + + web2py.gluon.html.XHTML + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class XHTML + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class XHTML

    source code

    +
    +  object --+        
    +           |        
    +XmlComponent --+    
    +               |    
    +             DIV --+
    +                   |
    +                  XHTML
    +
    + +
    +

    This is XHTML version of the HTML helper.

    +

    There are three predefined document type definitions. They can be + specified in the 'doctype' parameter:

    +

    -'strict' enables strict doctype -'transitional' enables transitional + doctype (default) -'frameset' enables frameset doctype -any other string + will be treated as user's own doctype

    +

    'lang' parameter specifies the language of the document and the xml + document. Defaults to 'en'.

    +

    'xmlns' parameter specifies the xml namespace. Defaults to + 'http://www.w3.org/1999/xhtml'.

    + See also :class:`DIV`

    + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    xml(self)
    + generates the xml for this component.
    + source code + +
    + +
    +

    Inherited from DIV: + __delitem__, + __getitem__, + __init__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update +

    +

    Inherited from DIV (private): + _fixup, + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + tag = 'html' +
    +   + + strict = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict/... +
    +   + + transitional = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 T... +
    +   + + frameset = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frame... +
    +   + + xmlns = 'http://www.w3.org/1999/xhtml' +
    +

    Inherited from DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    xml(self) +

    +
    source code  +
    + + generates the xml for this component. +
    +
    Overrides: + DIV.xml +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + +
    + + + + + +
    Class Variable Details[hide private]
    +
    + +
    + +
    +

    strict

    + +
    +
    +
    +
    Value:
    +
    +'''<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://ww\
    +w.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    +'''
    +
    +
    +
    +
    +
    + +
    + +
    +

    transitional

    + +
    +
    +
    +
    Value:
    +
    +'''<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "htt\
    +p://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    +'''
    +
    +
    +
    +
    +
    + +
    + +
    +

    frameset

    + +
    +
    +
    +
    Value:
    +
    +'''<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://\
    +www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
    +'''
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.XML-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.XML-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.XML-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.XML-class.html @@ -0,0 +1,567 @@ + + + + + web2py.gluon.html.XML + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class XML + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class XML

    source code

    +
    +  object --+    
    +           |    
    +XmlComponent --+
    +               |
    +              XML
    +
    + +
    +

    use it to wrap a string that contains XML/HTML so that it will not be + escaped by the template

    + example: +
    +>>> XML('<h1>Hello</h1>').xml()
    +'<h1>Hello</h1>'


    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + text, + sanitize=True, + permitted_tags=['a', 'b', 'blockquote', 'br/', 'i', 'li', 'ol', 'ul', 'p', 'c..., + allowed_attributes={'a': ['href', 'title'], 'blockquote': ['type'], 'img': ['src'...)
    + :param text: the XML text +:param sanitize: sanitize text using the permitted tags and allowed + attributes (default False) +:param permitted_tags: list of permitted tags (default: simple list of + tags) +:param allowed_attributes: dictionary of allowed attributed (default + for A, IMG and BlockQuote).
    + source code + +
    + +
    +   + + + + + + +
    xml(self) + source code + +
    + +
    +   + + + + + + +
    __str__(self)
    + str(x)
    + source code + +
    + +
    +   + + + + + + +
    __add__(self, + other) + source code + +
    + +
    +   + + + + + + +
    __radd__(self, + other) + source code + +
    + +
    +   + + + + + + +
    __cmp__(self, + other) + source code + +
    + +
    +   + + + + + + +
    __hash__(self)
    + hash(x)
    + source code + +
    + +
    +   + + + + + + +
    __getattr__(self, + name) + source code + +
    + +
    +   + + + + + + +
    __getitem__(self, + i) + source code + +
    + +
    +   + + + + + + +
    __getslice__(self, + i, + j) + source code + +
    + +
    +   + + + + + + +
    __iter__(self) + source code + +
    + +
    +   + + + + + + +
    __len__(self) + source code + +
    + +
    +   + + + + + + +
    flatten(self, + render=1)
    + return the text stored by the XML object rendered by the render + function
    + source code + +
    + +
    +   + + + + + + +
    elements(self, + *args, + **kargs)
    + to be considered experimental since the behavior of this method is + questionable another options could be + TAG(self.text).elements(*args,**kargs)
    + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + text, + sanitize=True, + permitted_tags=['a', 'b', 'blockquote', 'br/', 'i', 'li', 'ol', 'ul', 'p', 'c..., + allowed_attributes={'a': ['href', 'title'], 'blockquote': ['type'], 'img': ['src'...) +
    (Constructor) +

    +
    source code  +
    + +
    +
    +:param text: the XML text
    +:param sanitize: sanitize text using the permitted tags and allowed
    +    attributes (default False)
    +:param permitted_tags: list of permitted tags (default: simple list of
    +    tags)
    +:param allowed_attributes: dictionary of allowed attributed (default
    +    for A, IMG and BlockQuote).
    +    The key is the tag; the value is a list of allowed attributes.
    +
    +
    +
    +
    Overrides: + object.__init__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    xml(self) +

    +
    source code  +
    + + +
    +
    Overrides: + XmlComponent.xml +
    +
    +
    +
    + +
    + +
    + + +
    +

    __str__(self) +
    (Informal representation operator) +

    +
    source code  +
    + + str(x) +
    +
    Overrides: + object.__str__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    __hash__(self) +
    (Hashing function) +

    +
    source code  +
    + + hash(x) +
    +
    Overrides: + object.__hash__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.XmlComponent-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.XmlComponent-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.XmlComponent-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.XmlComponent-class.html @@ -0,0 +1,208 @@ + + + + + web2py.gluon.html.XmlComponent + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class XmlComponent + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class XmlComponent

    source code

    +
    +object --+
    +         |
    +        XmlComponent
    +
    + +
    Known Subclasses:
    +
    + DIV, + MARKMIN, + XML, + __TAG__ +
    + +
    +Abstract root for all Html components

    + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    xml(self) + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __init__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.__TAG__-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.__TAG__-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.__TAG__-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.__TAG__-class.html @@ -0,0 +1,244 @@ + + + + + web2py.gluon.html.__TAG__ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class __TAG__ + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class __TAG__

    source code

    +
    +  object --+    
    +           |    
    +XmlComponent --+
    +               |
    +              __TAG__
    +
    + +
    +TAG factory example: +
    +   >>> print TAG.first(TAG.second('test'), _key = 3)
    +   <first key="3"><second>test</second></first>
    +


    + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __getitem__(self, + name) + source code + +
    + +
    +   + + + + + + +
    __getattr__(self, + name) + source code + +
    + +
    +   + + + + + + +
    __call__(self, + html) + source code + +
    + +
    +

    Inherited from XmlComponent: + xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __init__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.html.web2pyHTMLParser-class.html Index: applications/examples/static/epydoc/web2py.gluon.html.web2pyHTMLParser-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.html.web2pyHTMLParser-class.html +++ applications/examples/static/epydoc/web2py.gluon.html.web2pyHTMLParser-class.html @@ -0,0 +1,489 @@ + + + + + web2py.gluon.html.web2pyHTMLParser + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module html :: + Class web2pyHTMLParser + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class web2pyHTMLParser

    source code

    +
    +markupbase.ParserBase --+    
    +                        |    
    +    HTMLParser.HTMLParser --+
    +                            |
    +                           web2pyHTMLParser
    +
    + +
    +obj = web2pyHTMLParser(text) parses and html/xml text into web2py + helpers. obj.tree contains the root of the tree, and tree can be + manipulated +
    +>>> str(web2pyHTMLParser('hello<div a="b" c=3>wor&lt;ld<span>xxx</span>y<script/>yy</div>zzz').tree)
    +'hello<div a="b" c="3">wor&lt;ld<span>xxx</span>y<script></script>yy</div>zzz'
    +>>> str(web2pyHTMLParser('<div>a<span>b</div>c').tree)
    +'<div>a<span>b</span></div>c'
    +>>> tree = web2pyHTMLParser('hello<div a="b">world</div>').tree
    +>>> tree.element(_a='b')['_c']=5
    +>>> str(tree)
    +'hello<div a="b" c="5">world</div>'


    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + text, + closed=('input', 'link'))
    + Initialize and reset this instance.
    + source code + +
    + +
    +   + + + + + + +
    handle_starttag(self, + tagname, + attrs) + source code + +
    + +
    +   + + + + + + +
    handle_data(self, + data) + source code + +
    + +
    +   + + + + + + +
    handle_charref(self, + name) + source code + +
    + +
    +   + + + + + + +
    handle_entityref(self, + name) + source code + +
    + +
    +   + + + + + + +
    handle_endtag(self, + tagname) + source code + +
    + +
    +

    Inherited from HTMLParser.HTMLParser: + check_for_whole_start_tag, + clear_cdata_mode, + close, + error, + feed, + get_starttag_text, + goahead, + handle_comment, + handle_decl, + handle_pi, + handle_startendtag, + parse_endtag, + parse_pi, + parse_starttag, + reset, + set_cdata_mode, + unescape, + unknown_decl +

    +

    Inherited from markupbase.ParserBase: + getpos, + parse_comment, + parse_declaration, + parse_marked_section, + updatepos +

    +

    Inherited from markupbase.ParserBase (private): + _parse_doctype_attlist, + _parse_doctype_element, + _parse_doctype_entity, + _parse_doctype_notation, + _parse_doctype_subset, + _scan_name +

    +
    + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +

    Inherited from HTMLParser.HTMLParser: + CDATA_CONTENT_ELEMENTS +

    +

    Inherited from markupbase.ParserBase (private): + _decl_otherchars +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + text, + closed=('input', 'link')) +
    (Constructor) +

    +
    source code  +
    + + Initialize and reset this instance. +
    +
    Overrides: + HTMLParser.HTMLParser.__init__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    handle_starttag(self, + tagname, + attrs) +

    +
    source code  +
    + + +
    +
    Overrides: + HTMLParser.HTMLParser.handle_starttag +
    +
    +
    +
    + +
    + +
    + + +
    +

    handle_data(self, + data) +

    +
    source code  +
    + + +
    +
    Overrides: + HTMLParser.HTMLParser.handle_data +
    +
    +
    +
    + +
    + +
    + + +
    +

    handle_charref(self, + name) +

    +
    source code  +
    + + +
    +
    Overrides: + HTMLParser.HTMLParser.handle_charref +
    +
    +
    +
    + +
    + +
    + + +
    +

    handle_entityref(self, + name) +

    +
    source code  +
    + + +
    +
    Overrides: + HTMLParser.HTMLParser.handle_entityref +
    +
    +
    +
    + +
    + +
    + + +
    +

    handle_endtag(self, + tagname) +

    +
    source code  +
    + + +
    +
    Overrides: + HTMLParser.HTMLParser.handle_endtag +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.http-module.html Index: applications/examples/static/epydoc/web2py.gluon.http-module.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.http-module.html +++ applications/examples/static/epydoc/web2py.gluon.http-module.html @@ -0,0 +1,265 @@ + + + + + web2py.gluon.http + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module http + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Module http

    source code

    +This file is part of the web2py Web Framework Copyrighted by Massimo + Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)

    + + + + + + + + + + + + + +
    + + + + + +
    Classes[hide private]
    +
    +   + + BaseException
    + Common base class for all non-exit exceptions. +
    +   + + HTTP +
    + + + + + + + + + +
    + + + + + +
    Functions[hide private]
    +
    +   + + + + + + +
    redirect(location, + how=303) + source code + +
    + +
    + + + + + + + + + +
    + + + + + +
    Variables[hide private]
    +
    +   + + defined_status = {200: 'OK', 201: 'CREATED', 202: 'ACCEPTED', ... +
    + + + + + + +
    + + + + + +
    Variables Details[hide private]
    +
    + +
    + +
    +

    defined_status

    + +
    +
    +
    +
    Value:
    +
    +{200: 'OK',
    + 201: 'CREATED',
    + 202: 'ACCEPTED',
    + 203: 'NON-AUTHORITATIVE INFORMATION',
    + 204: 'NO CONTENT',
    + 205: 'RESET CONTENT',
    + 206: 'PARTIAL CONTENT',
    + 301: 'MOVED PERMANENTLY',
    +...
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.http-pysrc.html Index: applications/examples/static/epydoc/web2py.gluon.http-pysrc.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.http-pysrc.html +++ applications/examples/static/epydoc/web2py.gluon.http-pysrc.html @@ -0,0 +1,253 @@ + + + + + web2py.gluon.http + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module http + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Module web2py.gluon.http

    +
    +  1  #!/usr/bin/env python 
    +  2  # -*- coding: utf-8 -*- 
    +  3   
    +  4  """ 
    +  5  This file is part of the web2py Web Framework 
    +  6  Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> 
    +  7  License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) 
    +  8  """ 
    +  9   
    + 10  __all__ = ['HTTP', 'redirect'] 
    + 11   
    + 12  defined_status = { 
    + 13      200: 'OK', 
    + 14      201: 'CREATED', 
    + 15      202: 'ACCEPTED', 
    + 16      203: 'NON-AUTHORITATIVE INFORMATION', 
    + 17      204: 'NO CONTENT', 
    + 18      205: 'RESET CONTENT', 
    + 19      206: 'PARTIAL CONTENT', 
    + 20      301: 'MOVED PERMANENTLY', 
    + 21      302: 'FOUND', 
    + 22      303: 'SEE OTHER', 
    + 23      304: 'NOT MODIFIED', 
    + 24      305: 'USE PROXY', 
    + 25      307: 'TEMPORARY REDIRECT', 
    + 26      400: 'BAD REQUEST', 
    + 27      401: 'UNAUTHORIZED', 
    + 28      403: 'FORBIDDEN', 
    + 29      404: 'NOT FOUND', 
    + 30      405: 'METHOD NOT ALLOWED', 
    + 31      406: 'NOT ACCEPTABLE', 
    + 32      407: 'PROXY AUTHENTICATION REQUIRED', 
    + 33      408: 'REQUEST TIMEOUT', 
    + 34      409: 'CONFLICT', 
    + 35      410: 'GONE', 
    + 36      411: 'LENGTH REQUIRED', 
    + 37      412: 'PRECONDITION FAILED', 
    + 38      413: 'REQUEST ENTITY TOO LARGE', 
    + 39      414: 'REQUEST-URI TOO LONG', 
    + 40      415: 'UNSUPPORTED MEDIA TYPE', 
    + 41      416: 'REQUESTED RANGE NOT SATISFIABLE', 
    + 42      417: 'EXPECTATION FAILED', 
    + 43      500: 'INTERNAL SERVER ERROR', 
    + 44      501: 'NOT IMPLEMENTED', 
    + 45      502: 'BAD GATEWAY', 
    + 46      503: 'SERVICE UNAVAILABLE', 
    + 47      504: 'GATEWAY TIMEOUT', 
    + 48      505: 'HTTP VERSION NOT SUPPORTED', 
    + 49      } 
    + 50   
    + 51  # If web2py is executed with python2.4 we need 
    + 52  # to use Exception instead of BaseException 
    + 53   
    + 54  try: 
    + 55      BaseException 
    + 56  except NameError: 
    + 57      BaseException = Exception 
    + 58   
    + 59   
    +
    60 -class HTTP(BaseException): +
    61 +
    62 - def __init__( + 63 self, + 64 status, + 65 body='', + 66 **headers + 67 ): +
    68 self.status = status + 69 self.body = body + 70 self.headers = headers +
    71 +
    72 - def to(self, responder): +
    73 if self.status in defined_status: + 74 status = '%d %s' % (self.status, defined_status[self.status]) + 75 else: + 76 status = str(self.status) + ' ' + 77 if not 'Content-Type' in self.headers: + 78 self.headers['Content-Type'] = 'text/html; charset=UTF-8' + 79 body = self.body + 80 if status[:1] == '4': + 81 if not body: + 82 body = status + 83 if isinstance(body, str): + 84 if len(body)<512 and self.headers['Content-Type'].startswith('text/html'): + 85 body += '<!-- %s //-->' % ('x'*512) ### trick IE + 86 self.headers['Content-Length'] = len(body) + 87 headers = [] + 88 for (k, v) in self.headers.items(): + 89 if isinstance(v, list): + 90 for item in v: + 91 headers.append((k, str(item))) + 92 else: + 93 headers.append((k, str(v))) + 94 responder(status, headers) + 95 if hasattr(body, '__iter__') and not isinstance(self.body, str): + 96 return body + 97 return [str(body)] +
    98 + 99 @property +
    100 - def message(self): +
    101 ''' +102 compose a message describing this exception +103 +104 "status defined_status [web2py_error]" +105 +106 message elements that are not defined are omitted +107 ''' +108 msg = '%(status)d' +109 if self.status in defined_status: +110 msg = '%(status)d %(defined_status)s' +111 if 'web2py_error' in self.headers: +112 msg += ' [%(web2py_error)s]' +113 return msg % dict(status=self.status, +114 defined_status=defined_status.get(self.status), +115 web2py_error=self.headers.get('web2py_error')) +
    116 +
    117 - def __str__(self): +
    118 "stringify me" +119 return self.message +
    120 +121 +
    122 -def redirect(location, how=303): +
    123 location = location.replace('\r', '%0D').replace('\n', '%0A') +124 raise HTTP(how, +125 'You are being redirected <a href="%s">here</a>' % location, +126 Location=location) +
    127 +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.http.HTTP-class.html Index: applications/examples/static/epydoc/web2py.gluon.http.HTTP-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.http.HTTP-class.html +++ applications/examples/static/epydoc/web2py.gluon.http.HTTP-class.html @@ -0,0 +1,373 @@ + + + + + web2py.gluon.http.HTTP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module http :: + Class HTTP + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class HTTP

    source code

    +
    +              object --+    
    +                       |    
    +exceptions.BaseException --+
    +                           |
    +                          HTTP
    +
    + +
    + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + status, + body='', + **headers)
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    to(self, + responder) + source code + +
    + +
    +   + + + + + + +
    __str__(self)
    + stringify me
    + source code + +
    + +
    +

    Inherited from exceptions.BaseException: + __delattr__, + __getattribute__, + __getitem__, + __getslice__, + __new__, + __reduce__, + __repr__, + __setattr__, + __setstate__ +

    +

    Inherited from object: + __hash__, + __reduce_ex__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +   + + message
    + compose a message describing this exception +
    +

    Inherited from exceptions.BaseException: + args +

    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + status, + body='', + **headers) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + exceptions.BaseException.__init__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    __str__(self) +
    (Informal representation operator) +

    +
    source code  +
    + + stringify me +
    +
    Overrides: + exceptions.BaseException.__str__ +
    +
    +
    +
    +
    + + + + + + +
    + + + + + +
    Property Details[hide private]
    +
    + +
    + +
    +

    message

    +

    compose a message describing this exception

    +

    "status defined_status [web2py_error]"

    + message elements that are not defined are omitted +
    +
    Get Method:
    +
    unreachable.message(self) + - compose a message describing this exception +
    +
    Set Method:
    +
    +1
    +
    + + - PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    +
    Delete Method:
    +
    +1
    +
    + + - PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.import_all-module.html Index: applications/examples/static/epydoc/web2py.gluon.import_all-module.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.import_all-module.html +++ applications/examples/static/epydoc/web2py.gluon.import_all-module.html @@ -0,0 +1,365 @@ + + + + + web2py.gluon.import_all + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module import_all + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Module import_all

    source code

    +

    This file is part of the web2py Web Framework Copyrighted by Massimo + Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)

    +

    This file is not strictly required by web2py. It is used for three + purposes:

    + 1) check that all required modules are installed properly 2) provide + py2exe and py2app a list of modules to be packaged in the binary 3) + (optional) preload modules in memory to speed up http responses

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Variables[hide private]
    +
    +   + + contributed_modules = ['gluon.myregex', 'gluon.contenttype', '... +
    +   + + python_version = '2.5' +
    +   + + alert_dependency = ['hashlib', 'uuid'] +
    +   + + py26_deprecated = ['mhlib', 'multifile', 'mimify', 'sets', 'Mi... +
    +   + + py27_deprecated = [] +
    +   + + base_modules = ['aifc', 'anydbm', 'array', 'asynchat', 'asynco... +
    +   + + candidate = 'gluon.contrib.pyrtf.Renderer' +
    +   + + dirs = [] +
    +   + + files = ['test_router.pyc', 'test_template.py', 'test_cache.py... +
    +   + + module = 'gluon.contrib.pyrtf.Renderer' +
    +   + + name = 'test_utils.py' +
    +   + + root = 'gluon/tests' +
    + + + + + + +
    + + + + + +
    Variables Details[hide private]
    +
    + +
    + +
    +

    contributed_modules

    + +
    +
    +
    +
    Value:
    +
    +['gluon.myregex',
    + 'gluon.contenttype',
    + 'gluon.http',
    + 'gluon.sanitizer',
    + 'gluon.cache',
    + 'gluon.serializers',
    + 'gluon.decoder',
    + 'gluon.main',
    +...
    +
    +
    +
    +
    +
    + +
    + +
    +

    py26_deprecated

    + +
    +
    +
    +
    Value:
    +
    +['mhlib', 'multifile', 'mimify', 'sets', 'MimeWriter']
    +
    +
    +
    +
    +
    + +
    + +
    +

    base_modules

    + +
    +
    +
    +
    Value:
    +
    +['aifc',
    + 'anydbm',
    + 'array',
    + 'asynchat',
    + 'asyncore',
    + 'atexit',
    + 'audioop',
    + 'base64',
    +...
    +
    +
    +
    +
    +
    + +
    + +
    +

    files

    + +
    +
    +
    +
    Value:
    +
    +['test_router.pyc',
    + 'test_template.py',
    + 'test_cache.pyc',
    + 'test_html.pyc',
    + 'test_template.pyc',
    + 'test_is_url.py',
    + 'test_cache.py',
    + 'test_utils.pyc',
    +...
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.import_all-pysrc.html Index: applications/examples/static/epydoc/web2py.gluon.import_all-pysrc.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.import_all-pysrc.html +++ applications/examples/static/epydoc/web2py.gluon.import_all-pysrc.html @@ -0,0 +1,233 @@ + + + + + web2py.gluon.import_all + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module import_all + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Module web2py.gluon.import_all

    +
    +  1  #!/usr/bin/env python 
    +  2   
    +  3  """ 
    +  4  This file is part of the web2py Web Framework 
    +  5  Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> 
    +  6  License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) 
    +  7   
    +  8  This file is not strictly required by web2py. It is used for three purposes: 
    +  9   
    + 10  1) check that all required modules are installed properly 
    + 11  2) provide py2exe and py2app a list of modules to be packaged in the binary 
    + 12  3) (optional) preload modules in memory to speed up http responses 
    + 13   
    + 14  """ 
    + 15   
    + 16  import os 
    + 17  import sys 
    + 18   
    + 19  base_modules = ['aifc', 'anydbm', 'array', 'asynchat', 'asyncore', 'atexit', 
    + 20                  'audioop', 'base64', 'BaseHTTPServer', 'Bastion', 'binascii', 
    + 21                  'binhex', 'bisect', 'bz2', 'calendar', 'cgi', 'CGIHTTPServer', 
    + 22                  'cgitb', 'chunk', 'cmath', 'cmd', 'code', 'codecs', 'codeop', 
    + 23                  'collections', 'colorsys', 'compileall', 'compiler', 
    + 24                  'compiler.ast', 'compiler.visitor', 'ConfigParser', 
    + 25                  'contextlib', 'Cookie', 'cookielib', 'copy', 'copy_reg', 
    + 26                  'cPickle', 'cProfile', 'cStringIO', 'csv', 'ctypes', 
    + 27                  'datetime', 'decimal', 'difflib', 'dircache', 'dis', 
    + 28                  'doctest', 'DocXMLRPCServer', 'dumbdbm', 'dummy_thread', 
    + 29                  'dummy_threading', 'email', 'email.charset', 'email.encoders', 
    + 30                  'email.errors', 'email.generator', 'email.header', 
    + 31                  'email.iterators', 'email.message', 'email.mime', 
    + 32                  'email.mime.audio', 'email.mime.base', 'email.mime.image', 
    + 33                  'email.mime.message', 'email.mime.multipart', 
    + 34                  'email.mime.nonmultipart', 'email.mime.text', 'email.parser', 
    + 35                  'email.utils', 'encodings.idna', 'errno', 'exceptions', 
    + 36                  'filecmp', 'fileinput', 'fnmatch', 'formatter', 'fpformat', 
    + 37                  'ftplib', 'functools', 'gc', 'getopt', 'getpass', 'gettext', 
    + 38                  'glob', 'gzip', 'hashlib', 'heapq', 'hmac', 'hotshot', 
    + 39                  'hotshot.stats', 'htmlentitydefs', 'htmllib', 'HTMLParser', 
    + 40                  'httplib', 'imaplib', 'imghdr', 'imp', 'inspect', 
    + 41                  'itertools', 'keyword', 'linecache', 'locale', 'logging', 
    + 42                  'macpath', 'mailbox', 'mailcap', 'marshal', 'math', 
    + 43                  'mimetools', 'mimetypes', 'mmap', 'modulefinder', 'mutex', 
    + 44                  'netrc', 'new', 'nntplib', 'operator', 'optparse', 'os', 
    + 45                  'parser', 'pdb', 'pickle', 'pickletools', 'pkgutil', 
    + 46                  'platform', 'poplib', 'pprint', 'py_compile', 'pyclbr', 
    + 47                  'pydoc', 'Queue', 'quopri', 'random', 're', 'repr', 
    + 48                  'rexec', 'rfc822', 'rlcompleter', 'robotparser', 'runpy', 
    + 49                  'sched', 'select', 'sgmllib', 'shelve', 
    + 50                  'shlex', 'shutil', 'signal', 'SimpleHTTPServer', 
    + 51                  'SimpleXMLRPCServer', 'site', 'smtpd', 'smtplib', 
    + 52                  'sndhdr', 'socket', 'SocketServer', 'sqlite3', 
    + 53                  'stat', 'statvfs', 'string', 'StringIO', 
    + 54                  'stringprep', 'struct', 'subprocess', 'sunau', 'symbol', 
    + 55                  'tabnanny', 'tarfile', 'telnetlib', 'tempfile', 'textwrap', 'thread', 'threading', 
    + 56                  'time', 'timeit', 'Tix', 'Tkinter', 'token', 
    + 57                  'tokenize', 'trace', 'traceback', 'types', 
    + 58                  'unicodedata', 'unittest', 'urllib', 'urllib2', 
    + 59                  'urlparse', 'user', 'UserDict', 'UserList', 'UserString', 
    + 60                  'uu', 'uuid', 'warnings', 'wave', 'weakref', 'webbrowser', 
    + 61                  'whichdb', 'wsgiref', 'wsgiref.handlers', 'wsgiref.headers', 
    + 62                  'wsgiref.simple_server', 'wsgiref.util', 'wsgiref.validate', 
    + 63                  'xdrlib', 'xml.dom', 'xml.dom.minidom', 'xml.dom.pulldom', 
    + 64                  'xml.etree.ElementTree', 'xml.parsers.expat', 'xml.sax', 
    + 65                  'xml.sax.handler', 'xml.sax.saxutils', 'xml.sax.xmlreader', 
    + 66                  'xmlrpclib', 'zipfile', 'zipimport', 'zlib', 'mhlib', 
    + 67                  'MimeWriter', 'mimify', 'multifile', 'sets'] 
    + 68   
    + 69  contributed_modules = [] 
    + 70  for root, dirs, files in os.walk('gluon'): 
    + 71      for candidate in ['.'.join( 
    + 72        os.path.join(root, os.path.splitext(name)[0]).split(os.sep)) 
    + 73        for name in files if name.endswith('.py') 
    + 74          and root.split(os.sep) != ['gluon', 'tests'] 
    + 75        ]: 
    + 76          contributed_modules.append(candidate) 
    + 77   
    + 78  # Python base version 
    + 79  python_version = sys.version[:3] 
    + 80   
    + 81  # Modules which we want to raise an Exception if they are missing 
    + 82  alert_dependency = ['hashlib', 'uuid'] 
    + 83   
    + 84  # Now we remove the blacklisted modules if we are using the stated 
    + 85  # python version. 
    + 86  # 
    + 87  # List of modules deprecated in Python 2.6 or 2.7 that are in the above set 
    + 88  py26_deprecated = ['mhlib', 'multifile', 'mimify', 'sets', 'MimeWriter'] 
    + 89  py27_deprecated = [] # ['optparse'] but we need it for now 
    + 90   
    + 91  if python_version >= '2.6': 
    + 92      base_modules += ['json', 'multiprocessing'] 
    + 93      base_modules = list(set(base_modules).difference(set(py26_deprecated))) 
    + 94   
    + 95  if python_version >= '2.7': 
    + 96      base_modules += ['argparse'] 
    + 97      base_modules = list(set(base_modules).difference(set(py27_deprecated))) 
    + 98   
    + 99  # Now iterate in the base_modules, trying to do the import 
    +100  for module in base_modules + contributed_modules: 
    +101      try: 
    +102           __import__(module, globals(), locals(), []) 
    +103      except: 
    +104          # Raise an exception if the current module is a dependency 
    +105          if module in alert_dependency: 
    +106              msg = "Missing dependency: %(module)s\n" % locals() 
    +107              msg += "Try the following command: " 
    +108              msg += "easy_install-%(python_version)s -U %(module)s" % locals() 
    +109              raise ImportError, msg 
    +110   
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.languages-module.html Index: applications/examples/static/epydoc/web2py.gluon.languages-module.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.languages-module.html +++ applications/examples/static/epydoc/web2py.gluon.languages-module.html @@ -0,0 +1,493 @@ + + + + + web2py.gluon.languages + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module languages + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Module languages

    source code

    +This file is part of the web2py Web Framework Copyrighted by Massimo + Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)

    + + + + + + + + + + + + + +
    + + + + + +
    Classes[hide private]
    +
    +   + + lazyT
    + never to be called explicitly, returned by + translator.__call__ +
    +   + + translator
    + this class is instantiated by gluon.compileapp.build_environment + as the T object +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Functions[hide private]
    +
    +   + + + + + + +
    read_dict_aux(filename) + source code + +
    + +
    +   + + + + + + +
    read_dict(filename) + source code + +
    + +
    +   + + + + + + +
    utf8_repr(s)
    + # note that we use raw strings to avoid having to use double back + slashes below
    + source code + +
    + +
    +   + + + + + + +
    write_dict(filename, + contents) + source code + +
    + +
    +   + + + + + + +
    findT(path, + language='en-us')
    + must be run by the admin app
    + source code + +
    + +
    +   + + + + + + +
    lazyT_unpickle(data) + source code + +
    + +
    +   + + + + + + +
    lazyT_pickle(data) + source code + +
    + +
    +   + + + + + + +
    update_all_languages(application_path) + source code + +
    + +
    + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Variables[hide private]
    +
    +   + + is_gae = 1
    + PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    +   + + PY_STRING_LITERAL_RE = '(?<=[^\\w]T\\()(?P<name>[uU]?[rR]?(?:\... +
    +   + + regex_translate = re.compile(r'(?s)(?<=[^\w]T\()(?P<name>[uU]?... +
    +   + + regex_language = re.compile(r'^[a-zA-Z]{2}(-[a-zA-Z]{2})?(-[a-... +
    + + + + + + +
    + + + + + +
    Function Details[hide private]
    +
    + +
    + +
    + + +
    +

    utf8_repr(s) +

    +
    source code  +
    + +

    # note that we use raw strings to avoid having to use double back + slashes below

    +

    utf8_repr() works same as repr() when processing ascii string + >>> utf8_repr('abc') == utf8_repr("abc") == + repr('abc') == repr("abc") == "'abc'" True + >>> utf8_repr('a"b"c') == repr('a"b"c') == + '\'a"b"c\'' True >>> utf8_repr("a'b'c") == + repr("a'b'c") == '"a\'b\'c"' True >>> + utf8_repr('a\'b"c') == repr('a\'b"c') == + utf8_repr("a'b\"c") == repr("a'b\"c") == + '\'a\\\'b"c\'' True >>> utf8_repr('a\r\nb') == + repr('a\r\nb') == "'a\\r\\nb'" # Test for \r, \n True

    + Unlike repr(), utf8_repr() remains utf8 content when processing utf8 + string >>> utf8_repr('中文字') == utf8_repr("中文字") == + "'中文字'" != repr('中文字') True >>> + utf8_repr('中"文"字') == "'中\"文\"字'" != + repr('中"文"字') True >>> utf8_repr("中'文'字") == + '"中\'文\'字"' != repr("中'文'字") True >>> + utf8_repr('中\'文"字') == utf8_repr("中'文\"字") == + '\'中\\\'文"字\'' != repr('中\'文"字') == + repr("中'文\"字") True >>> utf8_repr('中\r\n文') == + "'中\\r\\n文'" != repr('中\r\n文') # Test for \r, \n True +
    +
    +
    +
    +
    + + + + + + +
    + + + + + +
    Variables Details[hide private]
    +
    + +
    + +
    +

    PY_STRING_LITERAL_RE

    + +
    +
    +
    +
    Value:
    +
    +'(?<=[^\\w]T\\()(?P<name>[uU]?[rR]?(?:\'\'\'(?:[^\']|\'{1,2}(?!\'))*\'\
    +\'\')|(?:\'(?:[^\'\\\\]|\\\\.)*\')|(?:"""(?:[^"]|"{1,2}(?!"))*""")|(?:\
    +"(?:[^"\\\\]|\\\\.)*"))'
    +
    +
    +
    +
    +
    + +
    + +
    +

    regex_translate

    + +
    +
    +
    +
    Value:
    +
    +re.compile(r'(?s)(?<=[^\w]T\()(?P<name>[uU]?[rR]?(?:\'\'\'(?:[^\']|\'{\
    +1,2}(?!\'))*\'\'\')|(?:\'(?:[^\'\\]|\\.)*\')|(?:"""(?:[^"]|"{1,2}(?!")\
    +)*""")|(?:"(?:[^"\\]|\\.)*"))')
    +
    +
    +
    +
    +
    + +
    + +
    +

    regex_language

    + +
    +
    +
    +
    Value:
    +
    +re.compile(r'^[a-zA-Z]{2}(-[a-zA-Z]{2})?(-[a-zA-Z]+)?$')
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.languages-pysrc.html Index: applications/examples/static/epydoc/web2py.gluon.languages-pysrc.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.languages-pysrc.html +++ applications/examples/static/epydoc/web2py.gluon.languages-pysrc.html @@ -0,0 +1,515 @@ + + + + + web2py.gluon.languages + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module languages + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Module web2py.gluon.languages

    +
    +  1  #!/usr/bin/env python 
    +  2  # -*- coding: utf-8 -*- 
    +  3   
    +  4  """ 
    +  5  This file is part of the web2py Web Framework 
    +  6  Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> 
    +  7  License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) 
    +  8  """ 
    +  9   
    + 10  import os 
    + 11  import re 
    + 12  import cgi 
    + 13  import portalocker 
    + 14  import logging 
    + 15  import marshal 
    + 16  import copy_reg 
    + 17  from fileutils import listdir 
    + 18  import settings 
    + 19  from cfs import getcfs 
    + 20   
    + 21  __all__ = ['translator', 'findT', 'update_all_languages'] 
    + 22   
    + 23  is_gae = settings.global_settings.web2py_runtime_gae 
    + 24   
    + 25  # pattern to find T(blah blah blah) expressions 
    + 26   
    + 27  PY_STRING_LITERAL_RE = r'(?<=[^\w]T\()(?P<name>'\ 
    + 28       + r"[uU]?[rR]?(?:'''(?:[^']|'{1,2}(?!'))*''')|"\ 
    + 29       + r"(?:'(?:[^'\\]|\\.)*')|" + r'(?:"""(?:[^"]|"{1,2}(?!"))*""")|'\ 
    + 30       + r'(?:"(?:[^"\\]|\\.)*"))' 
    + 31   
    + 32  regex_translate = re.compile(PY_STRING_LITERAL_RE, re.DOTALL) 
    + 33   
    + 34  # patter for a valid accept_language 
    + 35   
    + 36  regex_language = \ 
    + 37      re.compile('^[a-zA-Z]{2}(\-[a-zA-Z]{2})?(\-[a-zA-Z]+)?$') 
    + 38   
    + 39   
    +
    40 -def read_dict_aux(filename): +
    41 fp = open(filename, 'r') + 42 portalocker.lock(fp, portalocker.LOCK_SH) + 43 lang_text = fp.read().replace('\r\n', '\n') + 44 portalocker.unlock(fp) + 45 fp.close() + 46 if not lang_text.strip(): + 47 return {} + 48 try: + 49 return eval(lang_text) + 50 except: + 51 logging.error('Syntax error in %s' % filename) + 52 return {} +
    53 +
    54 -def read_dict(filename): +
    55 return getcfs('language:%s'%filename,filename, + 56 lambda filename=filename:read_dict_aux(filename)) +
    57 +
    58 -def utf8_repr(s): +
    59 r''' # note that we use raw strings to avoid having to use double back slashes below + 60 + 61 utf8_repr() works same as repr() when processing ascii string + 62 >>> utf8_repr('abc') == utf8_repr("abc") == repr('abc') == repr("abc") == "'abc'" + 63 True + 64 >>> utf8_repr('a"b"c') == repr('a"b"c') == '\'a"b"c\'' + 65 True + 66 >>> utf8_repr("a'b'c") == repr("a'b'c") == '"a\'b\'c"' + 67 True + 68 >>> utf8_repr('a\'b"c') == repr('a\'b"c') == utf8_repr("a'b\"c") == repr("a'b\"c") == '\'a\\\'b"c\'' + 69 True + 70 >>> utf8_repr('a\r\nb') == repr('a\r\nb') == "'a\\r\\nb'" # Test for \r, \n + 71 True + 72 + 73 Unlike repr(), utf8_repr() remains utf8 content when processing utf8 string + 74 >>> utf8_repr('中文字') == utf8_repr("中文字") == "'中文字'" != repr('中文字') + 75 True + 76 >>> utf8_repr('中"文"字') == "'中\"文\"字'" != repr('中"文"字') + 77 True + 78 >>> utf8_repr("中'文'字") == '"中\'文\'字"' != repr("中'文'字") + 79 True + 80 >>> utf8_repr('中\'文"字') == utf8_repr("中'文\"字") == '\'中\\\'文"字\'' != repr('中\'文"字') == repr("中'文\"字") + 81 True + 82 >>> utf8_repr('中\r\n文') == "'中\\r\\n文'" != repr('中\r\n文') # Test for \r, \n + 83 True + 84 ''' + 85 if (s.find("'") >= 0) and (s.find('"') < 0): # only single quote exists + 86 s = ''.join(['"', s, '"']) # s = ''.join(['"', s.replace('"','\\"'), '"']) + 87 else: + 88 s = ''.join(["'", s.replace("'","\\'"), "'"]) + 89 return s.replace("\n","\\n").replace("\r","\\r") +
    90 + 91 +
    92 -def write_dict(filename, contents): +
    93 try: + 94 fp = open(filename, 'w') + 95 except IOError: + 96 logging.error('Unable to write to file %s' % filename) + 97 return + 98 portalocker.lock(fp, portalocker.LOCK_EX) + 99 fp.write('# coding: utf8\n{\n') +100 for key in sorted(contents): +101 fp.write('%s: %s,\n' % (utf8_repr(key), utf8_repr(contents[key]))) +102 fp.write('}\n') +103 portalocker.unlock(fp) +104 fp.close() +
    105 +106 +
    107 -class lazyT(object): +
    108 +109 """ +110 never to be called explicitly, returned by translator.__call__ +111 """ +112 +113 m = None +114 s = None +115 T = None +116 +
    117 - def __init__( +118 self, +119 message, +120 symbols = {}, +121 T = None, +122 ): +
    123 self.m = message +124 self.s = symbols +125 self.T = T +
    126 +
    127 - def __repr__(self): +
    128 return "<lazyT %s>" % (repr(str(self.m)), ) +
    129 +
    130 - def __str__(self): +
    131 return self.T.translate(self.m, self.s) +
    132 +
    133 - def __eq__(self, other): +
    134 return self.T.translate(self.m, self.s) == other +
    135 +
    136 - def __ne__(self, other): +
    137 return self.T.translate(self.m, self.s) != other +
    138 +
    139 - def __add__(self, other): +
    140 return '%s%s' % (self, other) +
    141 +
    142 - def __radd__(self, other): +
    143 return '%s%s' % (other, self) +
    144 +
    145 - def __cmp__(self,other): +
    146 return cmp(str(self),str(other)) +
    147 +
    148 - def __hash__(self): +
    149 return hash(str(self)) +
    150 +
    151 - def __getattr__(self, name): +
    152 return getattr(str(self),name) +
    153 +
    154 - def __getitem__(self, i): +
    155 return str(self)[i] +
    156 +
    157 - def __getslice__(self, i, j): +
    158 return str(self)[i:j] +
    159 +
    160 - def __iter__(self): +
    161 for c in str(self): yield c +
    162 +
    163 - def __len__(self): +
    164 return len(str(self)) +
    165 +
    166 - def xml(self): +
    167 return cgi.escape(str(self)) +
    168 +
    169 - def encode(self, *a, **b): +
    170 return str(self).encode(*a, **b) +
    171 +
    172 - def decode(self, *a, **b): +
    173 return str(self).decode(*a, **b) +
    174 +
    175 - def read(self): +
    176 return str(self) +
    177 +
    178 - def __mod__(self, symbols): +
    179 return self.T.translate(self.m, symbols) +
    180 +181 +
    182 -class translator(object): +
    183 +184 """ +185 this class is instantiated by gluon.compileapp.build_environment +186 as the T object +187 +188 :: +189 +190 T.force(None) # turns off translation +191 T.force('fr, it') # forces web2py to translate using fr.py or it.py +192 +193 T(\"Hello World\") # translates \"Hello World\" using the selected file +194 +195 notice 1: there is no need to force since, by default, T uses +196 accept_language to determine a translation file. +197 +198 notice 2: en and en-en are considered different languages! +199 """ +200 +
    201 - def __init__(self, request): +
    202 self.request = request +203 self.folder = request.folder +204 self.current_languages = ['en'] +205 self.accepted_language = None +206 self.language_file = None +207 self.http_accept_language = request.env.http_accept_language +208 self.requested_languages = self.force(self.http_accept_language) +209 self.lazy = True +210 self.otherTs = {} +
    211 +
    212 - def get_possible_languages(self): +
    213 possible_languages = self.current_languages +214 file_ending = re.compile("\.py$") +215 for langfile in os.listdir(os.path.join(self.folder,'languages')): +216 if file_ending.search(langfile): +217 possible_languages.append(file_ending.sub('',langfile)) +218 return possible_languages +
    219 +
    220 - def set_current_languages(self, *languages): +
    221 if len(languages) == 1 and isinstance(languages[0], (tuple, list)): +222 languages = languages[0] +223 self.current_languages = languages +224 self.force(self.http_accept_language) +
    225 +
    226 - def force(self, *languages): +
    227 if not languages or languages[0] == None: +228 languages = [] +229 if len(languages) == 1 and isinstance(languages[0], (str, unicode)): +230 languages = languages[0] +231 if languages: +232 if isinstance(languages, (str, unicode)): +233 accept_languages = languages.split(';') +234 languages = [] +235 [languages.extend(al.split(',')) for al in accept_languages] +236 languages = [item.strip().lower() for item in languages \ +237 if regex_language.match(item.strip())] +238 +239 for language in languages: +240 if language in self.current_languages: +241 self.accepted_language = language +242 break +243 filename = os.path.join(self.folder, 'languages/', language + '.py') +244 if os.path.exists(filename): +245 self.accepted_language = language +246 self.language_file = filename +247 self.t = read_dict(filename) +248 return languages +249 self.language_file = None +250 self.t = {} # ## no language by default +251 return languages +
    252 +
    253 - def __call__(self, message, symbols={},language=None): +
    254 if not language: +255 if self.lazy: +256 return lazyT(message, symbols, self) +257 else: +258 return self.translate(message, symbols) +259 else: +260 try: +261 otherT = self.otherTs[language] +262 except KeyError: +263 otherT = self.otherTs[language] = translator(self.request) +264 otherT.force(language) +265 return otherT(message,symbols) +
    266 +
    267 - def translate(self, message, symbols): +
    268 """ +269 user ## to add a comment into a translation string +270 the comment can be useful do discriminate different possible +271 translations for the same string (for example different locations) +272 +273 T(' hello world ') -> ' hello world ' +274 T(' hello world ## token') -> 'hello world' +275 T('hello ## world ## token') -> 'hello ## world' +276 +277 the ## notation is ignored in multiline strings and strings that +278 start with ##. this is to allow markmin syntax to be translated +279 """ +280 if not message.startswith('#') and not '\n' in message: +281 tokens = message.rsplit('##', 1) +282 else: +283 # this allows markmin syntax in translations +284 tokens = [message] +285 if len(tokens) == 2: +286 tokens[0] = tokens[0].strip() +287 message = tokens[0] + '##' + tokens[1].strip() +288 mt = self.t.get(message, None) +289 if mt == None: +290 self.t[message] = mt = tokens[0] +291 if self.language_file and not is_gae: +292 write_dict(self.language_file, self.t) +293 if symbols or symbols == 0: +294 return mt % symbols +295 return mt +
    296 +297 +
    298 -def findT(path, language='en-us'): +
    299 """ +300 must be run by the admin app +301 """ +302 filename = os.path.join(path, 'languages', '%s.py' % language) +303 sentences = read_dict(filename) +304 mp = os.path.join(path, 'models') +305 cp = os.path.join(path, 'controllers') +306 vp = os.path.join(path, 'views') +307 for file in listdir(mp, '.+\.py', 0) + listdir(cp, '.+\.py', 0)\ +308 + listdir(vp, '.+\.html', 0): +309 fp = open(file, 'r') +310 portalocker.lock(fp, portalocker.LOCK_SH) +311 data = fp.read() +312 portalocker.unlock(fp) +313 fp.close() +314 items = regex_translate.findall(data) +315 for item in items: +316 try: +317 message = eval(item) +318 if not message.startswith('#') and not '\n' in message: +319 tokens = message.rsplit('##', 1) +320 else: +321 # this allows markmin syntax in translations +322 tokens = [message] +323 if len(tokens) == 2: +324 message = tokens[0].strip() + '##' + tokens[1].strip() +325 if message and not message in sentences: +326 sentences[message] = message +327 except: +328 pass +329 write_dict(filename, sentences) +
    330 +331 ### important to allow safe session.flash=T(....) +
    332 -def lazyT_unpickle(data): +
    333 return marshal.loads(data) +
    334 -def lazyT_pickle(data): +
    335 return lazyT_unpickle, (marshal.dumps(str(data)),) +
    336 copy_reg.pickle(lazyT, lazyT_pickle, lazyT_unpickle) +337 +
    338 -def update_all_languages(application_path): +
    339 path = os.path.join(application_path, 'languages/') +340 for language in listdir(path, '^\w+(\-\w+)?\.py$'): +341 findT(application_path, language[:-3]) +
    342 +343 +344 if __name__ == '__main__': +345 import doctest +346 doctest.testmod() +347 +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.languages.lazyT-class.html Index: applications/examples/static/epydoc/web2py.gluon.languages.lazyT-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.languages.lazyT-class.html +++ applications/examples/static/epydoc/web2py.gluon.languages.lazyT-class.html @@ -0,0 +1,667 @@ + + + + + web2py.gluon.languages.lazyT + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module languages :: + Class lazyT + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class lazyT

    source code

    +
    +object --+
    +         |
    +        lazyT
    +
    + +
    +never to be called explicitly, returned by translator.__call__

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + message, + symbols={}, + T=1)
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    __repr__(self)
    + repr(x)
    + source code + +
    + +
    +   + + + + + + +
    __str__(self)
    + str(x)
    + source code + +
    + +
    +   + + + + + + +
    __eq__(self, + other) + source code + +
    + +
    +   + + + + + + +
    __ne__(self, + other) + source code + +
    + +
    +   + + + + + + +
    __add__(self, + other) + source code + +
    + +
    +   + + + + + + +
    __radd__(self, + other) + source code + +
    + +
    +   + + + + + + +
    __cmp__(self, + other) + source code + +
    + +
    +   + + + + + + +
    __hash__(self)
    + hash(x)
    + source code + +
    + +
    +   + + + + + + +
    __getattr__(self, + name) + source code + +
    + +
    +   + + + + + + +
    __getitem__(self, + i) + source code + +
    + +
    +   + + + + + + +
    __getslice__(self, + i, + j) + source code + +
    + +
    +   + + + + + + +
    __iter__(self) + source code + +
    + +
    +   + + + + + + +
    __len__(self) + source code + +
    + +
    +   + + + + + + +
    xml(self) + source code + +
    + +
    +   + + + + + + +
    encode(self, + *a, + **b) + source code + +
    + +
    +   + + + + + + +
    decode(self, + *a, + **b) + source code + +
    + +
    +   + + + + + + +
    read(self) + source code + +
    + +
    +   + + + + + + +
    __mod__(self, + symbols) + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __new__, + __reduce__, + __reduce_ex__, + __setattr__ +

    +
    + + + + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + m = 1
    + PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    +   + + s = 1
    + PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    +   + + T = 1
    + PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + message, + symbols={}, + T=1) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    __repr__(self) +
    (Representation operator) +

    +
    source code  +
    + + repr(x) +
    +
    Overrides: + object.__repr__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    __str__(self) +
    (Informal representation operator) +

    +
    source code  +
    + + str(x) +
    +
    Overrides: + object.__str__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    __hash__(self) +
    (Hashing function) +

    +
    source code  +
    + + hash(x) +
    +
    Overrides: + object.__hash__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.languages.translator-class.html Index: applications/examples/static/epydoc/web2py.gluon.languages.translator-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.languages.translator-class.html +++ applications/examples/static/epydoc/web2py.gluon.languages.translator-class.html @@ -0,0 +1,376 @@ + + + + + web2py.gluon.languages.translator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module languages :: + Class translator + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class translator

    source code

    +
    +object --+
    +         |
    +        translator
    +
    + +
    +

    this class is instantiated by gluon.compileapp.build_environment as + the T object

    + : +
    +   T.force(None) # turns off translation
    +   T.force('fr, it') # forces web2py to translate using fr.py or it.py
    +
    +   T("Hello World") # translates "Hello World" using the selected file
    +
    +

    notice 1: there is no need to force since, by default, T uses + accept_language to determine a translation file.

    + notice 2: en and en-en are considered different languages!

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + request)
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    get_possible_languages(self) + source code + +
    + +
    +   + + + + + + +
    set_current_languages(self, + *languages) + source code + +
    + +
    +   + + + + + + +
    force(self, + *languages) + source code + +
    + +
    +   + + + + + + +
    __call__(self, + message, + symbols={}, + language=1) + source code + +
    + +
    +   + + + + + + +
    translate(self, + message, + symbols)
    + user ## to add a comment into a translation string the comment can + be useful do discriminate different possible translations for the + same string (for example different locations)
    + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + request) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    translate(self, + message, + symbols) +

    +
    source code  +
    + +

    user ## to add a comment into a translation string the comment can be + useful do discriminate different possible translations for the same + string (for example different locations)

    +

    T(' hello world ') -> ' hello world ' T(' hello world ## token') + -> 'hello world' T('hello ## world ## token') -> 'hello ## + world'

    + the ## notation is ignored in multiline strings and strings that start + with ##. this is to allow markmin syntax to be translated +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.main-module.html Index: applications/examples/static/epydoc/web2py.gluon.main-module.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.main-module.html +++ applications/examples/static/epydoc/web2py.gluon.main-module.html @@ -0,0 +1,671 @@ + + + + + web2py.gluon.main + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module main + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Module main

    source code

    +
    +
    +This file is part of the web2py Web Framework
    +Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
    +License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
    +
    +Contains:
    +
    +- wsgibase: the gluon wsgi application
    +
    +


    + + + + + + + + + + +
    + + + + + +
    Classes[hide private]
    +
    +   + + HttpServer
    + the web2py web server (Rocket) +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Functions[hide private]
    +
    +   + + + + + + +
    get_client(env)
    + guess the client address from the environment variables
    + source code + +
    + +
    +   + + + + + + +
    copystream_progress(request, + chunk_size=100000)
    + copies request.env.wsgi_input into request.body and stores + progress upload status in cache.ram X-Progress-ID:length and + X-Progress-ID:uploaded
    + source code + +
    + +
    +   + + + + + + +
    serve_controller(request, + response, + session)
    + this function is used to generate a dynamic page.
    + source code + +
    + +
    +   + + + + + + +
    start_response_aux(status, + headers, + exc_info, + response=1)
    + in controller you can use::...
    + source code + +
    + +
    +   + + + + + + +
    middleware_aux(request, + response, + *middleware_apps)
    + In you controller use:
    + source code + +
    + +
    +   + + + + + + +
    environ_aux(environ, + request) + source code + +
    + +
    +   + + + + + + +
    parse_get_post_vars(request, + environ) + source code + +
    + +
    +   + + + + + + +
    wsgibase(environ, + responder)
    + this is the gluon wsgi application.
    + source code + +
    + +
    +   + + + + + + +
    save_password(password, + port)
    + used by main() to save the password in the parameters_port.py + file.
    + source code + +
    + +
    +   + + + + + + +
    appfactory(wsgiapp=<function wsgibase at 0x13d0410>, + logfilename='httpserver.log', + profilerfilename='profiler.log')
    + generates a wsgi application that does logging and profiling and calls +wsgibase + +..
    + source code + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Variables[hide private]
    +
    +   + + web2py_path = 1
    + PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    +   + + logpath = '/home/mdipierro/web2py/logging.conf' +
    +   + + logger = logging.getLogger("web2py") +
    +   + + requests = 0 +
    +   + + regex_client = re.compile(r'[\w-:]+(\.[\w-]+)*\.?') +
    +   + + version_info = <closed file '/home/mdipierro/web2py/VERSION', ... +
    +   + + web2py_version = 'Version 1.98.2 (2011-08-03 18:44:38)\n' +
    + + + + + + +
    + + + + + +
    Function Details[hide private]
    +
    + +
    + +
    + + +
    +

    get_client(env) +

    +
    source code  +
    + +

    guess the client address from the environment variables

    + first tries 'http_x_forwarded_for', secondly 'remote_addr' if all + fails assume '127.0.0.1' (running locally) +
    +
    +
    +
    + +
    + +
    + + +
    +

    serve_controller(request, + response, + session) +

    +
    source code  +
    + + this function is used to generate a dynamic page. It first runs all + models, then runs the function in the controller, and then tries to + render the output using a view/template. this function must run from the + [application] folder. A typical example would be the call to the url + /[application]/[controller]/[function] that would result in a call to + [function]() in applications/[application]/[controller].py rendered by + applications/[application]/views/[controller]/[function].html +
    +
    +
    +
    + +
    + +
    + + +
    +

    start_response_aux(status, + headers, + exc_info, + response=1) +

    +
    source code  +
    + +
    +
    +in controller you can use::
    +
    +- request.wsgi.environ
    +- request.wsgi.start_response
    +
    +to call third party WSGI applications
    +
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    middleware_aux(request, + response, + *middleware_apps) +

    +
    source code  +
    + + In you controller use: +
    +   @request.wsgi.middleware(middleware1, middleware2, ...)
    +
    + to decorate actions with WSGI middleware. actions must return strings. + uses a simulated environment so it may have weird behavior in some + cases +
    +
    +
    +
    + +
    + +
    + + +
    +

    wsgibase(environ, + responder) +

    +
    source code  +
    + +
    +
    +this is the gluon wsgi application. the first function called when a page
    +is requested (static or dynamic). it can be called by paste.httpserver
    +or by apache mod_wsgi.
    +
    +  - fills request with info
    +  - the environment variables, replacing '.' with '_'
    +  - adds web2py path and version info
    +  - compensates for fcgi missing path_info and query_string
    +  - validates the path in url
    +
    +The url path must be either:
    +
    +1. for static pages:
    +
    +  - /<application>/static/<file>
    +
    +2. for dynamic pages:
    +
    +  - /<application>[/<controller>[/<function>[/<sub>]]][.<extension>]
    +  - (sub may go several levels deep, currently 3 levels are supported:
    +     sub1/sub2/sub3)
    +
    +The naming conventions are:
    +
    +  - application, controller, function and extension may only contain
    +    [a-zA-Z0-9_]
    +  - file and sub may also contain '-', '=', '.' and '/'
    +
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    appfactory(wsgiapp=<function wsgibase at 0x13d0410>, + logfilename='httpserver.log', + profilerfilename='profiler.log') +

    +
    source code  +
    + +
    +
    +generates a wsgi application that does logging and profiling and calls
    +wsgibase
    +
    +.. function:: gluon.main.appfactory(
    +        [wsgiapp=wsgibase
    +        [, logfilename='httpserver.log'
    +        [, profilerfilename='profiler.log']]])
    +
    +
    +
    +
    +
    +
    +
    + + + + + + +
    + + + + + +
    Variables Details[hide private]
    +
    + +
    + +
    +

    version_info

    + +
    +
    +
    +
    Value:
    +
    +open(abspath('VERSION', gluon= True), 'r')
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.main-pysrc.html Index: applications/examples/static/epydoc/web2py.gluon.main-pysrc.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.main-pysrc.html +++ applications/examples/static/epydoc/web2py.gluon.main-pysrc.html @@ -0,0 +1,1236 @@ + + + + + web2py.gluon.main + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module main + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Module web2py.gluon.main

    +
    +  1  #!/bin/env python 
    +  2  # -*- coding: utf-8 -*- 
    +  3   
    +  4  """ 
    +  5  This file is part of the web2py Web Framework 
    +  6  Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> 
    +  7  License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) 
    +  8   
    +  9  Contains: 
    + 10   
    + 11  - wsgibase: the gluon wsgi application 
    + 12   
    + 13  """ 
    + 14   
    + 15  import gc 
    + 16  import cgi 
    + 17  import cStringIO 
    + 18  import Cookie 
    + 19  import os 
    + 20  import re 
    + 21  import copy 
    + 22  import sys 
    + 23  import time 
    + 24  import thread 
    + 25  import datetime 
    + 26  import signal 
    + 27  import socket 
    + 28  import tempfile 
    + 29  import random 
    + 30  import string 
    + 31  import platform 
    + 32  from fileutils import abspath, write_file 
    + 33  from settings import global_settings 
    + 34  from admin import add_path_first, create_missing_folders, create_missing_app_folders 
    + 35  from globals import current 
    + 36   
    + 37  from custom_import import custom_import_install 
    + 38  from contrib.simplejson import dumps 
    + 39   
    + 40  #  Remarks: 
    + 41  #  calling script has inserted path to script directory into sys.path 
    + 42  #  applications_parent (path to applications/, site-packages/ etc) 
    + 43  #  defaults to that directory set sys.path to 
    + 44  #  ("", gluon_parent/site-packages, gluon_parent, ...) 
    + 45  # 
    + 46  #  this is wrong: 
    + 47  #  web2py_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 
    + 48  #  because we do not want the path to this file which may be Library.zip 
    + 49  #  gluon_parent is the directory containing gluon, web2py.py, logging.conf 
    + 50  #  and the handlers. 
    + 51  #  applications_parent (web2py_path) is the directory containing applications/ 
    + 52  #  and routes.py 
    + 53  #  The two are identical unless web2py_path is changed via the web2py.py -f folder option 
    + 54  #  main.web2py_path is the same as applications_parent (for backward compatibility) 
    + 55   
    + 56  if not hasattr(os, 'mkdir'): 
    + 57      global_settings.db_sessions = True 
    + 58  if global_settings.db_sessions is not True: 
    + 59      global_settings.db_sessions = set() 
    + 60  global_settings.gluon_parent = os.environ.get('web2py_path', os.getcwd()) 
    + 61  global_settings.applications_parent = global_settings.gluon_parent 
    + 62  web2py_path = global_settings.applications_parent # backward compatibility 
    + 63  global_settings.app_folders = set() 
    + 64  global_settings.debugging = False 
    + 65   
    + 66  custom_import_install(web2py_path) 
    + 67   
    + 68  create_missing_folders() 
    + 69   
    + 70  # set up logging for subsequent imports 
    + 71  import logging 
    + 72  import logging.config 
    + 73  logpath = abspath("logging.conf") 
    + 74  if os.path.exists(logpath): 
    + 75      logging.config.fileConfig(abspath("logging.conf")) 
    + 76  else: 
    + 77      logging.basicConfig() 
    + 78  logger = logging.getLogger("web2py") 
    + 79   
    + 80  from restricted import RestrictedError 
    + 81  from http import HTTP, redirect 
    + 82  from globals import Request, Response, Session 
    + 83  from compileapp import build_environment, run_models_in, \ 
    + 84      run_controller_in, run_view_in 
    + 85  from fileutils import copystream 
    + 86  from contenttype import contenttype 
    + 87  from dal import BaseAdapter 
    + 88  from settings import global_settings 
    + 89  from validators import CRYPT 
    + 90  from cache import Cache 
    + 91  from html import URL as Url 
    + 92  import newcron 
    + 93  import rewrite 
    + 94   
    + 95  __all__ = ['wsgibase', 'save_password', 'appfactory', 'HttpServer'] 
    + 96   
    + 97  requests = 0    # gc timer 
    + 98   
    + 99  # Security Checks: validate URL and session_id here, 
    +100  # accept_language is validated in languages 
    +101   
    +102  # pattern used to validate client address 
    +103  regex_client = re.compile('[\w\-:]+(\.[\w\-]+)*\.?')  # ## to account for IPV6 
    +104   
    +105  version_info = open(abspath('VERSION', gluon=True), 'r') 
    +106  web2py_version = version_info.read() 
    +107  version_info.close() 
    +108   
    +109  try: 
    +110      import rocket 
    +111  except: 
    +112      if not global_settings.web2py_runtime_gae: 
    +113          logger.warn('unable to import Rocket') 
    +114   
    +115  rewrite.load() 
    +116   
    +
    117 -def get_client(env): +
    118 """ +119 guess the client address from the environment variables +120 +121 first tries 'http_x_forwarded_for', secondly 'remote_addr' +122 if all fails assume '127.0.0.1' (running locally) +123 """ +124 g = regex_client.search(env.get('http_x_forwarded_for', '')) +125 if g: +126 return g.group() +127 g = regex_client.search(env.get('remote_addr', '')) +128 if g: +129 return g.group() +130 return '127.0.0.1' +
    131 +
    132 -def copystream_progress(request, chunk_size= 10**5): +
    133 """ +134 copies request.env.wsgi_input into request.body +135 and stores progress upload status in cache.ram +136 X-Progress-ID:length and X-Progress-ID:uploaded +137 """ +138 if not request.env.content_length: +139 return cStringIO.StringIO() +140 source = request.env.wsgi_input +141 size = int(request.env.content_length) +142 dest = tempfile.TemporaryFile() +143 if not 'X-Progress-ID' in request.vars: +144 copystream(source, dest, size, chunk_size) +145 return dest +146 cache_key = 'X-Progress-ID:'+request.vars['X-Progress-ID'] +147 cache = Cache(request) +148 cache.ram(cache_key+':length', lambda: size, 0) +149 cache.ram(cache_key+':uploaded', lambda: 0, 0) +150 while size > 0: +151 if size < chunk_size: +152 data = source.read(size) +153 cache.ram.increment(cache_key+':uploaded', size) +154 else: +155 data = source.read(chunk_size) +156 cache.ram.increment(cache_key+':uploaded', chunk_size) +157 length = len(data) +158 if length > size: +159 (data, length) = (data[:size], size) +160 size -= length +161 if length == 0: +162 break +163 dest.write(data) +164 if length < chunk_size: +165 break +166 dest.seek(0) +167 cache.ram(cache_key+':length', None) +168 cache.ram(cache_key+':uploaded', None) +169 return dest +
    170 +171 +
    172 -def serve_controller(request, response, session): +
    173 """ +174 this function is used to generate a dynamic page. +175 It first runs all models, then runs the function in the controller, +176 and then tries to render the output using a view/template. +177 this function must run from the [application] folder. +178 A typical example would be the call to the url +179 /[application]/[controller]/[function] that would result in a call +180 to [function]() in applications/[application]/[controller].py +181 rendered by applications/[application]/views/[controller]/[function].html +182 """ +183 +184 # ################################################## +185 # build environment for controller and view +186 # ################################################## +187 +188 environment = build_environment(request, response, session) +189 +190 # set default view, controller can override it +191 +192 response.view = '%s/%s.%s' % (request.controller, +193 request.function, +194 request.extension) +195 +196 # also, make sure the flash is passed through +197 # ################################################## +198 # process models, controller and view (if required) +199 # ################################################## +200 +201 run_models_in(environment) +202 response._view_environment = copy.copy(environment) +203 page = run_controller_in(request.controller, request.function, environment) +204 if isinstance(page, dict): +205 response._vars = page +206 for key in page: +207 response._view_environment[key] = page[key] +208 run_view_in(response._view_environment) +209 page = response.body.getvalue() +210 # logic to garbage collect after exec, not always, once every 100 requests +211 global requests +212 requests = ('requests' in globals()) and (requests+1) % 100 or 0 +213 if not requests: gc.collect() +214 # end garbage collection logic +215 raise HTTP(response.status, page, **response.headers) +
    216 +217 +
    218 -def start_response_aux(status, headers, exc_info, response=None): +
    219 """ +220 in controller you can use:: +221 +222 - request.wsgi.environ +223 - request.wsgi.start_response +224 +225 to call third party WSGI applications +226 """ +227 response.status = str(status).split(' ',1)[0] +228 response.headers = dict(headers) +229 return lambda *args, **kargs: response.write(escape=False,*args,**kargs) +
    230 +231 +
    232 -def middleware_aux(request, response, *middleware_apps): +
    233 """ +234 In you controller use:: +235 +236 @request.wsgi.middleware(middleware1, middleware2, ...) +237 +238 to decorate actions with WSGI middleware. actions must return strings. +239 uses a simulated environment so it may have weird behavior in some cases +240 """ +241 def middleware(f): +242 def app(environ, start_response): +243 data = f() +244 start_response(response.status,response.headers.items()) +245 if isinstance(data,list): +246 return data +247 return [data] +
    248 for item in middleware_apps: +249 app=item(app) +250 def caller(app): +251 return app(request.wsgi.environ,request.wsgi.start_response) +252 return lambda caller=caller, app=app: caller(app) +253 return middleware +254 +
    255 -def environ_aux(environ,request): +
    256 new_environ = copy.copy(environ) +257 new_environ['wsgi.input'] = request.body +258 new_environ['wsgi.version'] = 1 +259 return new_environ +
    260 +
    261 -def parse_get_post_vars(request, environ): +
    262 +263 # always parse variables in URL for GET, POST, PUT, DELETE, etc. in get_vars +264 dget = cgi.parse_qsl(request.env.query_string or '', keep_blank_values=1) +265 for (key, value) in dget: +266 if key in request.get_vars: +267 if isinstance(request.get_vars[key], list): +268 request.get_vars[key] += [value] +269 else: +270 request.get_vars[key] = [request.get_vars[key]] + [value] +271 else: +272 request.get_vars[key] = value +273 request.vars[key] = request.get_vars[key] +274 +275 # parse POST variables on POST, PUT, BOTH only in post_vars +276 request.body = copystream_progress(request) ### stores request body +277 if (request.body and request.env.request_method in ('POST', 'PUT', 'BOTH')): +278 dpost = cgi.FieldStorage(fp=request.body,environ=environ,keep_blank_values=1) +279 # The same detection used by FieldStorage to detect multipart POSTs +280 is_multipart = dpost.type[:10] == 'multipart/' +281 request.body.seek(0) +282 isle25 = sys.version_info[1] <= 5 +283 +284 def listify(a): +285 return (not isinstance(a,list) and [a]) or a +
    286 try: +287 keys = sorted(dpost) +288 except TypeError: +289 keys = [] +290 for key in keys: +291 dpk = dpost[key] +292 # if en element is not a file replace it with its value else leave it alone +293 if isinstance(dpk, list): +294 if not dpk[0].filename: +295 value = [x.value for x in dpk] +296 else: +297 value = [x for x in dpk] +298 elif not dpk.filename: +299 value = dpk.value +300 else: +301 value = dpk +302 pvalue = listify(value) +303 if key in request.vars: +304 gvalue = listify(request.vars[key]) +305 if isle25: +306 value = pvalue + gvalue +307 elif is_multipart: +308 pvalue = pvalue[len(gvalue):] +309 else: +310 pvalue = pvalue[:-len(gvalue)] +311 request.vars[key] = value +312 if len(pvalue): +313 request.post_vars[key] = (len(pvalue)>1 and pvalue) or pvalue[0] +314 +315 +
    316 -def wsgibase(environ, responder): +
    317 """ +318 this is the gluon wsgi application. the first function called when a page +319 is requested (static or dynamic). it can be called by paste.httpserver +320 or by apache mod_wsgi. +321 +322 - fills request with info +323 - the environment variables, replacing '.' with '_' +324 - adds web2py path and version info +325 - compensates for fcgi missing path_info and query_string +326 - validates the path in url +327 +328 The url path must be either: +329 +330 1. for static pages: +331 +332 - /<application>/static/<file> +333 +334 2. for dynamic pages: +335 +336 - /<application>[/<controller>[/<function>[/<sub>]]][.<extension>] +337 - (sub may go several levels deep, currently 3 levels are supported: +338 sub1/sub2/sub3) +339 +340 The naming conventions are: +341 +342 - application, controller, function and extension may only contain +343 [a-zA-Z0-9_] +344 - file and sub may also contain '-', '=', '.' and '/' +345 """ +346 +347 current.__dict__.clear() +348 request = Request() +349 response = Response() +350 session = Session() +351 request.env.web2py_path = global_settings.applications_parent +352 request.env.web2py_version = web2py_version +353 request.env.update(global_settings) +354 static_file = False +355 try: +356 try: +357 try: +358 # ################################################## +359 # handle fcgi missing path_info and query_string +360 # select rewrite parameters +361 # rewrite incoming URL +362 # parse rewritten header variables +363 # parse rewritten URL +364 # serve file if static +365 # ################################################## +366 +367 if not environ.get('PATH_INFO',None) and \ +368 environ.get('REQUEST_URI',None): +369 # for fcgi, get path_info and query_string from request_uri +370 items = environ['REQUEST_URI'].split('?') +371 environ['PATH_INFO'] = items[0] +372 if len(items) > 1: +373 environ['QUERY_STRING'] = items[1] +374 else: +375 environ['QUERY_STRING'] = '' +376 if not environ.get('HTTP_HOST',None): +377 environ['HTTP_HOST'] = '%s:%s' % (environ.get('SERVER_NAME'), +378 environ.get('SERVER_PORT')) +379 +380 (static_file, environ) = rewrite.url_in(request, environ) +381 if static_file: +382 if request.env.get('query_string', '')[:10] == 'attachment': +383 response.headers['Content-Disposition'] = 'attachment' +384 response.stream(static_file, request=request) +385 +386 # ################################################## +387 # fill in request items +388 # ################################################## +389 +390 http_host = request.env.http_host.split(':',1)[0] +391 +392 local_hosts = [http_host,'::1','127.0.0.1','::ffff:127.0.0.1'] +393 if not global_settings.web2py_runtime_gae: +394 local_hosts += [socket.gethostname(), +395 socket.gethostbyname(http_host)] +396 request.client = get_client(request.env) +397 request.folder = abspath('applications', +398 request.application) + os.sep +399 x_req_with = str(request.env.http_x_requested_with).lower() +400 request.ajax = x_req_with == 'xmlhttprequest' +401 request.cid = request.env.http_web2py_component_element +402 request.is_local = request.env.remote_addr in local_hosts +403 request.is_https = request.env.wsgi_url_scheme \ +404 in ['https', 'HTTPS'] or request.env.https == 'on' +405 +406 # ################################################## +407 # compute a request.uuid to be used for tickets and toolbar +408 # ################################################## +409 +410 response.uuid = request.compute_uuid() +411 +412 # ################################################## +413 # access the requested application +414 # ################################################## +415 +416 if not os.path.exists(request.folder): +417 if request.application == rewrite.thread.routes.default_application and request.application != 'welcome': +418 request.application = 'welcome' +419 redirect(Url(r=request)) +420 elif rewrite.thread.routes.error_handler: +421 redirect(Url(rewrite.thread.routes.error_handler['application'], +422 rewrite.thread.routes.error_handler['controller'], +423 rewrite.thread.routes.error_handler['function'], +424 args=request.application)) +425 else: +426 raise HTTP(404, +427 rewrite.thread.routes.error_message % 'invalid request', +428 web2py_error='invalid application') +429 request.url = Url(r=request, args=request.args, +430 extension=request.raw_extension) +431 +432 # ################################################## +433 # build missing folders +434 # ################################################## +435 +436 create_missing_app_folders(request) +437 +438 # ################################################## +439 # get the GET and POST data +440 # ################################################## +441 +442 parse_get_post_vars(request, environ) +443 +444 # ################################################## +445 # expose wsgi hooks for convenience +446 # ################################################## +447 +448 request.wsgi.environ = environ_aux(environ,request) +449 request.wsgi.start_response = lambda status='200', headers=[], \ +450 exec_info=None, response=response: \ +451 start_response_aux(status, headers, exec_info, response) +452 request.wsgi.middleware = lambda *a: middleware_aux(request,response,*a) +453 +454 # ################################################## +455 # load cookies +456 # ################################################## +457 +458 if request.env.http_cookie: +459 try: +460 request.cookies.load(request.env.http_cookie) +461 except Cookie.CookieError, e: +462 pass # invalid cookies +463 +464 # ################################################## +465 # try load session or create new session file +466 # ################################################## +467 +468 session.connect(request, response) +469 +470 # ################################################## +471 # set no-cache headers +472 # ################################################## +473 +474 response.headers['Content-Type'] = contenttype('.'+request.extension) +475 response.headers['Cache-Control'] = \ +476 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0' +477 response.headers['Expires'] = \ +478 time.strftime('%a, %d %b %Y %H:%M:%S GMT', time.gmtime()) +479 response.headers['Pragma'] = 'no-cache' +480 +481 # ################################################## +482 # run controller +483 # ################################################## +484 +485 serve_controller(request, response, session) +486 +487 except HTTP, http_response: +488 if static_file: +489 return http_response.to(responder) +490 +491 if request.body: +492 request.body.close() +493 +494 # ################################################## +495 # on success, try store session in database +496 # ################################################## +497 session._try_store_in_db(request, response) +498 +499 # ################################################## +500 # on success, commit database +501 # ################################################## +502 +503 if response._custom_commit: +504 response._custom_commit() +505 else: +506 BaseAdapter.close_all_instances('commit') +507 +508 # ################################################## +509 # if session not in db try store session on filesystem +510 # this must be done after trying to commit database! +511 # ################################################## +512 +513 session._try_store_on_disk(request, response) +514 +515 # ################################################## +516 # store cookies in headers +517 # ################################################## +518 +519 if request.cid: +520 +521 if response.flash and not 'web2py-component-flash' in http_response.headers: +522 http_response.headers['web2py-component-flash'] = \ +523 str(response.flash).replace('\n','') +524 if response.js and not 'web2py-component-command' in http_response.headers: +525 http_response.headers['web2py-component-command'] = \ +526 response.js.replace('\n','') +527 if session._forget and \ +528 response.session_id_name in response.cookies: +529 del response.cookies[response.session_id_name] +530 elif session._secure: +531 response.cookies[response.session_id_name]['secure'] = True +532 if len(response.cookies)>0: +533 http_response.headers['Set-Cookie'] = \ +534 [str(cookie)[11:] for cookie in response.cookies.values()] +535 ticket=None +536 +537 except RestrictedError, e: +538 +539 if request.body: +540 request.body.close() +541 +542 # ################################################## +543 # on application error, rollback database +544 # ################################################## +545 +546 ticket = e.log(request) or 'unknown' +547 if response._custom_rollback: +548 response._custom_rollback() +549 else: +550 BaseAdapter.close_all_instances('rollback') +551 +552 http_response = \ +553 HTTP(500, +554 rewrite.thread.routes.error_message_ticket % dict(ticket=ticket), +555 web2py_error='ticket %s' % ticket) +556 +557 except: +558 +559 if request.body: +560 request.body.close() +561 +562 # ################################################## +563 # on application error, rollback database +564 # ################################################## +565 +566 try: +567 if response._custom_rollback: +568 response._custom_rollback() +569 else: +570 BaseAdapter.close_all_instances('rollback') +571 except: +572 pass +573 e = RestrictedError('Framework', '', '', locals()) +574 ticket = e.log(request) or 'unrecoverable' +575 http_response = \ +576 HTTP(500, +577 rewrite.thread.routes.error_message_ticket % dict(ticket=ticket), +578 web2py_error='ticket %s' % ticket) +579 +580 finally: +581 if response and hasattr(response, 'session_file') and response.session_file: +582 response.session_file.close() +583 # if global_settings.debugging: +584 # import gluon.debug +585 # gluon.debug.stop_trace() +586 +587 session._unlock(response) +588 http_response, new_environ = rewrite.try_rewrite_on_error( +589 http_response, request, environ, ticket) +590 if not http_response: +591 return wsgibase(new_environ,responder) +592 if global_settings.web2py_crontype == 'soft': +593 newcron.softcron(global_settings.applications_parent).start() +594 return http_response.to(responder) +
    595 +596 +
    597 -def save_password(password, port): +
    598 """ +599 used by main() to save the password in the parameters_port.py file. +600 """ +601 +602 password_file = abspath('parameters_%i.py' % port) +603 if password == '<random>': +604 # make up a new password +605 chars = string.letters + string.digits +606 password = ''.join([random.choice(chars) for i in range(8)]) +607 cpassword = CRYPT()(password)[0] +608 print '******************* IMPORTANT!!! ************************' +609 print 'your admin password is "%s"' % password +610 print '*********************************************************' +611 elif password == '<recycle>': +612 # reuse the current password if any +613 if os.path.exists(password_file): +614 return +615 else: +616 password = '' +617 elif password.startswith('<pam_user:'): +618 # use the pam password for specified user +619 cpassword = password[1:-1] +620 else: +621 # use provided password +622 cpassword = CRYPT()(password)[0] +623 fp = open(password_file, 'w') +624 if password: +625 fp.write('password="%s"\n' % cpassword) +626 else: +627 fp.write('password=None\n') +628 fp.close() +
    629 +630 +
    631 -def appfactory(wsgiapp=wsgibase, +632 logfilename='httpserver.log', +633 profilerfilename='profiler.log'): +
    634 """ +635 generates a wsgi application that does logging and profiling and calls +636 wsgibase +637 +638 .. function:: gluon.main.appfactory( +639 [wsgiapp=wsgibase +640 [, logfilename='httpserver.log' +641 [, profilerfilename='profiler.log']]]) +642 +643 """ +644 if profilerfilename and os.path.exists(profilerfilename): +645 os.unlink(profilerfilename) +646 locker = thread.allocate_lock() +647 +648 def app_with_logging(environ, responder): +649 """ +650 a wsgi app that does logging and profiling and calls wsgibase +651 """ +652 status_headers = [] +653 +654 def responder2(s, h): +655 """ +656 wsgi responder app +657 """ +658 status_headers.append(s) +659 status_headers.append(h) +660 return responder(s, h) +
    661 +662 time_in = time.time() +663 ret = [0] +664 if not profilerfilename: +665 ret[0] = wsgiapp(environ, responder2) +666 else: +667 import cProfile +668 import pstats +669 logger.warn('profiler is on. this makes web2py slower and serial') +670 +671 locker.acquire() +672 cProfile.runctx('ret[0] = wsgiapp(environ, responder2)', +673 globals(), locals(), profilerfilename+'.tmp') +674 stat = pstats.Stats(profilerfilename+'.tmp') +675 stat.stream = cStringIO.StringIO() +676 stat.strip_dirs().sort_stats("time").print_stats(80) +677 profile_out = stat.stream.getvalue() +678 profile_file = open(profilerfilename, 'a') +679 profile_file.write('%s\n%s\n%s\n%s\n\n' % \ +680 ('='*60, environ['PATH_INFO'], '='*60, profile_out)) +681 profile_file.close() +682 locker.release() +683 try: +684 line = '%s, %s, %s, %s, %s, %s, %f\n' % ( +685 environ['REMOTE_ADDR'], +686 datetime.datetime.today().strftime('%Y-%m-%d %H:%M:%S'), +687 environ['REQUEST_METHOD'], +688 environ['PATH_INFO'].replace(',', '%2C'), +689 environ['SERVER_PROTOCOL'], +690 (status_headers[0])[:3], +691 time.time() - time_in, +692 ) +693 if not logfilename: +694 sys.stdout.write(line) +695 elif isinstance(logfilename, str): +696 write_file(logfilename, line, 'a') +697 else: +698 logfilename.write(line) +699 except: +700 pass +701 return ret[0] +702 +703 return app_with_logging +704 +705 +
    706 -class HttpServer(object): +
    707 """ +708 the web2py web server (Rocket) +709 """ +710 +
    711 - def __init__( +712 self, +713 ip='127.0.0.1', +714 port=8000, +715 password='', +716 pid_filename='httpserver.pid', +717 log_filename='httpserver.log', +718 profiler_filename=None, +719 ssl_certificate=None, +720 ssl_private_key=None, +721 min_threads=None, +722 max_threads=None, +723 server_name=None, +724 request_queue_size=5, +725 timeout=10, +726 shutdown_timeout=None, # Rocket does not use a shutdown timeout +727 path=None, +728 interfaces=None # Rocket is able to use several interfaces - must be list of socket-tuples as string +729 ): +
    730 """ +731 starts the web server. +732 """ +733 +734 if interfaces: +735 # if interfaces is specified, it must be tested for rocket parameter correctness +736 # not necessarily completely tested (e.g. content of tuples or ip-format) +737 import types +738 if isinstance(interfaces,types.ListType): +739 for i in interfaces: +740 if not isinstance(i,types.TupleType): +741 raise "Wrong format for rocket interfaces parameter - see http://packages.python.org/rocket/" +742 else: +743 raise "Wrong format for rocket interfaces parameter - see http://packages.python.org/rocket/" +744 +745 if path: +746 # if a path is specified change the global variables so that web2py +747 # runs from there instead of cwd or os.environ['web2py_path'] +748 global web2py_path +749 path = os.path.normpath(path) +750 web2py_path = path +751 global_settings.applications_parent = path +752 os.chdir(path) +753 [add_path_first(p) for p in (path, abspath('site-packages'), "")] +754 +755 save_password(password, port) +756 self.pid_filename = pid_filename +757 if not server_name: +758 server_name = socket.gethostname() +759 logger.info('starting web server...') +760 rocket.SERVER_NAME = server_name +761 sock_list = [ip, port] +762 if not ssl_certificate or not ssl_private_key: +763 logger.info('SSL is off') +764 elif not rocket.ssl: +765 logger.warning('Python "ssl" module unavailable. SSL is OFF') +766 elif not os.path.exists(ssl_certificate): +767 logger.warning('unable to open SSL certificate. SSL is OFF') +768 elif not os.path.exists(ssl_private_key): +769 logger.warning('unable to open SSL private key. SSL is OFF') +770 else: +771 sock_list.extend([ssl_private_key, ssl_certificate]) +772 logger.info('SSL is ON') +773 app_info = {'wsgi_app': appfactory(wsgibase, +774 log_filename, +775 profiler_filename) } +776 +777 self.server = rocket.Rocket(interfaces or tuple(sock_list), +778 method='wsgi', +779 app_info=app_info, +780 min_threads=min_threads, +781 max_threads=max_threads, +782 queue_size=int(request_queue_size), +783 timeout=int(timeout), +784 handle_signals=False, +785 ) +
    786 +787 +
    788 - def start(self): +
    789 """ +790 start the web server +791 """ +792 try: +793 signal.signal(signal.SIGTERM, lambda a, b, s=self: s.stop()) +794 signal.signal(signal.SIGINT, lambda a, b, s=self: s.stop()) +795 except: +796 pass +797 write_file(self.pid_filename, str(os.getpid())) +798 self.server.start() +
    799 +
    800 - def stop(self, stoplogging=False): +
    801 """ +802 stop cron and the web server +803 """ +804 newcron.stopcron() +805 self.server.stop(stoplogging) +806 try: +807 os.unlink(self.pid_filename) +808 except: +809 pass +
    810 +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.main.HttpServer-class.html Index: applications/examples/static/epydoc/web2py.gluon.main.HttpServer-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.main.HttpServer-class.html +++ applications/examples/static/epydoc/web2py.gluon.main.HttpServer-class.html @@ -0,0 +1,309 @@ + + + + + web2py.gluon.main.HttpServer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module main :: + Class HttpServer + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class HttpServer

    source code

    +
    +object --+
    +         |
    +        HttpServer
    +
    + +
    +the web2py web server (Rocket)

    + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + ip='127.0.0.1', + port=8000, + password='', + pid_filename='httpserver.pid', + log_filename='httpserver.log', + profiler_filename=1, + ssl_certificate=1, + ssl_private_key=1, + min_threads=1, + max_threads=1, + server_name=1, + request_queue_size=5, + timeout=10, + shutdown_timeout=1, + path=1, + interfaces=1)
    + starts the web server.
    + source code + +
    + +
    +   + + + + + + +
    start(self)
    + start the web server
    + source code + +
    + +
    +   + + + + + + +
    stop(self, + stoplogging=True)
    + stop cron and the web server
    + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + ip='127.0.0.1', + port=8000, + password='', + pid_filename='httpserver.pid', + log_filename='httpserver.log', + profiler_filename=1, + ssl_certificate=1, + ssl_private_key=1, + min_threads=1, + max_threads=1, + server_name=1, + request_queue_size=5, + timeout=10, + shutdown_timeout=1, + path=1, + interfaces=1) +
    (Constructor) +

    +
    source code  +
    + + starts the web server. +
    +
    Overrides: + object.__init__ +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.myregex-module.html Index: applications/examples/static/epydoc/web2py.gluon.myregex-module.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.myregex-module.html +++ applications/examples/static/epydoc/web2py.gluon.myregex-module.html @@ -0,0 +1,262 @@ + + + + + web2py.gluon.myregex + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module myregex + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Module myregex

    source code

    +This file is part of the web2py Web Framework Copyrighted by Massimo + Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)

    + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Variables[hide private]
    +
    +   + + regex_tables = re.compile(r'(?m)^\w+\.define_table\(\s*[\'"](?... +
    +   + + regex_expose = re.compile(r'(?m)^def\s+(?P<name>(?:[a-zA-Z0-9]... +
    +   + + regex_include = re.compile(r'(?P<all>\{\{\s*include\s+[\'"](?P... +
    +   + + regex_extend = re.compile(r'(?m)^\s*(?P<all>\{\{\s*extend\s+[\... +
    + + + + + + +
    + + + + + +
    Variables Details[hide private]
    +
    + +
    + +
    +

    regex_tables

    + +
    +
    +
    +
    Value:
    +
    +re.compile(r'(?m)^\w+\.define_table\(\s*[\'"](?P<name>[\w_]+)[\'"]')
    +
    +
    +
    +
    +
    + +
    + +
    +

    regex_expose

    + +
    +
    +
    +
    Value:
    +
    +re.compile(r'(?m)^def\s+(?P<name>(?:[a-zA-Z0-9]\w*)|(?:_[a-zA-Z0-9]\w*\
    +))\(\)\s*:')
    +
    +
    +
    +
    +
    + +
    + +
    +

    regex_include

    + +
    +
    +
    +
    Value:
    +
    +re.compile(r'(?P<all>\{\{\s*include\s+[\'"](?P<name>[^\'"]*)[\'"]\s*\}\
    +\})')
    +
    +
    +
    +
    +
    + +
    + +
    +

    regex_extend

    + +
    +
    +
    +
    Value:
    +
    +re.compile(r'(?m)^\s*(?P<all>\{\{\s*extend\s+[\'"](?P<name>[^\'"]+)[\'\
    +"]\s*\}\})')
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.myregex-pysrc.html Index: applications/examples/static/epydoc/web2py.gluon.myregex-pysrc.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.myregex-pysrc.html +++ applications/examples/static/epydoc/web2py.gluon.myregex-pysrc.html @@ -0,0 +1,150 @@ + + + + + web2py.gluon.myregex + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module myregex + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Module web2py.gluon.myregex

    +
    + 1  #!/usr/bin/env python 
    + 2  # -*- coding: utf-8 -*- 
    + 3   
    + 4  """ 
    + 5  This file is part of the web2py Web Framework 
    + 6  Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> 
    + 7  License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) 
    + 8  """ 
    + 9   
    +10  import re 
    +11   
    +12  # pattern to find defined tables 
    +13   
    +14  regex_tables = re.compile(\ 
    +15      """^[\w]+\.define_table\(\s*[\'\"](?P<name>[\w_]+)[\'\"]""", 
    +16      flags=re.M) 
    +17   
    +18  # pattern to find exposed functions in controller 
    +19   
    +20  regex_expose = re.compile(\ 
    +21      '^def\s+(?P<name>(?:[a-zA-Z0-9]\w*)|(?:_[a-zA-Z0-9]\w*))\(\)\s*:', 
    +22      flags=re.M) 
    +23   
    +24  regex_include = re.compile(\ 
    +25      '(?P<all>\{\{\s*include\s+[\'"](?P<name>[^\'"]*)[\'"]\s*\}\})') 
    +26   
    +27  regex_extend = re.compile(\ 
    +28      '^\s*(?P<all>\{\{\s*extend\s+[\'"](?P<name>[^\'"]+)[\'"]\s*\}\})',re.MULTILINE) 
    +29   
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.newcron-module.html Index: applications/examples/static/epydoc/web2py.gluon.newcron-module.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.newcron-module.html +++ applications/examples/static/epydoc/web2py.gluon.newcron-module.html @@ -0,0 +1,289 @@ + + + + + web2py.gluon.newcron + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module newcron + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Module newcron

    source code

    +Created by Attila Csipa <web2py@csipa.in.rs> Modified by Massimo + Di Pierro <mdipierro@cs.depaul.edu>

    + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Classes[hide private]
    +
    +   + + extcron +
    +   + + hardcron +
    +   + + softcron +
    +   + + Token +
    +   + + cronlauncher +
    + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Functions[hide private]
    +
    +   + + + + + + +
    stopcron()
    + graceful shutdown of cron
    + source code + +
    + +
    +   + + + + + + +
    rangetolist(s, + period='min') + source code + +
    + +
    +   + + + + + + +
    parsecronline(line) + source code + +
    + +
    +   + + + + + + +
    crondance(applications_parent, + ctype='soft', + startup=True) + source code + +
    + +
    + + + + + + + + + + + + +
    + + + + + +
    Variables[hide private]
    +
    +   + + logger = logging.getLogger("web2py.cron") +
    +   + + _cron_stopping = True +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.newcron-pysrc.html Index: applications/examples/static/epydoc/web2py.gluon.newcron-pysrc.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.newcron-pysrc.html +++ applications/examples/static/epydoc/web2py.gluon.newcron-pysrc.html @@ -0,0 +1,1204 @@ + + + + + web2py.gluon.newcron + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module newcron + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Module web2py.gluon.newcron

    +
    +  1  #!/usr/bin/env python 
    +  2  # -*- coding: utf-8 -*- 
    +  3   
    +  4  """ 
    +  5  Created by Attila Csipa <web2py@csipa.in.rs> 
    +  6  Modified by Massimo Di Pierro <mdipierro@cs.depaul.edu> 
    +  7  """ 
    +  8   
    +  9  import sys 
    + 10  import os 
    + 11  import threading 
    + 12  import logging 
    + 13  import time 
    + 14  import sched 
    + 15  import re 
    + 16  import datetime 
    + 17  import platform 
    + 18  import portalocker 
    + 19  import fileutils 
    + 20  import cPickle 
    + 21  from settings import global_settings 
    + 22   
    + 23  logger = logging.getLogger("web2py.cron") 
    + 24  _cron_stopping = False 
    + 25   
    +
    26 -def stopcron(): +
    27 "graceful shutdown of cron" + 28 global _cron_stopping + 29 _cron_stopping = True +
    30 +
    31 -class extcron(threading.Thread): +
    32 +
    33 - def __init__(self, applications_parent): +
    34 threading.Thread.__init__(self) + 35 self.setDaemon(False) + 36 self.path = applications_parent + 37 crondance(self.path, 'external', startup=True) +
    38 +
    39 - def run(self): +
    40 if not _cron_stopping: + 41 logger.debug('external cron invocation') + 42 crondance(self.path, 'external', startup=False) +
    43 +
    44 -class hardcron(threading.Thread): +
    45 +
    46 - def __init__(self, applications_parent): +
    47 threading.Thread.__init__(self) + 48 self.setDaemon(True) + 49 self.path = applications_parent + 50 crondance(self.path, 'hard', startup=True) +
    51 +
    52 - def launch(self): +
    53 if not _cron_stopping: + 54 logger.debug('hard cron invocation') + 55 crondance(self.path, 'hard', startup = False) +
    56 +
    57 - def run(self): +
    58 s = sched.scheduler(time.time, time.sleep) + 59 logger.info('Hard cron daemon started') + 60 while not _cron_stopping: + 61 now = time.time() + 62 s.enter(60 - now % 60, 1, self.launch, ()) + 63 s.run() +
    64 +
    65 -class softcron(threading.Thread): +
    66 +
    67 - def __init__(self, applications_parent): +
    68 threading.Thread.__init__(self) + 69 self.path = applications_parent + 70 crondance(self.path, 'soft', startup=True) +
    71 +
    72 - def run(self): +
    73 if not _cron_stopping: + 74 logger.debug('soft cron invocation') + 75 crondance(self.path, 'soft', startup=False) +
    76 +
    77 -class Token(object): +
    78 +
    79 - def __init__(self,path): +
    80 self.path = os.path.join(path, 'cron.master') + 81 if not os.path.exists(self.path): + 82 fileutils.write_file(self.path, '', 'wb') + 83 self.master = None + 84 self.now = time.time() +
    85 +
    86 - def acquire(self,startup=False): +
    87 """ + 88 returns the time when the lock is acquired or + 89 None if cron already running + 90 + 91 lock is implemented by writing a pickle (start, stop) in cron.master + 92 start is time when cron job starts and stop is time when cron completed + 93 stop == 0 if job started but did not yet complete + 94 if a cron job started within less than 60 seconds, acquire returns None + 95 if a cron job started before 60 seconds and did not stop, + 96 a warning is issue "Stale cron.master detected" + 97 """ + 98 if portalocker.LOCK_EX == None: + 99 logger.warning('WEB2PY CRON: Disabled because no file locking') +100 return None +101 self.master = open(self.path,'rb+') +102 try: +103 ret = None +104 portalocker.lock(self.master,portalocker.LOCK_EX) +105 try: +106 (start, stop) = cPickle.load(self.master) +107 except: +108 (start, stop) = (0, 1) +109 if startup or self.now - start > 59.99: +110 ret = self.now +111 if not stop: +112 # this happens if previous cron job longer than 1 minute +113 logger.warning('WEB2PY CRON: Stale cron.master detected') +114 logger.debug('WEB2PY CRON: Acquiring lock') +115 self.master.seek(0) +116 cPickle.dump((self.now,0),self.master) +117 finally: +118 portalocker.unlock(self.master) +119 if not ret: +120 # do this so no need to release +121 self.master.close() +122 return ret +
    123 +
    124 - def release(self): +
    125 """ +126 this function writes into cron.master the time when cron job +127 was completed +128 """ +129 if not self.master.closed: +130 portalocker.lock(self.master,portalocker.LOCK_EX) +131 logger.debug('WEB2PY CRON: Releasing cron lock') +132 self.master.seek(0) +133 (start, stop) = cPickle.load(self.master) +134 if start == self.now: # if this is my lock +135 self.master.seek(0) +136 cPickle.dump((self.now,time.time()),self.master) +137 portalocker.unlock(self.master) +138 self.master.close() +
    139 +140 +
    141 -def rangetolist(s, period='min'): +
    142 retval = [] +143 if s.startswith('*'): +144 if period == 'min': +145 s = s.replace('*', '0-59', 1) +146 elif period == 'hr': +147 s = s.replace('*', '0-23', 1) +148 elif period == 'dom': +149 s = s.replace('*', '1-31', 1) +150 elif period == 'mon': +151 s = s.replace('*', '1-12', 1) +152 elif period == 'dow': +153 s = s.replace('*', '0-6', 1) +154 m = re.compile(r'(\d+)-(\d+)/(\d+)') +155 match = m.match(s) +156 if match: +157 for i in range(int(match.group(1)), int(match.group(2)) + 1): +158 if i % int(match.group(3)) == 0: +159 retval.append(i) +160 return retval +
    161 +162 +
    163 -def parsecronline(line): +
    164 task = {} +165 if line.startswith('@reboot'): +166 line=line.replace('@reboot', '-1 * * * *') +167 elif line.startswith('@yearly'): +168 line=line.replace('@yearly', '0 0 1 1 *') +169 elif line.startswith('@annually'): +170 line=line.replace('@annually', '0 0 1 1 *') +171 elif line.startswith('@monthly'): +172 line=line.replace('@monthly', '0 0 1 * *') +173 elif line.startswith('@weekly'): +174 line=line.replace('@weekly', '0 0 * * 0') +175 elif line.startswith('@daily'): +176 line=line.replace('@daily', '0 0 * * *') +177 elif line.startswith('@midnight'): +178 line=line.replace('@midnight', '0 0 * * *') +179 elif line.startswith('@hourly'): +180 line=line.replace('@hourly', '0 * * * *') +181 params = line.strip().split(None, 6) +182 if len(params) < 7: +183 return None +184 daysofweek={'sun':0,'mon':1,'tue':2,'wed':3,'thu':4,'fri':5,'sat':6} +185 for (s, id) in zip(params[:5], ['min', 'hr', 'dom', 'mon', 'dow']): +186 if not s in [None, '*']: +187 task[id] = [] +188 vals = s.split(',') +189 for val in vals: +190 if val != '-1' and '-' in val and '/' not in val: +191 val = '%s/1' % val +192 if '/' in val: +193 task[id] += rangetolist(val, id) +194 elif val.isdigit() or val=='-1': +195 task[id].append(int(val)) +196 elif id=='dow' and val[:3].lower() in daysofweek: +197 task[id].append(daysofweek(val[:3].lower())) +198 task['user'] = params[5] +199 task['cmd'] = params[6] +200 return task +
    201 +202 +
    203 -class cronlauncher(threading.Thread): +
    204 +
    205 - def __init__(self, cmd, shell=True): +
    206 threading.Thread.__init__(self) +207 if platform.system() == 'Windows': +208 shell = False +209 elif isinstance(cmd,list): +210 cmd = ' '.join(cmd) +211 self.cmd = cmd +212 self.shell = shell +
    213 +
    214 - def run(self): +
    215 import subprocess +216 proc = subprocess.Popen(self.cmd, +217 stdin=subprocess.PIPE, +218 stdout=subprocess.PIPE, +219 stderr=subprocess.PIPE, +220 shell=self.shell) +221 (stdoutdata,stderrdata) = proc.communicate() +222 if proc.returncode != 0: +223 logger.warning( +224 'WEB2PY CRON Call returned code %s:\n%s' % \ +225 (proc.returncode, stdoutdata+stderrdata)) +226 else: +227 logger.debug('WEB2PY CRON Call returned success:\n%s' \ +228 % stdoutdata) +
    229 +
    230 -def crondance(applications_parent, ctype='soft', startup=False): +
    231 apppath = os.path.join(applications_parent,'applications') +232 cron_path = os.path.join(apppath,'admin','cron') +233 token = Token(cron_path) +234 cronmaster = token.acquire(startup=startup) +235 if not cronmaster: +236 return +237 now_s = time.localtime() +238 checks=(('min',now_s.tm_min), +239 ('hr',now_s.tm_hour), +240 ('mon',now_s.tm_mon), +241 ('dom',now_s.tm_mday), +242 ('dow',(now_s.tm_wday+1)%7)) +243 +244 apps = [x for x in os.listdir(apppath) +245 if os.path.isdir(os.path.join(apppath, x))] +246 +247 for app in apps: +248 if _cron_stopping: +249 break; +250 apath = os.path.join(apppath,app) +251 cronpath = os.path.join(apath, 'cron') +252 crontab = os.path.join(cronpath, 'crontab') +253 if not os.path.exists(crontab): +254 continue +255 try: +256 cronlines = fileutils.readlines_file(crontab, 'rt') +257 lines = [x.strip() for x in cronlines if x.strip() and not x.strip().startswith('#')] +258 tasks = [parsecronline(cline) for cline in lines] +259 except Exception, e: +260 logger.error('WEB2PY CRON: crontab read error %s' % e) +261 continue +262 +263 for task in tasks: +264 if _cron_stopping: +265 break; +266 commands = [sys.executable] +267 w2p_path = fileutils.abspath('web2py.py', gluon=True) +268 if os.path.exists(w2p_path): +269 commands.append(w2p_path) +270 if global_settings.applications_parent != global_settings.gluon_parent: +271 commands.extend(('-f', global_settings.applications_parent)) +272 citems = [(k in task and not v in task[k]) for k,v in checks] +273 task_min= task.get('min',[]) +274 if not task: +275 continue +276 elif not startup and task_min == [-1]: +277 continue +278 elif task_min != [-1] and reduce(lambda a,b: a or b, citems): +279 continue +280 logger.info('WEB2PY CRON (%s): %s executing %s in %s at %s' \ +281 % (ctype, app, task.get('cmd'), +282 os.getcwd(), datetime.datetime.now())) +283 action, command, models = False, task['cmd'], '' +284 if command.startswith('**'): +285 (action,models,command) = (True,'',command[2:]) +286 elif command.startswith('*'): +287 (action,models,command) = (True,'-M',command[1:]) +288 else: +289 action=False +290 if action and command.endswith('.py'): +291 commands.extend(('-J', # cron job +292 models, # import models? +293 '-S', app, # app name +294 '-a', '"<recycle>"', # password +295 '-R', command)) # command +296 shell = True +297 elif action: +298 commands.extend(('-J', # cron job +299 models, # import models? +300 '-S', app+'/'+command, # app name +301 '-a', '"<recycle>"')) # password +302 shell = True +303 else: +304 commands = command +305 shell = False +306 try: +307 cronlauncher(commands, shell=shell).start() +308 except Exception, e: +309 logger.warning( +310 'WEB2PY CRON: Execution error for %s: %s' \ +311 % (task.get('cmd'), e)) +312 token.release() +
    313 +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.newcron.Token-class.html Index: applications/examples/static/epydoc/web2py.gluon.newcron.Token-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.newcron.Token-class.html +++ applications/examples/static/epydoc/web2py.gluon.newcron.Token-class.html @@ -0,0 +1,309 @@ + + + + + web2py.gluon.newcron.Token + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module newcron :: + Class Token + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class Token

    source code

    +
    +object --+
    +         |
    +        Token
    +
    + +
    + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + path)
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    acquire(self, + startup=True)
    + returns the time when the lock is acquired or None if cron already + running
    + source code + +
    + +
    +   + + + + + + +
    release(self)
    + this function writes into cron.master the time when cron job was + completed
    + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + path) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    acquire(self, + startup=True) +

    +
    source code  +
    + +

    returns the time when the lock is acquired or None if cron already + running

    + lock is implemented by writing a pickle (start, stop) in cron.master + start is time when cron job starts and stop is time when cron completed + stop == 0 if job started but did not yet complete if a cron job started + within less than 60 seconds, acquire returns None if a cron job started + before 60 seconds and did not stop, a warning is issue "Stale + cron.master detected" +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.newcron.cronlauncher-class.html Index: applications/examples/static/epydoc/web2py.gluon.newcron.cronlauncher-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.newcron.cronlauncher-class.html +++ applications/examples/static/epydoc/web2py.gluon.newcron.cronlauncher-class.html @@ -0,0 +1,300 @@ + + + + + web2py.gluon.newcron.cronlauncher + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module newcron :: + Class cronlauncher + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class cronlauncher

    source code

    +
    +        object --+        
    +                 |        
    +threading._Verbose --+    
    +                     |    
    +      threading.Thread --+
    +                         |
    +                        cronlauncher
    +
    + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + cmd, + shell=True) + source code + +
    + +
    +   + + + + + + +
    run(self) + source code + +
    + +
    +

    Inherited from threading.Thread: + __repr__, + getName, + isAlive, + isDaemon, + join, + setDaemon, + setName, + start +

    +

    Inherited from threading.Thread (private): + _set_daemon +

    +

    Inherited from threading._Verbose (private): + _note +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + cmd, + shell=True) +
    (Constructor) +

    +
    source code  +
    + + +
    +
    Overrides: + threading.Thread.__init__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    run(self) +

    +
    source code  +
    + + +
    +
    Overrides: + threading.Thread.run +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.newcron.extcron-class.html Index: applications/examples/static/epydoc/web2py.gluon.newcron.extcron-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.newcron.extcron-class.html +++ applications/examples/static/epydoc/web2py.gluon.newcron.extcron-class.html @@ -0,0 +1,298 @@ + + + + + web2py.gluon.newcron.extcron + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module newcron :: + Class extcron + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class extcron

    source code

    +
    +        object --+        
    +                 |        
    +threading._Verbose --+    
    +                     |    
    +      threading.Thread --+
    +                         |
    +                        extcron
    +
    + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + applications_parent) + source code + +
    + +
    +   + + + + + + +
    run(self) + source code + +
    + +
    +

    Inherited from threading.Thread: + __repr__, + getName, + isAlive, + isDaemon, + join, + setDaemon, + setName, + start +

    +

    Inherited from threading.Thread (private): + _set_daemon +

    +

    Inherited from threading._Verbose (private): + _note +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + applications_parent) +
    (Constructor) +

    +
    source code  +
    + + +
    +
    Overrides: + threading.Thread.__init__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    run(self) +

    +
    source code  +
    + + +
    +
    Overrides: + threading.Thread.run +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.newcron.hardcron-class.html Index: applications/examples/static/epydoc/web2py.gluon.newcron.hardcron-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.newcron.hardcron-class.html +++ applications/examples/static/epydoc/web2py.gluon.newcron.hardcron-class.html @@ -0,0 +1,314 @@ + + + + + web2py.gluon.newcron.hardcron + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module newcron :: + Class hardcron + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class hardcron

    source code

    +
    +        object --+        
    +                 |        
    +threading._Verbose --+    
    +                     |    
    +      threading.Thread --+
    +                         |
    +                        hardcron
    +
    + +
    + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + applications_parent) + source code + +
    + +
    +   + + + + + + +
    launch(self) + source code + +
    + +
    +   + + + + + + +
    run(self) + source code + +
    + +
    +

    Inherited from threading.Thread: + __repr__, + getName, + isAlive, + isDaemon, + join, + setDaemon, + setName, + start +

    +

    Inherited from threading.Thread (private): + _set_daemon +

    +

    Inherited from threading._Verbose (private): + _note +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + applications_parent) +
    (Constructor) +

    +
    source code  +
    + + +
    +
    Overrides: + threading.Thread.__init__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    run(self) +

    +
    source code  +
    + + +
    +
    Overrides: + threading.Thread.run +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.newcron.softcron-class.html Index: applications/examples/static/epydoc/web2py.gluon.newcron.softcron-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.newcron.softcron-class.html +++ applications/examples/static/epydoc/web2py.gluon.newcron.softcron-class.html @@ -0,0 +1,298 @@ + + + + + web2py.gluon.newcron.softcron + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module newcron :: + Class softcron + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class softcron

    source code

    +
    +        object --+        
    +                 |        
    +threading._Verbose --+    
    +                     |    
    +      threading.Thread --+
    +                         |
    +                        softcron
    +
    + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + applications_parent) + source code + +
    + +
    +   + + + + + + +
    run(self) + source code + +
    + +
    +

    Inherited from threading.Thread: + __repr__, + getName, + isAlive, + isDaemon, + join, + setDaemon, + setName, + start +

    +

    Inherited from threading.Thread (private): + _set_daemon +

    +

    Inherited from threading._Verbose (private): + _note +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + applications_parent) +
    (Constructor) +

    +
    source code  +
    + + +
    +
    Overrides: + threading.Thread.__init__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    run(self) +

    +
    source code  +
    + + +
    +
    Overrides: + threading.Thread.run +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.portalocker-module.html Index: applications/examples/static/epydoc/web2py.gluon.portalocker-module.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.portalocker-module.html +++ applications/examples/static/epydoc/web2py.gluon.portalocker-module.html @@ -0,0 +1,268 @@ + + + + + web2py.gluon.portalocker + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module portalocker + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Module portalocker

    source code

    +
    +
    +Cross-platform (posix/nt) API for flock-style file locking.
    +
    +Synopsis:
    +
    +   import portalocker
    +   file = open("somefile", "r+")
    +   portalocker.lock(file, portalocker.LOCK_EX)
    +   file.seek(12)
    +   file.write("foo")
    +   file.close()
    +
    +If you know what you're doing, you may choose to
    +
    +   portalocker.unlock(file)
    +
    +before closing the file, but why?
    +
    +Methods:
    +
    +   lock( file, flags )
    +   unlock( file )
    +
    +Constants:
    +
    +   LOCK_EX
    +   LOCK_SH
    +   LOCK_NB
    +
    +I learned the win32 technique for locking files from sample code
    +provided by John Nielsen <nielsenjf@my-deja.com> in the documentation
    +that accompanies the win32 modules.
    +
    +Author: Jonathan Feinberg <jdf@pobox.com>
    +Version: $Id: portalocker.py,v 1.3 2001/05/29 18:47:55 Administrator Exp $
    +
    +


    + + + + + + + + + + + + + +
    + + + + + +
    Functions[hide private]
    +
    +   + + + + + + +
    lock(file, + flags) + source code + +
    + +
    +   + + + + + + +
    unlock(file) + source code + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Variables[hide private]
    +
    +   + + logger = logging.getLogger("web2py") +
    +   + + os_locking = 'posix' +
    +   + + __overlapped = pywintypes.OVERLAPPED() +
    +   + + LOCK_EX = 1
    + PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    +   + + LOCK_SH = 1
    + PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    +   + + LOCK_NB = 1
    + PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.portalocker-pysrc.html Index: applications/examples/static/epydoc/web2py.gluon.portalocker-pysrc.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.portalocker-pysrc.html +++ applications/examples/static/epydoc/web2py.gluon.portalocker-pysrc.html @@ -0,0 +1,298 @@ + + + + + web2py.gluon.portalocker + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module portalocker + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Module web2py.gluon.portalocker

    +
    +  1  #!/usr/bin/env python 
    +  2  # -*- coding: utf-8 -*- 
    +  3  # portalocker.py - Cross-platform (posix/nt) API for flock-style file locking. 
    +  4  #                  Requires python 1.5.2 or better. 
    +  5   
    +  6  """ 
    +  7  Cross-platform (posix/nt) API for flock-style file locking. 
    +  8   
    +  9  Synopsis: 
    + 10   
    + 11     import portalocker 
    + 12     file = open(\"somefile\", \"r+\") 
    + 13     portalocker.lock(file, portalocker.LOCK_EX) 
    + 14     file.seek(12) 
    + 15     file.write(\"foo\") 
    + 16     file.close() 
    + 17   
    + 18  If you know what you're doing, you may choose to 
    + 19   
    + 20     portalocker.unlock(file) 
    + 21   
    + 22  before closing the file, but why? 
    + 23   
    + 24  Methods: 
    + 25   
    + 26     lock( file, flags ) 
    + 27     unlock( file ) 
    + 28   
    + 29  Constants: 
    + 30   
    + 31     LOCK_EX 
    + 32     LOCK_SH 
    + 33     LOCK_NB 
    + 34   
    + 35  I learned the win32 technique for locking files from sample code 
    + 36  provided by John Nielsen <nielsenjf@my-deja.com> in the documentation 
    + 37  that accompanies the win32 modules. 
    + 38   
    + 39  Author: Jonathan Feinberg <jdf@pobox.com> 
    + 40  Version: $Id: portalocker.py,v 1.3 2001/05/29 18:47:55 Administrator Exp $ 
    + 41  """ 
    + 42   
    + 43  import os 
    + 44  import logging 
    + 45  import platform 
    + 46  logger = logging.getLogger("web2py") 
    + 47   
    + 48  os_locking = None 
    + 49  try: 
    + 50      import fcntl 
    + 51      os_locking = 'posix' 
    + 52  except: 
    + 53      pass 
    + 54  try: 
    + 55      import win32con 
    + 56      import win32file 
    + 57      import pywintypes 
    + 58      os_locking = 'windows' 
    + 59  except: 
    + 60      pass 
    + 61   
    + 62  if os_locking == 'windows': 
    + 63      LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK 
    + 64      LOCK_SH = 0  # the default 
    + 65      LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY 
    + 66   
    + 67      # is there any reason not to reuse the following structure? 
    + 68   
    + 69      __overlapped = pywintypes.OVERLAPPED() 
    + 70   
    +
    71 - def lock(file, flags): +
    72 hfile = win32file._get_osfhandle(file.fileno()) + 73 win32file.LockFileEx(hfile, flags, 0, 0x7fff0000, __overlapped) +
    74 +
    75 - def unlock(file): +
    76 hfile = win32file._get_osfhandle(file.fileno()) + 77 win32file.UnlockFileEx(hfile, 0, 0x7fff0000, __overlapped) +
    78 + 79 + 80 elif os_locking == 'posix': + 81 LOCK_EX = fcntl.LOCK_EX + 82 LOCK_SH = fcntl.LOCK_SH + 83 LOCK_NB = fcntl.LOCK_NB + 84 +
    85 - def lock(file, flags): +
    86 fcntl.flock(file.fileno(), flags) +
    87 +
    88 - def unlock(file): +
    89 fcntl.flock(file.fileno(), fcntl.LOCK_UN) +
    90 + 91 + 92 else: + 93 if platform.system() == 'Windows': + 94 logger.error('no file locking, you must install the win32 extensions from: http://sourceforge.net/projects/pywin32/files/') + 95 else: + 96 logger.debug('no file locking, this will cause problems') + 97 + 98 LOCK_EX = None + 99 LOCK_SH = None +100 LOCK_NB = None +101 +
    102 - def lock(file, flags): +
    103 pass +
    104 +
    105 - def unlock(file): +
    106 pass +
    107 +108 +109 if __name__ == '__main__': +110 from time import time, strftime, localtime +111 import sys +112 +113 log = open('log.txt', 'a+') +114 lock(log, LOCK_EX) +115 +116 timestamp = strftime('%m/%d/%Y %H:%M:%S\n', localtime(time())) +117 log.write(timestamp) +118 +119 print 'Wrote lines. Hit enter to release lock.' +120 dummy = sys.stdin.readline() +121 +122 log.close() +123 +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.reserved_sql_keywords-module.html Index: applications/examples/static/epydoc/web2py.gluon.reserved_sql_keywords-module.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.reserved_sql_keywords-module.html +++ applications/examples/static/epydoc/web2py.gluon.reserved_sql_keywords-module.html @@ -0,0 +1,774 @@ + + + + + web2py.gluon.reserved_sql_keywords + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module reserved_sql_keywords + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Module reserved_sql_keywords

    source code

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Variables[hide private]
    +
    +   + + __author__ = 'Thadeus Burgess <thadeusb@thadeusb.com>' +
    +   + + COMMON = set(['ALTER', 'AND', 'AS', 'BY', 'CASE', 'CREATE', 'D... +
    +   + + POSTGRESQL = set(['ALL', 'ANALYSE', 'ANALYZE', 'AND', 'ANY', '... +
    +   + + POSTGRESQL_NONRESERVED = set(['A', 'ABORT', 'ABS', 'ABSENT', '... +
    +   + + FIREBIRD = set(['ABS', 'ACTIVE', 'ADMIN', 'AFTER', 'ASCENDING'... +
    +   + + FIREBIRD_NONRESERVED = set(['ABS', 'ACCENT', 'ACOS', 'ALWAYS',... +
    +   + + MYSQL = set(['ACCESSIBLE', 'ADD', 'ALL', 'ALTER', 'ANALYZE', '... +
    +   + + MSSQL = set(['ADD', 'ALL', 'ALTER', 'AND', 'ANY', 'AS', 'ASC',... +
    +   + + ORACLE = set(['ACCESS', 'ADD', 'ALL', 'ALTER', 'AND', 'ANY', '... +
    +   + + SQLITE = set(['ABORT', 'ACTION', 'ADD', 'AFTER', 'ALL', 'ALTER... +
    +   + + JDBCSQLITE = set(['ABORT', 'ACTION', 'ADD', 'AFTER', 'ALL', 'A... +
    +   + + JDBCPOSTGRESQL = set(['ALTER', 'AND', 'AS', 'BY', 'CASE', 'CRE... +
    +   + + INGRES = set(['ALTER', 'AND', 'AS', 'BY', 'CASE', 'CREATE', 'D... +
    +   + + INFORMIX = set(['ALTER', 'AND', 'AS', 'BY', 'CASE', 'CREATE', ... +
    +   + + DB2 = set(['ALTER', 'AND', 'AS', 'BY', 'CASE', 'CREATE', 'DELE... +
    +   + + ADAPTERS = {'all': set(['A', 'ABORT', 'ABS', 'ABSENT', 'ABSOLU... +
    + + + + + + +
    + + + + + +
    Variables Details[hide private]
    +
    + +
    + +
    +

    COMMON

    + +
    +
    +
    +
    Value:
    +
    +set(['ALTER',
    +     'AND',
    +     'AS',
    +     'BY',
    +     'CASE',
    +     'CREATE',
    +     'DELETE',
    +     'DISTINCT',
    +...
    +
    +
    +
    +
    +
    + +
    + +
    +

    POSTGRESQL

    + +
    +
    +
    +
    Value:
    +
    +set(['ALL',
    +     'ANALYSE',
    +     'ANALYZE',
    +     'AND',
    +     'ANY',
    +     'ARRAY',
    +     'AS',
    +     'ASC',
    +...
    +
    +
    +
    +
    +
    + +
    + +
    +

    POSTGRESQL_NONRESERVED

    + +
    +
    +
    +
    Value:
    +
    +set(['A',
    +     'ABORT',
    +     'ABS',
    +     'ABSENT',
    +     'ABSOLUTE',
    +     'ACCESS',
    +     'ACCORDING',
    +     'ACTION',
    +...
    +
    +
    +
    +
    +
    + +
    + +
    +

    FIREBIRD

    + +
    +
    +
    +
    Value:
    +
    +set(['ABS',
    +     'ACTIVE',
    +     'ADMIN',
    +     'AFTER',
    +     'ASCENDING',
    +     'AUTO',
    +     'AUTODDL',
    +     'BASED',
    +...
    +
    +
    +
    +
    +
    + +
    + +
    +

    FIREBIRD_NONRESERVED

    + +
    +
    +
    +
    Value:
    +
    +set(['ABS',
    +     'ACCENT',
    +     'ACOS',
    +     'ALWAYS',
    +     'ASCII_CHAR',
    +     'ASCII_VAL',
    +     'ASIN',
    +     'ATAN',
    +...
    +
    +
    +
    +
    +
    + +
    + +
    +

    MYSQL

    + +
    +
    +
    +
    Value:
    +
    +set(['ACCESSIBLE',
    +     'ADD',
    +     'ALL',
    +     'ALTER',
    +     'ANALYZE',
    +     'AND',
    +     'AS',
    +     'ASC',
    +...
    +
    +
    +
    +
    +
    + +
    + +
    +

    MSSQL

    + +
    +
    +
    +
    Value:
    +
    +set(['ADD',
    +     'ALL',
    +     'ALTER',
    +     'AND',
    +     'ANY',
    +     'AS',
    +     'ASC',
    +     'AUTHORIZATION',
    +...
    +
    +
    +
    +
    +
    + +
    + +
    +

    ORACLE

    + +
    +
    +
    +
    Value:
    +
    +set(['ACCESS',
    +     'ADD',
    +     'ALL',
    +     'ALTER',
    +     'AND',
    +     'ANY',
    +     'AS',
    +     'ASC',
    +...
    +
    +
    +
    +
    +
    + +
    + +
    +

    SQLITE

    + +
    +
    +
    +
    Value:
    +
    +set(['ABORT',
    +     'ACTION',
    +     'ADD',
    +     'AFTER',
    +     'ALL',
    +     'ALTER',
    +     'ANALYZE',
    +     'AND',
    +...
    +
    +
    +
    +
    +
    + +
    + +
    +

    JDBCSQLITE

    + +
    +
    +
    +
    Value:
    +
    +set(['ABORT',
    +     'ACTION',
    +     'ADD',
    +     'AFTER',
    +     'ALL',
    +     'ALTER',
    +     'ANALYZE',
    +     'AND',
    +...
    +
    +
    +
    +
    +
    + +
    + +
    +

    JDBCPOSTGRESQL

    + +
    +
    +
    +
    Value:
    +
    +set(['ALTER',
    +     'AND',
    +     'AS',
    +     'BY',
    +     'CASE',
    +     'CREATE',
    +     'DELETE',
    +     'DISTINCT',
    +...
    +
    +
    +
    +
    +
    + +
    + +
    +

    INGRES

    + +
    +
    +
    +
    Value:
    +
    +set(['ALTER',
    +     'AND',
    +     'AS',
    +     'BY',
    +     'CASE',
    +     'CREATE',
    +     'DELETE',
    +     'DISTINCT',
    +...
    +
    +
    +
    +
    +
    + +
    + +
    +

    INFORMIX

    + +
    +
    +
    +
    Value:
    +
    +set(['ALTER',
    +     'AND',
    +     'AS',
    +     'BY',
    +     'CASE',
    +     'CREATE',
    +     'DELETE',
    +     'DISTINCT',
    +...
    +
    +
    +
    +
    +
    + +
    + +
    +

    DB2

    + +
    +
    +
    +
    Value:
    +
    +set(['ALTER',
    +     'AND',
    +     'AS',
    +     'BY',
    +     'CASE',
    +     'CREATE',
    +     'DELETE',
    +     'DISTINCT',
    +...
    +
    +
    +
    +
    +
    + +
    + +
    +

    ADAPTERS

    + +
    +
    +
    +
    Value:
    +
    +{'all': set(['A',
    +             'ABORT',
    +             'ABS',
    +             'ABSENT',
    +             'ABSOLUTE',
    +             'ACCENT',
    +             'ACCESS',
    +             'ACCESSIBLE',
    +...
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.reserved_sql_keywords-pysrc.html Index: applications/examples/static/epydoc/web2py.gluon.reserved_sql_keywords-pysrc.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.reserved_sql_keywords-pysrc.html +++ applications/examples/static/epydoc/web2py.gluon.reserved_sql_keywords-pysrc.html @@ -0,0 +1,1838 @@ + + + + + web2py.gluon.reserved_sql_keywords + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module reserved_sql_keywords + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Module web2py.gluon.reserved_sql_keywords

    +
    +   1  # encoding utf-8 
    +   2   
    +   3  __author__ = "Thadeus Burgess <thadeusb@thadeusb.com>" 
    +   4   
    +   5  #    we classify as "non-reserved" those key words that are explicitly known 
    +   6  #    to the parser but are allowed as column or table names. Some key words 
    +   7  #    that are otherwise non-reserved cannot be used as function or data type n 
    +   8  #    ames and are in the nonreserved list. (Most of these words represent 
    +   9  #    built-in functions or data types with special syntax. The function 
    +  10  #    or type is still available but it cannot be redefined by the user.) 
    +  11  #    Labeled "reserved" are those tokens that are not allowed as column or 
    +  12  #    table names. Some reserved key words are allowable as names for 
    +  13  #    functions or data typesself. 
    +  14   
    +  15  # Note at the bottom of the list is a dict containing references to the 
    +  16  # tuples, and also if you add a list don't forget to remove its default 
    +  17  # set of COMMON. 
    +  18   
    +  19  # Keywords that are adapter specific. Such as a list of "postgresql" 
    +  20  # or "mysql" keywords 
    +  21   
    +  22  # These are keywords that are common to all SQL dialects, and should 
    +  23  # never be used as a table or column. Even if you use one of these 
    +  24  # the cursor will throw an OperationalError for the SQL syntax. 
    +  25  COMMON = set(( 
    +  26      'SELECT', 
    +  27      'INSERT', 
    +  28      'DELETE', 
    +  29      'UPDATE', 
    +  30      'DROP', 
    +  31      'CREATE', 
    +  32      'ALTER', 
    +  33   
    +  34      'WHERE', 
    +  35      'FROM', 
    +  36      'INNER', 
    +  37      'JOIN', 
    +  38      'AND', 
    +  39      'OR', 
    +  40      'LIKE', 
    +  41      'ON', 
    +  42      'IN', 
    +  43      'SET', 
    +  44   
    +  45      'BY', 
    +  46      'GROUP', 
    +  47      'ORDER', 
    +  48      'LEFT', 
    +  49      'OUTER', 
    +  50   
    +  51      'IF', 
    +  52      'END', 
    +  53      'THEN', 
    +  54      'LOOP', 
    +  55      'AS', 
    +  56      'ELSE', 
    +  57      'FOR', 
    +  58   
    +  59      'CASE', 
    +  60      'WHEN', 
    +  61      'MIN', 
    +  62      'MAX', 
    +  63      'DISTINCT', 
    +  64  )) 
    +  65   
    +  66   
    +  67  POSTGRESQL = set(( 
    +  68      'FALSE', 
    +  69      'TRUE', 
    +  70      'ALL', 
    +  71      'ANALYSE', 
    +  72      'ANALYZE', 
    +  73      'AND', 
    +  74      'ANY', 
    +  75      'ARRAY', 
    +  76      'AS', 
    +  77      'ASC', 
    +  78      'ASYMMETRIC', 
    +  79      'AUTHORIZATION', 
    +  80      'BETWEEN', 
    +  81      'BIGINT', 
    +  82      'BINARY', 
    +  83      'BIT', 
    +  84      'BOOLEAN', 
    +  85      'BOTH', 
    +  86      'CASE', 
    +  87      'CAST', 
    +  88      'CHAR', 
    +  89      'CHARACTER', 
    +  90      'CHECK', 
    +  91      'COALESCE', 
    +  92      'COLLATE', 
    +  93      'COLUMN', 
    +  94      'CONSTRAINT', 
    +  95      'CREATE', 
    +  96      'CROSS', 
    +  97      'CURRENT_CATALOG', 
    +  98      'CURRENT_DATE', 
    +  99      'CURRENT_ROLE', 
    + 100      'CURRENT_SCHEMA', 
    + 101      'CURRENT_TIME', 
    + 102      'CURRENT_TIMESTAMP', 
    + 103      'CURRENT_USER', 
    + 104      'DEC', 
    + 105      'DECIMAL', 
    + 106      'DEFAULT', 
    + 107      'DEFERRABLE', 
    + 108      'DESC', 
    + 109      'DISTINCT', 
    + 110      'DO', 
    + 111      'ELSE', 
    + 112      'END', 
    + 113      'EXCEPT', 
    + 114      'EXISTS', 
    + 115      'EXTRACT', 
    + 116      'FETCH', 
    + 117      'FLOAT', 
    + 118      'FOR', 
    + 119      'FOREIGN', 
    + 120      'FREEZE', 
    + 121      'FROM', 
    + 122      'FULL', 
    + 123      'GRANT', 
    + 124      'GREATEST', 
    + 125      'GROUP', 
    + 126      'HAVING', 
    + 127      'ILIKE', 
    + 128      'IN', 
    + 129      'INITIALLY', 
    + 130      'INNER', 
    + 131      'INOUT', 
    + 132      'INT', 
    + 133      'INTEGER', 
    + 134      'INTERSECT', 
    + 135      'INTERVAL', 
    + 136      'INTO', 
    + 137      'IS', 
    + 138      'ISNULL', 
    + 139      'JOIN', 
    + 140      'LEADING', 
    + 141      'LEAST', 
    + 142      'LEFT', 
    + 143      'LIKE', 
    + 144      'LIMIT', 
    + 145      'LOCALTIME', 
    + 146      'LOCALTIMESTAMP', 
    + 147      'NATIONAL', 
    + 148      'NATURAL', 
    + 149      'NCHAR', 
    + 150      'NEW', 
    + 151      'NONE', 
    + 152      'NOT', 
    + 153      'NOTNULL', 
    + 154      'NULL', 
    + 155      'NULLIF', 
    + 156      'NUMERIC', 
    + 157      'OFF', 
    + 158      'OFFSET', 
    + 159      'OLD', 
    + 160      'ON', 
    + 161      'ONLY', 
    + 162      'OR', 
    + 163      'ORDER', 
    + 164      'OUT', 
    + 165      'OUTER', 
    + 166      'OVERLAPS', 
    + 167      'OVERLAY', 
    + 168      'PLACING', 
    + 169      'POSITION', 
    + 170      'PRECISION', 
    + 171      'PRIMARY', 
    + 172      'REAL', 
    + 173      'REFERENCES', 
    + 174      'RETURNING', 
    + 175      'RIGHT', 
    + 176      'ROW', 
    + 177      'SELECT', 
    + 178      'SESSION_USER', 
    + 179      'SETOF', 
    + 180      'SIMILAR', 
    + 181      'SMALLINT', 
    + 182      'SOME', 
    + 183      'SUBSTRING', 
    + 184      'SYMMETRIC', 
    + 185      'TABLE', 
    + 186      'THEN', 
    + 187      'TIME', 
    + 188      'TIMESTAMP', 
    + 189      'TO', 
    + 190      'TRAILING', 
    + 191      'TREAT', 
    + 192      'TRIM', 
    + 193      'UNION', 
    + 194      'UNIQUE', 
    + 195      'USER', 
    + 196      'USING', 
    + 197      'VALUES', 
    + 198      'VARCHAR', 
    + 199      'VARIADIC', 
    + 200      'VERBOSE', 
    + 201      'WHEN', 
    + 202      'WHERE', 
    + 203      'WITH', 
    + 204      'XMLATTRIBUTES', 
    + 205      'XMLCONCAT', 
    + 206      'XMLELEMENT', 
    + 207      'XMLFOREST', 
    + 208      'XMLPARSE', 
    + 209      'XMLPI', 
    + 210      'XMLROOT', 
    + 211      'XMLSERIALIZE', 
    + 212  )) 
    + 213   
    + 214   
    + 215  POSTGRESQL_NONRESERVED = set(( 
    + 216      'A', 
    + 217      'ABORT', 
    + 218      'ABS', 
    + 219      'ABSENT', 
    + 220      'ABSOLUTE', 
    + 221      'ACCESS', 
    + 222      'ACCORDING', 
    + 223      'ACTION', 
    + 224      'ADA', 
    + 225      'ADD', 
    + 226      'ADMIN', 
    + 227      'AFTER', 
    + 228      'AGGREGATE', 
    + 229      'ALIAS', 
    + 230      'ALLOCATE', 
    + 231      'ALSO', 
    + 232      'ALTER', 
    + 233      'ALWAYS', 
    + 234      'ARE', 
    + 235      'ARRAY_AGG', 
    + 236      'ASENSITIVE', 
    + 237      'ASSERTION', 
    + 238      'ASSIGNMENT', 
    + 239      'AT', 
    + 240      'ATOMIC', 
    + 241      'ATTRIBUTE', 
    + 242      'ATTRIBUTES', 
    + 243      'AVG', 
    + 244      'BACKWARD', 
    + 245      'BASE64', 
    + 246      'BEFORE', 
    + 247      'BEGIN', 
    + 248      'BERNOULLI', 
    + 249      'BIT_LENGTH', 
    + 250      'BITVAR', 
    + 251      'BLOB', 
    + 252      'BOM', 
    + 253      'BREADTH', 
    + 254      'BY', 
    + 255      'C', 
    + 256      'CACHE', 
    + 257      'CALL', 
    + 258      'CALLED', 
    + 259      'CARDINALITY', 
    + 260      'CASCADE', 
    + 261      'CASCADED', 
    + 262      'CATALOG', 
    + 263      'CATALOG_NAME', 
    + 264      'CEIL', 
    + 265      'CEILING', 
    + 266      'CHAIN', 
    + 267      'CHAR_LENGTH', 
    + 268      'CHARACTER_LENGTH', 
    + 269      'CHARACTER_SET_CATALOG', 
    + 270      'CHARACTER_SET_NAME', 
    + 271      'CHARACTER_SET_SCHEMA', 
    + 272      'CHARACTERISTICS', 
    + 273      'CHARACTERS', 
    + 274      'CHECKED', 
    + 275      'CHECKPOINT', 
    + 276      'CLASS', 
    + 277      'CLASS_ORIGIN', 
    + 278      'CLOB', 
    + 279      'CLOSE', 
    + 280      'CLUSTER', 
    + 281      'COBOL', 
    + 282      'COLLATION', 
    + 283      'COLLATION_CATALOG', 
    + 284      'COLLATION_NAME', 
    + 285      'COLLATION_SCHEMA', 
    + 286      'COLLECT', 
    + 287      'COLUMN_NAME', 
    + 288      'COLUMNS', 
    + 289      'COMMAND_FUNCTION', 
    + 290      'COMMAND_FUNCTION_CODE', 
    + 291      'COMMENT', 
    + 292      'COMMIT', 
    + 293      'COMMITTED', 
    + 294      'COMPLETION', 
    + 295      'CONCURRENTLY', 
    + 296      'CONDITION', 
    + 297      'CONDITION_NUMBER', 
    + 298      'CONFIGURATION', 
    + 299      'CONNECT', 
    + 300      'CONNECTION', 
    + 301      'CONNECTION_NAME', 
    + 302      'CONSTRAINT_CATALOG', 
    + 303      'CONSTRAINT_NAME', 
    + 304      'CONSTRAINT_SCHEMA', 
    + 305      'CONSTRAINTS', 
    + 306      'CONSTRUCTOR', 
    + 307      'CONTAINS', 
    + 308      'CONTENT', 
    + 309      'CONTINUE', 
    + 310      'CONVERSION', 
    + 311      'CONVERT', 
    + 312      'COPY', 
    + 313      'CORR', 
    + 314      'CORRESPONDING', 
    + 315      'COST', 
    + 316      'COUNT', 
    + 317      'COVAR_POP', 
    + 318      'COVAR_SAMP', 
    + 319      'CREATEDB', 
    + 320      'CREATEROLE', 
    + 321      'CREATEUSER', 
    + 322      'CSV', 
    + 323      'CUBE', 
    + 324      'CUME_DIST', 
    + 325      'CURRENT', 
    + 326      'CURRENT_DEFAULT_TRANSFORM_GROUP', 
    + 327      'CURRENT_PATH', 
    + 328      'CURRENT_TRANSFORM_GROUP_FOR_TYPE', 
    + 329      'CURSOR', 
    + 330      'CURSOR_NAME', 
    + 331      'CYCLE', 
    + 332      'DATA', 
    + 333      'DATABASE', 
    + 334      'DATE', 
    + 335      'DATETIME_INTERVAL_CODE', 
    + 336      'DATETIME_INTERVAL_PRECISION', 
    + 337      'DAY', 
    + 338      'DEALLOCATE', 
    + 339      'DECLARE', 
    + 340      'DEFAULTS', 
    + 341      'DEFERRED', 
    + 342      'DEFINED', 
    + 343      'DEFINER', 
    + 344      'DEGREE', 
    + 345      'DELETE', 
    + 346      'DELIMITER', 
    + 347      'DELIMITERS', 
    + 348      'DENSE_RANK', 
    + 349      'DEPTH', 
    + 350      'DEREF', 
    + 351      'DERIVED', 
    + 352      'DESCRIBE', 
    + 353      'DESCRIPTOR', 
    + 354      'DESTROY', 
    + 355      'DESTRUCTOR', 
    + 356      'DETERMINISTIC', 
    + 357      'DIAGNOSTICS', 
    + 358      'DICTIONARY', 
    + 359      'DISABLE', 
    + 360      'DISCARD', 
    + 361      'DISCONNECT', 
    + 362      'DISPATCH', 
    + 363      'DOCUMENT', 
    + 364      'DOMAIN', 
    + 365      'DOUBLE', 
    + 366      'DROP', 
    + 367      'DYNAMIC', 
    + 368      'DYNAMIC_FUNCTION', 
    + 369      'DYNAMIC_FUNCTION_CODE', 
    + 370      'EACH', 
    + 371      'ELEMENT', 
    + 372      'EMPTY', 
    + 373      'ENABLE', 
    + 374      'ENCODING', 
    + 375      'ENCRYPTED', 
    + 376      'END-EXEC', 
    + 377      'ENUM', 
    + 378      'EQUALS', 
    + 379      'ESCAPE', 
    + 380      'EVERY', 
    + 381      'EXCEPTION', 
    + 382      'EXCLUDE', 
    + 383      'EXCLUDING', 
    + 384      'EXCLUSIVE', 
    + 385      'EXEC', 
    + 386      'EXECUTE', 
    + 387      'EXISTING', 
    + 388      'EXP', 
    + 389      'EXPLAIN', 
    + 390      'EXTERNAL', 
    + 391      'FAMILY', 
    + 392      'FILTER', 
    + 393      'FINAL', 
    + 394      'FIRST', 
    + 395      'FIRST_VALUE', 
    + 396      'FLAG', 
    + 397      'FLOOR', 
    + 398      'FOLLOWING', 
    + 399      'FORCE', 
    + 400      'FORTRAN', 
    + 401      'FORWARD', 
    + 402      'FOUND', 
    + 403      'FREE', 
    + 404      'FUNCTION', 
    + 405      'FUSION', 
    + 406      'G', 
    + 407      'GENERAL', 
    + 408      'GENERATED', 
    + 409      'GET', 
    + 410      'GLOBAL', 
    + 411      'GO', 
    + 412      'GOTO', 
    + 413      'GRANTED', 
    + 414      'GROUPING', 
    + 415      'HANDLER', 
    + 416      'HEADER', 
    + 417      'HEX', 
    + 418      'HIERARCHY', 
    + 419      'HOLD', 
    + 420      'HOST', 
    + 421      'HOUR', 
    + 422  #    'ID', 
    + 423      'IDENTITY', 
    + 424      'IF', 
    + 425      'IGNORE', 
    + 426      'IMMEDIATE', 
    + 427      'IMMUTABLE', 
    + 428      'IMPLEMENTATION', 
    + 429      'IMPLICIT', 
    + 430      'INCLUDING', 
    + 431      'INCREMENT', 
    + 432      'INDENT', 
    + 433      'INDEX', 
    + 434      'INDEXES', 
    + 435      'INDICATOR', 
    + 436      'INFIX', 
    + 437      'INHERIT', 
    + 438      'INHERITS', 
    + 439      'INITIALIZE', 
    + 440      'INPUT', 
    + 441      'INSENSITIVE', 
    + 442      'INSERT', 
    + 443      'INSTANCE', 
    + 444      'INSTANTIABLE', 
    + 445      'INSTEAD', 
    + 446      'INTERSECTION', 
    + 447      'INVOKER', 
    + 448      'ISOLATION', 
    + 449      'ITERATE', 
    + 450      'K', 
    + 451      'KEY', 
    + 452      'KEY_MEMBER', 
    + 453      'KEY_TYPE', 
    + 454      'LAG', 
    + 455      'LANCOMPILER', 
    + 456      'LANGUAGE', 
    + 457      'LARGE', 
    + 458      'LAST', 
    + 459      'LAST_VALUE', 
    + 460      'LATERAL', 
    + 461      'LC_COLLATE', 
    + 462      'LC_CTYPE', 
    + 463      'LEAD', 
    + 464      'LENGTH', 
    + 465      'LESS', 
    + 466      'LEVEL', 
    + 467      'LIKE_REGEX', 
    + 468      'LISTEN', 
    + 469      'LN', 
    + 470      'LOAD', 
    + 471      'LOCAL', 
    + 472      'LOCATION', 
    + 473      'LOCATOR', 
    + 474      'LOCK', 
    + 475      'LOGIN', 
    + 476      'LOWER', 
    + 477      'M', 
    + 478      'MAP', 
    + 479      'MAPPING', 
    + 480      'MATCH', 
    + 481      'MATCHED', 
    + 482      'MAX', 
    + 483      'MAX_CARDINALITY', 
    + 484      'MAXVALUE', 
    + 485      'MEMBER', 
    + 486      'MERGE', 
    + 487      'MESSAGE_LENGTH', 
    + 488      'MESSAGE_OCTET_LENGTH', 
    + 489      'MESSAGE_TEXT', 
    + 490      'METHOD', 
    + 491      'MIN', 
    + 492      'MINUTE', 
    + 493      'MINVALUE', 
    + 494      'MOD', 
    + 495      'MODE', 
    + 496      'MODIFIES', 
    + 497      'MODIFY', 
    + 498      'MODULE', 
    + 499      'MONTH', 
    + 500      'MORE', 
    + 501      'MOVE', 
    + 502      'MULTISET', 
    + 503      'MUMPS', 
    + 504  #    'NAME', 
    + 505      'NAMES', 
    + 506      'NAMESPACE', 
    + 507      'NCLOB', 
    + 508      'NESTING', 
    + 509      'NEXT', 
    + 510      'NFC', 
    + 511      'NFD', 
    + 512      'NFKC', 
    + 513      'NFKD', 
    + 514      'NIL', 
    + 515      'NO', 
    + 516      'NOCREATEDB', 
    + 517      'NOCREATEROLE', 
    + 518      'NOCREATEUSER', 
    + 519      'NOINHERIT', 
    + 520      'NOLOGIN', 
    + 521      'NORMALIZE', 
    + 522      'NORMALIZED', 
    + 523      'NOSUPERUSER', 
    + 524      'NOTHING', 
    + 525      'NOTIFY', 
    + 526      'NOWAIT', 
    + 527      'NTH_VALUE', 
    + 528      'NTILE', 
    + 529      'NULLABLE', 
    + 530      'NULLS', 
    + 531      'NUMBER', 
    + 532      'OBJECT', 
    + 533      'OCCURRENCES_REGEX', 
    + 534      'OCTET_LENGTH', 
    + 535      'OCTETS', 
    + 536      'OF', 
    + 537      'OIDS', 
    + 538      'OPEN', 
    + 539      'OPERATION', 
    + 540      'OPERATOR', 
    + 541      'OPTION', 
    + 542      'OPTIONS', 
    + 543      'ORDERING', 
    + 544      'ORDINALITY', 
    + 545      'OTHERS', 
    + 546      'OUTPUT', 
    + 547      'OVER', 
    + 548      'OVERRIDING', 
    + 549      'OWNED', 
    + 550      'OWNER', 
    + 551      'P', 
    + 552      'PAD', 
    + 553      'PARAMETER', 
    + 554      'PARAMETER_MODE', 
    + 555      'PARAMETER_NAME', 
    + 556      'PARAMETER_ORDINAL_POSITION', 
    + 557      'PARAMETER_SPECIFIC_CATALOG', 
    + 558      'PARAMETER_SPECIFIC_NAME', 
    + 559      'PARAMETER_SPECIFIC_SCHEMA', 
    + 560      'PARAMETERS', 
    + 561      'PARSER', 
    + 562      'PARTIAL', 
    + 563      'PARTITION', 
    + 564      'PASCAL', 
    + 565      'PASSING', 
    + 566  #    'PASSWORD', 
    + 567      'PATH', 
    + 568      'PERCENT_RANK', 
    + 569      'PERCENTILE_CONT', 
    + 570      'PERCENTILE_DISC', 
    + 571      'PLANS', 
    + 572      'PLI', 
    + 573      'POSITION_REGEX', 
    + 574      'POSTFIX', 
    + 575      'POWER', 
    + 576      'PRECEDING', 
    + 577      'PREFIX', 
    + 578      'PREORDER', 
    + 579      'PREPARE', 
    + 580      'PREPARED', 
    + 581      'PRESERVE', 
    + 582      'PRIOR', 
    + 583      'PRIVILEGES', 
    + 584      'PROCEDURAL', 
    + 585      'PROCEDURE', 
    + 586      'PUBLIC', 
    + 587      'QUOTE', 
    + 588      'RANGE', 
    + 589      'RANK', 
    + 590      'READ', 
    + 591      'READS', 
    + 592      'REASSIGN', 
    + 593      'RECHECK', 
    + 594      'RECURSIVE', 
    + 595      'REF', 
    + 596      'REFERENCING', 
    + 597      'REGR_AVGX', 
    + 598      'REGR_AVGY', 
    + 599      'REGR_COUNT', 
    + 600      'REGR_INTERCEPT', 
    + 601      'REGR_R2', 
    + 602      'REGR_SLOPE', 
    + 603      'REGR_SXX', 
    + 604      'REGR_SXY', 
    + 605      'REGR_SYY', 
    + 606      'REINDEX', 
    + 607      'RELATIVE', 
    + 608      'RELEASE', 
    + 609      'RENAME', 
    + 610      'REPEATABLE', 
    + 611      'REPLACE', 
    + 612      'REPLICA', 
    + 613      'RESET', 
    + 614      'RESPECT', 
    + 615      'RESTART', 
    + 616      'RESTRICT', 
    + 617      'RESULT', 
    + 618      'RETURN', 
    + 619      'RETURNED_CARDINALITY', 
    + 620      'RETURNED_LENGTH', 
    + 621      'RETURNED_OCTET_LENGTH', 
    + 622      'RETURNED_SQLSTATE', 
    + 623      'RETURNS', 
    + 624      'REVOKE', 
    + 625  #    'ROLE', 
    + 626      'ROLLBACK', 
    + 627      'ROLLUP', 
    + 628      'ROUTINE', 
    + 629      'ROUTINE_CATALOG', 
    + 630      'ROUTINE_NAME', 
    + 631      'ROUTINE_SCHEMA', 
    + 632      'ROW_COUNT', 
    + 633      'ROW_NUMBER', 
    + 634      'ROWS', 
    + 635      'RULE', 
    + 636      'SAVEPOINT', 
    + 637      'SCALE', 
    + 638      'SCHEMA', 
    + 639      'SCHEMA_NAME', 
    + 640      'SCOPE', 
    + 641      'SCOPE_CATALOG', 
    + 642      'SCOPE_NAME', 
    + 643      'SCOPE_SCHEMA', 
    + 644      'SCROLL', 
    + 645      'SEARCH', 
    + 646      'SECOND', 
    + 647      'SECTION', 
    + 648      'SECURITY', 
    + 649      'SELF', 
    + 650      'SENSITIVE', 
    + 651      'SEQUENCE', 
    + 652      'SERIALIZABLE', 
    + 653      'SERVER', 
    + 654      'SERVER_NAME', 
    + 655      'SESSION', 
    + 656      'SET', 
    + 657      'SETS', 
    + 658      'SHARE', 
    + 659      'SHOW', 
    + 660      'SIMPLE', 
    + 661      'SIZE', 
    + 662      'SOURCE', 
    + 663      'SPACE', 
    + 664      'SPECIFIC', 
    + 665      'SPECIFIC_NAME', 
    + 666      'SPECIFICTYPE', 
    + 667      'SQL', 
    + 668      'SQLCODE', 
    + 669      'SQLERROR', 
    + 670      'SQLEXCEPTION', 
    + 671      'SQLSTATE', 
    + 672      'SQLWARNING', 
    + 673      'SQRT', 
    + 674      'STABLE', 
    + 675      'STANDALONE', 
    + 676      'START', 
    + 677      'STATE', 
    + 678      'STATEMENT', 
    + 679      'STATIC', 
    + 680      'STATISTICS', 
    + 681      'STDDEV_POP', 
    + 682      'STDDEV_SAMP', 
    + 683      'STDIN', 
    + 684      'STDOUT', 
    + 685      'STORAGE', 
    + 686      'STRICT', 
    + 687      'STRIP', 
    + 688      'STRUCTURE', 
    + 689      'STYLE', 
    + 690      'SUBCLASS_ORIGIN', 
    + 691      'SUBLIST', 
    + 692      'SUBMULTISET', 
    + 693      'SUBSTRING_REGEX', 
    + 694      'SUM', 
    + 695      'SUPERUSER', 
    + 696      'SYSID', 
    + 697      'SYSTEM', 
    + 698      'SYSTEM_USER', 
    + 699      'T', 
    + 700  #    'TABLE_NAME', 
    + 701      'TABLESAMPLE', 
    + 702      'TABLESPACE', 
    + 703      'TEMP', 
    + 704      'TEMPLATE', 
    + 705      'TEMPORARY', 
    + 706      'TERMINATE', 
    + 707      'TEXT', 
    + 708      'THAN', 
    + 709      'TIES', 
    + 710      'TIMEZONE_HOUR', 
    + 711      'TIMEZONE_MINUTE', 
    + 712      'TOP_LEVEL_COUNT', 
    + 713      'TRANSACTION', 
    + 714      'TRANSACTION_ACTIVE', 
    + 715      'TRANSACTIONS_COMMITTED', 
    + 716      'TRANSACTIONS_ROLLED_BACK', 
    + 717      'TRANSFORM', 
    + 718      'TRANSFORMS', 
    + 719      'TRANSLATE', 
    + 720      'TRANSLATE_REGEX', 
    + 721      'TRANSLATION', 
    + 722      'TRIGGER', 
    + 723      'TRIGGER_CATALOG', 
    + 724      'TRIGGER_NAME', 
    + 725      'TRIGGER_SCHEMA', 
    + 726      'TRIM_ARRAY', 
    + 727      'TRUNCATE', 
    + 728      'TRUSTED', 
    + 729      'TYPE', 
    + 730      'UESCAPE', 
    + 731      'UNBOUNDED', 
    + 732      'UNCOMMITTED', 
    + 733      'UNDER', 
    + 734      'UNENCRYPTED', 
    + 735      'UNKNOWN', 
    + 736      'UNLISTEN', 
    + 737      'UNNAMED', 
    + 738      'UNNEST', 
    + 739      'UNTIL', 
    + 740      'UNTYPED', 
    + 741      'UPDATE', 
    + 742      'UPPER', 
    + 743      'URI', 
    + 744      'USAGE', 
    + 745      'USER_DEFINED_TYPE_CATALOG', 
    + 746      'USER_DEFINED_TYPE_CODE', 
    + 747      'USER_DEFINED_TYPE_NAME', 
    + 748      'USER_DEFINED_TYPE_SCHEMA', 
    + 749      'VACUUM', 
    + 750      'VALID', 
    + 751      'VALIDATOR', 
    + 752      'VALUE', 
    + 753      'VAR_POP', 
    + 754      'VAR_SAMP', 
    + 755      'VARBINARY', 
    + 756      'VARIABLE', 
    + 757      'VARYING', 
    + 758      'VERSION', 
    + 759      'VIEW', 
    + 760      'VOLATILE', 
    + 761      'WHENEVER', 
    + 762      'WHITESPACE', 
    + 763      'WIDTH_BUCKET', 
    + 764      'WINDOW', 
    + 765      'WITHIN', 
    + 766      'WITHOUT', 
    + 767      'WORK', 
    + 768      'WRAPPER', 
    + 769      'WRITE', 
    + 770      'XML', 
    + 771      'XMLAGG', 
    + 772      'XMLBINARY', 
    + 773      'XMLCAST', 
    + 774      'XMLCOMMENT', 
    + 775      'XMLDECLARATION', 
    + 776      'XMLDOCUMENT', 
    + 777      'XMLEXISTS', 
    + 778      'XMLITERATE', 
    + 779      'XMLNAMESPACES', 
    + 780      'XMLQUERY', 
    + 781      'XMLSCHEMA', 
    + 782      'XMLTABLE', 
    + 783      'XMLTEXT', 
    + 784      'XMLVALIDATE', 
    + 785      'YEAR', 
    + 786      'YES', 
    + 787      'ZONE', 
    + 788  )) 
    + 789   
    + 790  #Thanks villas 
    + 791  FIREBIRD = set(( 
    + 792      'ABS', 
    + 793      'ACTIVE', 
    + 794      'ADMIN', 
    + 795      'AFTER', 
    + 796      'ASCENDING', 
    + 797      'AUTO', 
    + 798      'AUTODDL', 
    + 799      'BASED', 
    + 800      'BASENAME', 
    + 801      'BASE_NAME', 
    + 802      'BEFORE', 
    + 803      'BIT_LENGTH', 
    + 804      'BLOB', 
    + 805      'BLOBEDIT', 
    + 806      'BOOLEAN', 
    + 807      'BOTH', 
    + 808      'BUFFER', 
    + 809      'CACHE', 
    + 810      'CHAR_LENGTH', 
    + 811      'CHARACTER_LENGTH', 
    + 812      'CHECK_POINT_LEN', 
    + 813      'CHECK_POINT_LENGTH', 
    + 814      'CLOSE', 
    + 815      'COMMITTED', 
    + 816      'COMPILETIME', 
    + 817      'COMPUTED', 
    + 818      'CONDITIONAL', 
    + 819      'CONNECT', 
    + 820      'CONTAINING', 
    + 821      'CROSS', 
    + 822      'CSTRING', 
    + 823      'CURRENT_CONNECTION', 
    + 824      'CURRENT_ROLE', 
    + 825      'CURRENT_TRANSACTION', 
    + 826      'CURRENT_USER', 
    + 827      'DATABASE', 
    + 828      'DB_KEY', 
    + 829      'DEBUG', 
    + 830      'DESCENDING', 
    + 831      'DISCONNECT', 
    + 832      'DISPLAY', 
    + 833      'DO', 
    + 834      'ECHO', 
    + 835      'EDIT', 
    + 836      'ENTRY_POINT', 
    + 837      'EVENT', 
    + 838      'EXIT', 
    + 839      'EXTERN', 
    + 840      'FALSE', 
    + 841      'FETCH', 
    + 842      'FILE', 
    + 843      'FILTER', 
    + 844      'FREE_IT', 
    + 845      'FUNCTION', 
    + 846      'GDSCODE', 
    + 847      'GENERATOR', 
    + 848      'GEN_ID', 
    + 849      'GLOBAL', 
    + 850      'GROUP_COMMIT_WAIT', 
    + 851      'GROUP_COMMIT_WAIT_TIME', 
    + 852      'HELP', 
    + 853      'IF', 
    + 854      'INACTIVE', 
    + 855      'INDEX', 
    + 856      'INIT', 
    + 857      'INPUT_TYPE', 
    + 858      'INSENSITIVE', 
    + 859      'ISQL', 
    + 860      'LC_MESSAGES', 
    + 861      'LC_TYPE', 
    + 862      'LEADING', 
    + 863      'LENGTH', 
    + 864      'LEV', 
    + 865      'LOGFILE', 
    + 866      'LOG_BUFFER_SIZE', 
    + 867      'LOG_BUF_SIZE', 
    + 868      'LONG', 
    + 869      'LOWER', 
    + 870      'MANUAL', 
    + 871      'MAXIMUM', 
    + 872      'MAXIMUM_SEGMENT', 
    + 873      'MAX_SEGMENT', 
    + 874      'MERGE', 
    + 875      'MESSAGE', 
    + 876      'MINIMUM', 
    + 877      'MODULE_NAME', 
    + 878      'NOAUTO', 
    + 879      'NUM_LOG_BUFS', 
    + 880      'NUM_LOG_BUFFERS', 
    + 881      'OCTET_LENGTH', 
    + 882      'OPEN', 
    + 883      'OUTPUT_TYPE', 
    + 884      'OVERFLOW', 
    + 885      'PAGE', 
    + 886      'PAGELENGTH', 
    + 887      'PAGES', 
    + 888      'PAGE_SIZE', 
    + 889      'PARAMETER', 
    + 890  #    'PASSWORD', 
    + 891      'PLAN', 
    + 892      'POST_EVENT', 
    + 893      'QUIT', 
    + 894      'RAW_PARTITIONS', 
    + 895      'RDB$DB_KEY', 
    + 896      'RECORD_VERSION', 
    + 897      'RECREATE', 
    + 898      'RECURSIVE', 
    + 899      'RELEASE', 
    + 900      'RESERV', 
    + 901      'RESERVING', 
    + 902      'RETAIN', 
    + 903      'RETURN', 
    + 904      'RETURNING_VALUES', 
    + 905      'RETURNS', 
    + 906  #    'ROLE', 
    + 907      'ROW_COUNT', 
    + 908      'ROWS', 
    + 909      'RUNTIME', 
    + 910      'SAVEPOINT', 
    + 911      'SECOND', 
    + 912      'SENSITIVE', 
    + 913      'SHADOW', 
    + 914      'SHARED', 
    + 915      'SHELL', 
    + 916      'SHOW', 
    + 917      'SINGULAR', 
    + 918      'SNAPSHOT', 
    + 919      'SORT', 
    + 920      'STABILITY', 
    + 921      'START', 
    + 922      'STARTING', 
    + 923      'STARTS', 
    + 924      'STATEMENT', 
    + 925      'STATIC', 
    + 926      'STATISTICS', 
    + 927      'SUB_TYPE', 
    + 928      'SUSPEND', 
    + 929      'TERMINATOR', 
    + 930      'TRAILING', 
    + 931      'TRIGGER', 
    + 932      'TRIM', 
    + 933      'TRUE', 
    + 934      'TYPE', 
    + 935      'UNCOMMITTED', 
    + 936      'UNKNOWN', 
    + 937      'USING', 
    + 938      'VARIABLE', 
    + 939      'VERSION', 
    + 940      'WAIT', 
    + 941      'WEEKDAY', 
    + 942      'WHILE', 
    + 943      'YEARDAY', 
    + 944  )) 
    + 945  FIREBIRD_NONRESERVED = set(( 
    + 946      'BACKUP', 
    + 947      'BLOCK', 
    + 948      'COALESCE', 
    + 949      'COLLATION', 
    + 950      'COMMENT', 
    + 951      'DELETING', 
    + 952      'DIFFERENCE', 
    + 953      'IIF', 
    + 954      'INSERTING', 
    + 955      'LAST', 
    + 956      'LEAVE', 
    + 957      'LOCK', 
    + 958      'NEXT', 
    + 959      'NULLIF', 
    + 960      'NULLS', 
    + 961      'RESTART', 
    + 962      'RETURNING', 
    + 963      'SCALAR_ARRAY', 
    + 964      'SEQUENCE', 
    + 965      'STATEMENT', 
    + 966      'UPDATING', 
    + 967      'ABS', 
    + 968      'ACCENT', 
    + 969      'ACOS', 
    + 970      'ALWAYS', 
    + 971      'ASCII_CHAR', 
    + 972      'ASCII_VAL', 
    + 973      'ASIN', 
    + 974      'ATAN', 
    + 975      'ATAN2', 
    + 976      'BACKUP', 
    + 977      'BIN_AND', 
    + 978      'BIN_OR', 
    + 979      'BIN_SHL', 
    + 980      'BIN_SHR', 
    + 981      'BIN_XOR', 
    + 982      'BLOCK', 
    + 983      'CEIL', 
    + 984      'CEILING', 
    + 985      'COLLATION', 
    + 986      'COMMENT', 
    + 987      'COS', 
    + 988      'COSH', 
    + 989      'COT', 
    + 990      'DATEADD', 
    + 991      'DATEDIFF', 
    + 992      'DECODE', 
    + 993      'DIFFERENCE', 
    + 994      'EXP', 
    + 995      'FLOOR', 
    + 996      'GEN_UUID', 
    + 997      'GENERATED', 
    + 998      'HASH', 
    + 999      'IIF', 
    +1000      'LIST', 
    +1001      'LN', 
    +1002      'LOG', 
    +1003      'LOG10', 
    +1004      'LPAD', 
    +1005      'MATCHED', 
    +1006      'MATCHING', 
    +1007      'MAXVALUE', 
    +1008      'MILLISECOND', 
    +1009      'MINVALUE', 
    +1010      'MOD', 
    +1011      'NEXT', 
    +1012      'OVERLAY', 
    +1013      'PAD', 
    +1014      'PI', 
    +1015      'PLACING', 
    +1016      'POWER', 
    +1017      'PRESERVE', 
    +1018      'RAND', 
    +1019      'REPLACE', 
    +1020      'RESTART', 
    +1021      'RETURNING', 
    +1022      'REVERSE', 
    +1023      'ROUND', 
    +1024      'RPAD', 
    +1025      'SCALAR_ARRAY', 
    +1026      'SEQUENCE', 
    +1027      'SIGN', 
    +1028      'SIN', 
    +1029      'SINH', 
    +1030      'SPACE', 
    +1031      'SQRT', 
    +1032      'TAN', 
    +1033      'TANH', 
    +1034      'TEMPORARY', 
    +1035      'TRUNC', 
    +1036      'WEEK', 
    +1037  )) 
    +1038   
    +1039  # Thanks Jonathan Lundell 
    +1040  MYSQL = set(( 
    +1041      'ACCESSIBLE', 
    +1042      'ADD', 
    +1043      'ALL', 
    +1044      'ALTER', 
    +1045      'ANALYZE', 
    +1046      'AND', 
    +1047      'AS', 
    +1048      'ASC', 
    +1049      'ASENSITIVE', 
    +1050      'BEFORE', 
    +1051      'BETWEEN', 
    +1052      'BIGINT', 
    +1053      'BINARY', 
    +1054      'BLOB', 
    +1055      'BOTH', 
    +1056      'BY', 
    +1057      'CALL', 
    +1058      'CASCADE', 
    +1059      'CASE', 
    +1060      'CHANGE', 
    +1061      'CHAR', 
    +1062      'CHARACTER', 
    +1063      'CHECK', 
    +1064      'COLLATE', 
    +1065      'COLUMN', 
    +1066      'CONDITION', 
    +1067      'CONSTRAINT', 
    +1068      'CONTINUE', 
    +1069      'CONVERT', 
    +1070      'CREATE', 
    +1071      'CROSS', 
    +1072      'CURRENT_DATE', 
    +1073      'CURRENT_TIME', 
    +1074      'CURRENT_TIMESTAMP', 
    +1075      'CURRENT_USER', 
    +1076      'CURSOR', 
    +1077      'DATABASE', 
    +1078      'DATABASES', 
    +1079      'DAY_HOUR', 
    +1080      'DAY_MICROSECOND', 
    +1081      'DAY_MINUTE', 
    +1082      'DAY_SECOND', 
    +1083      'DEC', 
    +1084      'DECIMAL', 
    +1085      'DECLARE', 
    +1086      'DEFAULT', 
    +1087      'DELAYED', 
    +1088      'DELETE', 
    +1089      'DESC', 
    +1090      'DESCRIBE', 
    +1091      'DETERMINISTIC', 
    +1092      'DISTINCT', 
    +1093      'DISTINCTROW', 
    +1094      'DIV', 
    +1095      'DOUBLE', 
    +1096      'DROP', 
    +1097      'DUAL', 
    +1098      'EACH', 
    +1099      'ELSE', 
    +1100      'ELSEIF', 
    +1101      'ENCLOSED', 
    +1102      'ESCAPED', 
    +1103      'EXISTS', 
    +1104      'EXIT', 
    +1105      'EXPLAIN', 
    +1106      'FALSE', 
    +1107      'FETCH', 
    +1108      'FLOAT', 
    +1109      'FLOAT4', 
    +1110      'FLOAT8', 
    +1111      'FOR', 
    +1112      'FORCE', 
    +1113      'FOREIGN', 
    +1114      'FROM', 
    +1115      'FULLTEXT', 
    +1116      'GRANT', 
    +1117      'GROUP', 
    +1118      'HAVING', 
    +1119      'HIGH_PRIORITY', 
    +1120      'HOUR_MICROSECOND', 
    +1121      'HOUR_MINUTE', 
    +1122      'HOUR_SECOND', 
    +1123      'IF', 
    +1124      'IGNORE', 
    +1125      'IGNORE_SERVER_IDS', 
    +1126      'IGNORE_SERVER_IDS', 
    +1127      'IN', 
    +1128      'INDEX', 
    +1129      'INFILE', 
    +1130      'INNER', 
    +1131      'INOUT', 
    +1132      'INSENSITIVE', 
    +1133      'INSERT', 
    +1134      'INT', 
    +1135      'INT1', 
    +1136      'INT2', 
    +1137      'INT3', 
    +1138      'INT4', 
    +1139      'INT8', 
    +1140      'INTEGER', 
    +1141      'INTERVAL', 
    +1142      'INTO', 
    +1143      'IS', 
    +1144      'ITERATE', 
    +1145      'JOIN', 
    +1146      'KEY', 
    +1147      'KEYS', 
    +1148      'KILL', 
    +1149      'LEADING', 
    +1150      'LEAVE', 
    +1151      'LEFT', 
    +1152      'LIKE', 
    +1153      'LIMIT', 
    +1154      'LINEAR', 
    +1155      'LINES', 
    +1156      'LOAD', 
    +1157      'LOCALTIME', 
    +1158      'LOCALTIMESTAMP', 
    +1159      'LOCK', 
    +1160      'LONG', 
    +1161      'LONGBLOB', 
    +1162      'LONGTEXT', 
    +1163      'LOOP', 
    +1164      'LOW_PRIORITY', 
    +1165      'MASTER_HEARTBEAT_PERIOD', 
    +1166      'MASTER_HEARTBEAT_PERIOD', 
    +1167      'MASTER_SSL_VERIFY_SERVER_CERT', 
    +1168      'MATCH', 
    +1169      'MAXVALUE', 
    +1170      'MAXVALUE', 
    +1171      'MEDIUMBLOB', 
    +1172      'MEDIUMINT', 
    +1173      'MEDIUMTEXT', 
    +1174      'MIDDLEINT', 
    +1175      'MINUTE_MICROSECOND', 
    +1176      'MINUTE_SECOND', 
    +1177      'MOD', 
    +1178      'MODIFIES', 
    +1179      'NATURAL', 
    +1180      'NO_WRITE_TO_BINLOG', 
    +1181      'NOT', 
    +1182      'NULL', 
    +1183      'NUMERIC', 
    +1184      'ON', 
    +1185      'OPTIMIZE', 
    +1186      'OPTION', 
    +1187      'OPTIONALLY', 
    +1188      'OR', 
    +1189      'ORDER', 
    +1190      'OUT', 
    +1191      'OUTER', 
    +1192      'OUTFILE', 
    +1193      'PRECISION', 
    +1194      'PRIMARY', 
    +1195      'PROCEDURE', 
    +1196      'PURGE', 
    +1197      'RANGE', 
    +1198      'READ', 
    +1199      'READ_WRITE', 
    +1200      'READS', 
    +1201      'REAL', 
    +1202      'REFERENCES', 
    +1203      'REGEXP', 
    +1204      'RELEASE', 
    +1205      'RENAME', 
    +1206      'REPEAT', 
    +1207      'REPLACE', 
    +1208      'REQUIRE', 
    +1209      'RESIGNAL', 
    +1210      'RESIGNAL', 
    +1211      'RESTRICT', 
    +1212      'RETURN', 
    +1213      'REVOKE', 
    +1214      'RIGHT', 
    +1215      'RLIKE', 
    +1216      'SCHEMA', 
    +1217      'SCHEMAS', 
    +1218      'SECOND_MICROSECOND', 
    +1219      'SELECT', 
    +1220      'SENSITIVE', 
    +1221      'SEPARATOR', 
    +1222      'SET', 
    +1223      'SHOW', 
    +1224      'SIGNAL', 
    +1225      'SIGNAL', 
    +1226      'SMALLINT', 
    +1227      'SPATIAL', 
    +1228      'SPECIFIC', 
    +1229      'SQL', 
    +1230      'SQL_BIG_RESULT', 
    +1231      'SQL_CALC_FOUND_ROWS', 
    +1232      'SQL_SMALL_RESULT', 
    +1233      'SQLEXCEPTION', 
    +1234      'SQLSTATE', 
    +1235      'SQLWARNING', 
    +1236      'SSL', 
    +1237      'STARTING', 
    +1238      'STRAIGHT_JOIN', 
    +1239      'TABLE', 
    +1240      'TERMINATED', 
    +1241      'THEN', 
    +1242      'TINYBLOB', 
    +1243      'TINYINT', 
    +1244      'TINYTEXT', 
    +1245      'TO', 
    +1246      'TRAILING', 
    +1247      'TRIGGER', 
    +1248      'TRUE', 
    +1249      'UNDO', 
    +1250      'UNION', 
    +1251      'UNIQUE', 
    +1252      'UNLOCK', 
    +1253      'UNSIGNED', 
    +1254      'UPDATE', 
    +1255      'USAGE', 
    +1256      'USE', 
    +1257      'USING', 
    +1258      'UTC_DATE', 
    +1259      'UTC_TIME', 
    +1260      'UTC_TIMESTAMP', 
    +1261      'VALUES', 
    +1262      'VARBINARY', 
    +1263      'VARCHAR', 
    +1264      'VARCHARACTER', 
    +1265      'VARYING', 
    +1266      'WHEN', 
    +1267      'WHERE', 
    +1268      'WHILE', 
    +1269      'WITH', 
    +1270      'WRITE', 
    +1271      'XOR', 
    +1272      'YEAR_MONTH', 
    +1273      'ZEROFILL', 
    +1274  )) 
    +1275   
    +1276  MSSQL = set(( 
    +1277      'ADD', 
    +1278      'ALL', 
    +1279      'ALTER', 
    +1280      'AND', 
    +1281      'ANY', 
    +1282      'AS', 
    +1283      'ASC', 
    +1284      'AUTHORIZATION', 
    +1285      'BACKUP', 
    +1286      'BEGIN', 
    +1287      'BETWEEN', 
    +1288      'BREAK', 
    +1289      'BROWSE', 
    +1290      'BULK', 
    +1291      'BY', 
    +1292      'CASCADE', 
    +1293      'CASE', 
    +1294      'CHECK', 
    +1295      'CHECKPOINT', 
    +1296      'CLOSE', 
    +1297      'CLUSTERED', 
    +1298      'COALESCE', 
    +1299      'COLLATE', 
    +1300      'COLUMN', 
    +1301      'COMMIT', 
    +1302      'COMPUTE', 
    +1303      'CONSTRAINT', 
    +1304      'CONTAINS', 
    +1305      'CONTAINSTABLE', 
    +1306      'CONTINUE', 
    +1307      'CONVERT', 
    +1308      'CREATE', 
    +1309      'CROSS', 
    +1310      'CURRENT', 
    +1311      'CURRENT_DATE', 
    +1312      'CURRENT_TIME', 
    +1313      'CURRENT_TIMESTAMP', 
    +1314      'CURRENT_USER', 
    +1315      'CURSOR', 
    +1316      'DATABASE', 
    +1317      'DBCC', 
    +1318      'DEALLOCATE', 
    +1319      'DECLARE', 
    +1320      'DEFAULT', 
    +1321      'DELETE', 
    +1322      'DENY', 
    +1323      'DESC', 
    +1324      'DISK', 
    +1325      'DISTINCT', 
    +1326      'DISTRIBUTED', 
    +1327      'DOUBLE', 
    +1328      'DROP', 
    +1329      'DUMMY', 
    +1330      'DUMP', 
    +1331      'ELSE', 
    +1332      'END', 
    +1333      'ERRLVL', 
    +1334      'ESCAPE', 
    +1335      'EXCEPT', 
    +1336      'EXEC', 
    +1337      'EXECUTE', 
    +1338      'EXISTS', 
    +1339      'EXIT', 
    +1340      'FETCH', 
    +1341      'FILE', 
    +1342      'FILLFACTOR', 
    +1343      'FOR', 
    +1344      'FOREIGN', 
    +1345      'FREETEXT', 
    +1346      'FREETEXTTABLE', 
    +1347      'FROM', 
    +1348      'FULL', 
    +1349      'FUNCTION', 
    +1350      'GOTO', 
    +1351      'GRANT', 
    +1352      'GROUP', 
    +1353      'HAVING', 
    +1354      'HOLDLOCK', 
    +1355      'IDENTITY', 
    +1356      'IDENTITY_INSERT', 
    +1357      'IDENTITYCOL', 
    +1358      'IF', 
    +1359      'IN', 
    +1360      'INDEX', 
    +1361      'INNER', 
    +1362      'INSERT', 
    +1363      'INTERSECT', 
    +1364      'INTO', 
    +1365      'IS', 
    +1366      'JOIN', 
    +1367      'KEY', 
    +1368      'KILL', 
    +1369      'LEFT', 
    +1370      'LIKE', 
    +1371      'LINENO', 
    +1372      'LOAD', 
    +1373      'NATIONAL ', 
    +1374      'NOCHECK', 
    +1375      'NONCLUSTERED', 
    +1376      'NOT', 
    +1377      'NULL', 
    +1378      'NULLIF', 
    +1379      'OF', 
    +1380      'OFF', 
    +1381      'OFFSETS', 
    +1382      'ON', 
    +1383      'OPEN', 
    +1384      'OPENDATASOURCE', 
    +1385      'OPENQUERY', 
    +1386      'OPENROWSET', 
    +1387      'OPENXML', 
    +1388      'OPTION', 
    +1389      'OR', 
    +1390      'ORDER', 
    +1391      'OUTER', 
    +1392      'OVER', 
    +1393      'PERCENT', 
    +1394      'PLAN', 
    +1395      'PRECISION', 
    +1396      'PRIMARY', 
    +1397      'PRINT', 
    +1398      'PROC', 
    +1399      'PROCEDURE', 
    +1400      'PUBLIC', 
    +1401      'RAISERROR', 
    +1402      'READ', 
    +1403      'READTEXT', 
    +1404      'RECONFIGURE', 
    +1405      'REFERENCES', 
    +1406      'REPLICATION', 
    +1407      'RESTORE', 
    +1408      'RESTRICT', 
    +1409      'RETURN', 
    +1410      'REVOKE', 
    +1411      'RIGHT', 
    +1412      'ROLLBACK', 
    +1413      'ROWCOUNT', 
    +1414      'ROWGUIDCOL', 
    +1415      'RULE', 
    +1416      'SAVE', 
    +1417      'SCHEMA', 
    +1418      'SELECT', 
    +1419      'SESSION_USER', 
    +1420      'SET', 
    +1421      'SETUSER', 
    +1422      'SHUTDOWN', 
    +1423      'SOME', 
    +1424      'STATISTICS', 
    +1425      'SYSTEM_USER', 
    +1426      'TABLE', 
    +1427      'TEXTSIZE', 
    +1428      'THEN', 
    +1429      'TO', 
    +1430      'TOP', 
    +1431      'TRAN', 
    +1432      'TRANSACTION', 
    +1433      'TRIGGER', 
    +1434      'TRUNCATE', 
    +1435      'TSEQUAL', 
    +1436      'UNION', 
    +1437      'UNIQUE', 
    +1438      'UPDATE', 
    +1439      'UPDATETEXT', 
    +1440      'USE', 
    +1441      'USER', 
    +1442      'VALUES', 
    +1443      'VARYING', 
    +1444      'VIEW', 
    +1445      'WAITFOR', 
    +1446      'WHEN', 
    +1447      'WHERE', 
    +1448      'WHILE', 
    +1449      'WITH', 
    +1450      'WRITETEXT', 
    +1451  )) 
    +1452   
    +1453  ORACLE = set(( 
    +1454      'ACCESS', 
    +1455      'ADD', 
    +1456      'ALL', 
    +1457      'ALTER', 
    +1458      'AND', 
    +1459      'ANY', 
    +1460      'AS', 
    +1461      'ASC', 
    +1462      'AUDIT', 
    +1463      'BETWEEN', 
    +1464      'BY', 
    +1465      'CHAR', 
    +1466      'CHECK', 
    +1467      'CLUSTER', 
    +1468      'COLUMN', 
    +1469      'COMMENT', 
    +1470      'COMPRESS', 
    +1471      'CONNECT', 
    +1472      'CREATE', 
    +1473      'CURRENT', 
    +1474      'DATE', 
    +1475      'DECIMAL', 
    +1476      'DEFAULT', 
    +1477      'DELETE', 
    +1478      'DESC', 
    +1479      'DISTINCT', 
    +1480      'DROP', 
    +1481      'ELSE', 
    +1482      'EXCLUSIVE', 
    +1483      'EXISTS', 
    +1484      'FILE', 
    +1485      'FLOAT', 
    +1486      'FOR', 
    +1487      'FROM', 
    +1488      'GRANT', 
    +1489      'GROUP', 
    +1490      'HAVING', 
    +1491      'IDENTIFIED', 
    +1492      'IMMEDIATE', 
    +1493      'IN', 
    +1494      'INCREMENT', 
    +1495      'INDEX', 
    +1496      'INITIAL', 
    +1497      'INSERT', 
    +1498      'INTEGER', 
    +1499      'INTERSECT', 
    +1500      'INTO', 
    +1501      'IS', 
    +1502      'LEVEL', 
    +1503      'LIKE', 
    +1504      'LOCK', 
    +1505      'LONG', 
    +1506      'MAXEXTENTS', 
    +1507      'MINUS', 
    +1508      'MLSLABEL', 
    +1509      'MODE', 
    +1510      'MODIFY', 
    +1511      'NOAUDIT', 
    +1512      'NOCOMPRESS', 
    +1513      'NOT', 
    +1514      'NOWAIT', 
    +1515      'NULL', 
    +1516      'NUMBER', 
    +1517      'OF', 
    +1518      'OFFLINE', 
    +1519      'ON', 
    +1520      'ONLINE', 
    +1521      'OPTION', 
    +1522      'OR', 
    +1523      'ORDER', 
    +1524      'PCTFREE', 
    +1525      'PRIOR', 
    +1526      'PRIVILEGES', 
    +1527      'PUBLIC', 
    +1528      'RAW', 
    +1529      'RENAME', 
    +1530      'RESOURCE', 
    +1531      'REVOKE', 
    +1532      'ROW', 
    +1533      'ROWID', 
    +1534      'ROWNUM', 
    +1535      'ROWS', 
    +1536      'SELECT', 
    +1537      'SESSION', 
    +1538      'SET', 
    +1539      'SHARE', 
    +1540      'SIZE', 
    +1541      'SMALLINT', 
    +1542      'START', 
    +1543      'SUCCESSFUL', 
    +1544      'SYNONYM', 
    +1545      'SYSDATE', 
    +1546      'TABLE', 
    +1547      'THEN', 
    +1548      'TO', 
    +1549      'TRIGGER', 
    +1550      'UID', 
    +1551      'UNION', 
    +1552      'UNIQUE', 
    +1553      'UPDATE', 
    +1554      'USER', 
    +1555      'VALIDATE', 
    +1556      'VALUES', 
    +1557      'VARCHAR', 
    +1558      'VARCHAR2', 
    +1559      'VIEW', 
    +1560      'WHENEVER', 
    +1561      'WHERE', 
    +1562      'WITH', 
    +1563  )) 
    +1564   
    +1565  SQLITE = set(( 
    +1566      'ABORT', 
    +1567      'ACTION', 
    +1568      'ADD', 
    +1569      'AFTER', 
    +1570      'ALL', 
    +1571      'ALTER', 
    +1572      'ANALYZE', 
    +1573      'AND', 
    +1574      'AS', 
    +1575      'ASC', 
    +1576      'ATTACH', 
    +1577      'AUTOINCREMENT', 
    +1578      'BEFORE', 
    +1579      'BEGIN', 
    +1580      'BETWEEN', 
    +1581      'BY', 
    +1582      'CASCADE', 
    +1583      'CASE', 
    +1584      'CAST', 
    +1585      'CHECK', 
    +1586      'COLLATE', 
    +1587      'COLUMN', 
    +1588      'COMMIT', 
    +1589      'CONFLICT', 
    +1590      'CONSTRAINT', 
    +1591      'CREATE', 
    +1592      'CROSS', 
    +1593      'CURRENT_DATE', 
    +1594      'CURRENT_TIME', 
    +1595      'CURRENT_TIMESTAMP', 
    +1596      'DATABASE', 
    +1597      'DEFAULT', 
    +1598      'DEFERRABLE', 
    +1599      'DEFERRED', 
    +1600      'DELETE', 
    +1601      'DESC', 
    +1602      'DETACH', 
    +1603      'DISTINCT', 
    +1604      'DROP', 
    +1605      'EACH', 
    +1606      'ELSE', 
    +1607      'END', 
    +1608      'ESCAPE', 
    +1609      'EXCEPT', 
    +1610      'EXCLUSIVE', 
    +1611      'EXISTS', 
    +1612      'EXPLAIN', 
    +1613      'FAIL', 
    +1614      'FOR', 
    +1615      'FOREIGN', 
    +1616      'FROM', 
    +1617      'FULL', 
    +1618      'GLOB', 
    +1619      'GROUP', 
    +1620      'HAVING', 
    +1621      'IF', 
    +1622      'IGNORE', 
    +1623      'IMMEDIATE', 
    +1624      'IN', 
    +1625      'INDEX', 
    +1626      'INDEXED', 
    +1627      'INITIALLY', 
    +1628      'INNER', 
    +1629      'INSERT', 
    +1630      'INSTEAD', 
    +1631      'INTERSECT', 
    +1632      'INTO', 
    +1633      'IS', 
    +1634      'ISNULL', 
    +1635      'JOIN', 
    +1636      'KEY', 
    +1637      'LEFT', 
    +1638      'LIKE', 
    +1639      'LIMIT', 
    +1640      'MATCH', 
    +1641      'NATURAL', 
    +1642      'NO', 
    +1643      'NOT', 
    +1644      'NOTNULL', 
    +1645      'NULL', 
    +1646      'OF', 
    +1647      'OFFSET', 
    +1648      'ON', 
    +1649      'OR', 
    +1650      'ORDER', 
    +1651      'OUTER', 
    +1652      'PLAN', 
    +1653      'PRAGMA', 
    +1654      'PRIMARY', 
    +1655      'QUERY', 
    +1656      'RAISE', 
    +1657      'REFERENCES', 
    +1658      'REGEXP', 
    +1659      'REINDEX', 
    +1660      'RELEASE', 
    +1661      'RENAME', 
    +1662      'REPLACE', 
    +1663      'RESTRICT', 
    +1664      'RIGHT', 
    +1665      'ROLLBACK', 
    +1666      'ROW', 
    +1667      'SAVEPOINT', 
    +1668      'SELECT', 
    +1669      'SET', 
    +1670      'TABLE', 
    +1671      'TEMP', 
    +1672      'TEMPORARY', 
    +1673      'THEN', 
    +1674      'TO', 
    +1675      'TRANSACTION', 
    +1676      'TRIGGER', 
    +1677      'UNION', 
    +1678      'UNIQUE', 
    +1679      'UPDATE', 
    +1680      'USING', 
    +1681      'VACUUM', 
    +1682      'VALUES', 
    +1683      'VIEW', 
    +1684      'VIRTUAL', 
    +1685      'WHEN', 
    +1686      'WHERE', 
    +1687  )) 
    +1688   
    +1689  # remove from here when you add a list. 
    +1690  JDBCSQLITE = SQLITE 
    +1691  DB2 = INFORMIX = INGRES = JDBCPOSTGRESQL = COMMON 
    +1692   
    +1693  ADAPTERS = { 
    +1694      'sqlite': SQLITE, 
    +1695      'mysql': MYSQL, 
    +1696      'postgres': POSTGRESQL, 
    +1697      'postgres_nonreserved': POSTGRESQL_NONRESERVED, 
    +1698      'oracle': ORACLE, 
    +1699      'mssql': MSSQL, 
    +1700      'mssql2': MSSQL, 
    +1701      'db2': DB2, 
    +1702      'informix': INFORMIX, 
    +1703      'firebird': FIREBIRD, 
    +1704      'firebird_embedded': FIREBIRD, 
    +1705      'firebird_nonreserved': FIREBIRD_NONRESERVED, 
    +1706      'ingres': INGRES, 
    +1707      'ingresu': INGRES, 
    +1708      'jdbc:sqlite': JDBCSQLITE, 
    +1709      'jdbc:postgres': JDBCPOSTGRESQL, 
    +1710      'common': COMMON, 
    +1711  } 
    +1712   
    +1713  ADAPTERS['all'] = reduce(lambda a,b:a.union(b),(x for x in ADAPTERS.values())) 
    +1714   
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.restricted-module.html Index: applications/examples/static/epydoc/web2py.gluon.restricted-module.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.restricted-module.html +++ applications/examples/static/epydoc/web2py.gluon.restricted-module.html @@ -0,0 +1,300 @@ + + + + + web2py.gluon.restricted + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module restricted + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Module restricted

    source code

    +This file is part of the web2py Web Framework Copyrighted by Massimo + Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)

    + + + + + + + + + + + + + +
    + + + + + +
    Classes[hide private]
    +
    +   + + TicketStorage
    + defines the ticket object and the default values of its members + (None) +
    +   + + RestrictedError
    + class used to wrap an exception that occurs in the restricted + environment below. +
    + + + + + + + + + + + + + + + +
    + + + + + +
    Functions[hide private]
    +
    +   + + + + + + +
    compile2(code, + layer)
    + The +' +' is necessary else compile fails when code ends in a comment.
    + source code + +
    + +
    +   + + + + + + +
    restricted(code, + environment={}, + layer='Unknown')
    + runs code in environment and returns the output.
    + source code + +
    + +
    +   + + + + + + +
    snapshot(info=1, + context=5, + code=1, + environment=1)
    + Return a dict describing a given traceback (based on + cgitb.text).
    + source code + +
    + +
    + + + + + + + + + +
    + + + + + +
    Variables[hide private]
    +
    +   + + logger = logging.getLogger("web2py") +
    + + + + + + +
    + + + + + +
    Function Details[hide private]
    +
    + +
    + +
    + + +
    +

    restricted(code, + environment={}, + layer='Unknown') +

    +
    source code  +
    + + runs code in environment and returns the output. if an exception + occurs in code it raises a RestrictedError containing the traceback. + layer is passed to RestrictedError to identify where the error + occurred. +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.restricted-pysrc.html Index: applications/examples/static/epydoc/web2py.gluon.restricted-pysrc.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.restricted-pysrc.html +++ applications/examples/static/epydoc/web2py.gluon.restricted-pysrc.html @@ -0,0 +1,541 @@ + + + + + web2py.gluon.restricted + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module restricted + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Module web2py.gluon.restricted

    +
    +  1  #!/usr/bin/env python 
    +  2  # -*- coding: utf-8 -*- 
    +  3   
    +  4  """ 
    +  5  This file is part of the web2py Web Framework 
    +  6  Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> 
    +  7  License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) 
    +  8  """ 
    +  9   
    + 10  import sys 
    + 11  import cPickle 
    + 12  import traceback 
    + 13  import types 
    + 14  import os 
    + 15  import datetime 
    + 16  import logging 
    + 17   
    + 18  from utils import web2py_uuid 
    + 19  from storage import Storage 
    + 20  from http import HTTP 
    + 21  from html import BEAUTIFY 
    + 22   
    + 23  logger = logging.getLogger("web2py") 
    + 24   
    + 25  __all__ = ['RestrictedError', 'restricted', 'TicketStorage', 'compile2'] 
    + 26   
    +
    27 -class TicketStorage(Storage): +
    28 + 29 """ + 30 defines the ticket object and the default values of its members (None) + 31 """ + 32 +
    33 - def __init__( + 34 self, + 35 db=None, + 36 tablename='web2py_ticket' + 37 ): +
    38 self.db = db + 39 self.tablename = tablename +
    40 +
    41 - def store(self, request, ticket_id, ticket_data): +
    42 """ + 43 stores the ticket. It will figure out if this must be on disk or in db + 44 """ + 45 if self.db: + 46 self._store_in_db(request, ticket_id, ticket_data) + 47 else: + 48 self._store_on_disk(request, ticket_id, ticket_data) +
    49 +
    50 - def _store_in_db(self, request, ticket_id, ticket_data): +
    51 table = self._get_table(self.db, self.tablename, request.application) + 52 table.insert(ticket_id=ticket_id, + 53 ticket_data=cPickle.dumps(ticket_data), + 54 created_datetime=request.now) + 55 logger.error('In FILE: %(layer)s\n\n%(traceback)s\n' % ticket_data) +
    56 +
    57 - def _store_on_disk(self, request, ticket_id, ticket_data): +
    58 ef = self._error_file(request, ticket_id, 'wb') + 59 try: + 60 cPickle.dump(ticket_data, ef) + 61 finally: + 62 ef.close() +
    63 +
    64 - def _error_file(self, request, ticket_id, mode, app=None): +
    65 root = request.folder + 66 if app: + 67 root = os.path.join(os.path.join(root, '..'), app) + 68 errors_folder = os.path.abspath(os.path.join(root, 'errors'))#.replace('\\', '/') + 69 return open(os.path.join(errors_folder, ticket_id), mode) +
    70 +
    71 - def _get_table(self, db, tablename, app): +
    72 tablename = tablename + '_' + app + 73 table = db.get(tablename, None) + 74 if table is None: + 75 db.rollback() # not necessary but one day + 76 # any app may store tickets on DB + 77 table = db.define_table( + 78 tablename, + 79 db.Field('ticket_id', length=100), + 80 db.Field('ticket_data', 'text'), + 81 db.Field('created_datetime', 'datetime'), + 82 ) + 83 return table +
    84 +
    85 - def load( + 86 self, + 87 request, + 88 app, + 89 ticket_id, + 90 ): +
    91 if not self.db: + 92 ef = self._error_file(request, ticket_id, 'rb', app) + 93 try: + 94 return cPickle.load(ef) + 95 finally: + 96 ef.close() + 97 table = self._get_table(self.db, self.tablename, app) + 98 rows = self.db(table.ticket_id == ticket_id).select() + 99 if rows: +100 return cPickle.loads(rows[0].ticket_data) +101 return None +
    102 +103 +
    104 -class RestrictedError(Exception): +
    105 """ +106 class used to wrap an exception that occurs in the restricted environment +107 below. the traceback is used to log the exception and generate a ticket. +108 """ +109 +
    110 - def __init__( +111 self, +112 layer='', +113 code='', +114 output='', +115 environment={}, +116 ): +
    117 """ +118 layer here is some description of where in the system the exception +119 occurred. +120 """ +121 +122 self.layer = layer +123 self.code = code +124 self.output = output +125 if layer: +126 try: +127 self.traceback = traceback.format_exc() +128 except: +129 self.traceback = 'no traceback because template parting error' +130 try: +131 self.snapshot = snapshot(context=10,code=code,environment=environment) +132 except: +133 self.snapshot = {} +134 else: +135 self.traceback = '(no error)' +136 self.snapshot = {} +137 self.environment = environment +
    138 +
    139 - def log(self, request): +
    140 """ +141 logs the exception. +142 """ +143 +144 try: +145 d = { +146 'layer': str(self.layer), +147 'code': str(self.code), +148 'output': str(self.output), +149 'traceback': str(self.traceback), +150 'snapshot': self.snapshot, +151 } +152 ticket_storage = TicketStorage(db=request.tickets_db) +153 ticket_storage.store(request, request.uuid.split('/',1)[1], d) +154 return request.uuid +155 except: +156 logger.error(self.traceback) +157 return None +
    158 +159 +
    160 - def load(self, request, app, ticket_id): +
    161 """ +162 loads a logged exception. +163 """ +164 ticket_storage = TicketStorage(db=request.tickets_db) +165 d = ticket_storage.load(request, app, ticket_id) +166 +167 self.layer = d['layer'] +168 self.code = d['code'] +169 self.output = d['output'] +170 self.traceback = d['traceback'] +171 self.snapshot = d.get('snapshot') +
    172 +173 +
    174 -def compile2(code,layer): +
    175 """ +176 The +'\n' is necessary else compile fails when code ends in a comment. +177 """ +178 return compile(code.rstrip().replace('\r\n','\n')+'\n', layer, 'exec') +
    179 +
    180 -def restricted(code, environment={}, layer='Unknown'): +
    181 """ +182 runs code in environment and returns the output. if an exception occurs +183 in code it raises a RestrictedError containing the traceback. layer is +184 passed to RestrictedError to identify where the error occurred. +185 """ +186 environment['__file__'] = layer +187 try: +188 if type(code) == types.CodeType: +189 ccode = code +190 else: +191 ccode = compile2(code,layer) +192 exec ccode in environment +193 except HTTP: +194 raise +195 except Exception, error: +196 # XXX Show exception in Wing IDE if running in debugger +197 if __debug__ and 'WINGDB_ACTIVE' in os.environ: +198 etype, evalue, tb = sys.exc_info() +199 sys.excepthook(etype, evalue, tb) +200 raise RestrictedError(layer, code, '', environment) +
    201 +
    202 -def snapshot(info=None, context=5, code=None, environment=None): +
    203 """Return a dict describing a given traceback (based on cgitb.text).""" +204 import os, types, time, traceback, linecache, inspect, pydoc, cgitb +205 +206 # if no exception info given, get current: +207 etype, evalue, etb = info or sys.exc_info() +208 +209 if type(etype) is types.ClassType: +210 etype = etype.__name__ +211 +212 # create a snapshot dict with some basic information +213 s = {} +214 s['pyver'] = 'Python ' + sys.version.split()[0] + ': ' + sys.executable +215 s['date'] = time.ctime(time.time()) +216 +217 # start to process frames +218 records = inspect.getinnerframes(etb, context) +219 s['frames'] = [] +220 for frame, file, lnum, func, lines, index in records: +221 file = file and os.path.abspath(file) or '?' +222 args, varargs, varkw, locals = inspect.getargvalues(frame) +223 call = '' +224 if func != '?': +225 call = inspect.formatargvalues(args, varargs, varkw, locals, +226 formatvalue=lambda value: '=' + pydoc.text.repr(value)) +227 +228 # basic frame information +229 f = {'file': file, 'func': func, 'call': call, 'lines': {}, 'lnum': lnum} +230 +231 highlight = {} +232 def reader(lnum=[lnum]): +233 highlight[lnum[0]] = 1 +234 try: return linecache.getline(file, lnum[0]) +235 finally: lnum[0] += 1 +
    236 vars = cgitb.scanvars(reader, frame, locals) +237 +238 # if it is a view, replace with generated code +239 if file.endswith('html'): +240 lmin = lnum>context and (lnum-context) or 0 +241 lmax = lnum+context +242 lines = code.split("\n")[lmin:lmax] +243 index = min(context, lnum) - 1 +244 +245 if index is not None: +246 i = lnum - index +247 for line in lines: +248 f['lines'][i] = line.rstrip() +249 i += 1 +250 +251 # dump local variables (referenced in current line only) +252 f['dump'] = {} +253 for name, where, value in vars: +254 if name in f['dump']: continue +255 if value is not cgitb.__UNDEF__: +256 if where == 'global': name = 'global ' + name +257 elif where != 'local': name = where + name.split('.')[-1] +258 f['dump'][name] = pydoc.text.repr(value) +259 else: +260 f['dump'][name] = 'undefined' +261 +262 s['frames'].append(f) +263 +264 # add exception type, value and attributes +265 s['etype'] = str(etype) +266 s['evalue'] = str(evalue) +267 s['exception'] = {} +268 if isinstance(evalue, BaseException): +269 for name in dir(evalue): +270 # prevent py26 DeprecatedWarning: +271 if name!='message' or sys.version_info<(2.6): +272 value = pydoc.text.repr(getattr(evalue, name)) +273 s['exception'][name] = value +274 +275 # add all local values (of last frame) to the snapshot +276 s['locals'] = {} +277 for name, value in locals.items(): +278 s['locals'][name] = pydoc.text.repr(value) +279 +280 # add web2py environment variables +281 for k,v in environment.items(): +282 if k in ('request', 'response', 'session'): +283 s[k] = BEAUTIFY(v) +284 +285 return s +286 +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.restricted.RestrictedError-class.html Index: applications/examples/static/epydoc/web2py.gluon.restricted.RestrictedError-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.restricted.RestrictedError-class.html +++ applications/examples/static/epydoc/web2py.gluon.restricted.RestrictedError-class.html @@ -0,0 +1,307 @@ + + + + + web2py.gluon.restricted.RestrictedError + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module restricted :: + Class RestrictedError + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class RestrictedError

    source code

    +
    +              object --+        
    +                       |        
    +exceptions.BaseException --+    
    +                           |    
    +        exceptions.Exception --+
    +                               |
    +                              RestrictedError
    +
    + +
    +class used to wrap an exception that occurs in the restricted + environment below. the traceback is used to log the exception and + generate a ticket.

    + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + layer='', + code='', + output='', + environment={})
    + layer here is some description of where in the system the + exception occurred.
    + source code + +
    + +
    +   + + + + + + +
    log(self, + request)
    + logs the exception.
    + source code + +
    + +
    +   + + + + + + +
    load(self, + request, + app, + ticket_id)
    + loads a logged exception.
    + source code + +
    + +
    +

    Inherited from exceptions.Exception: + __new__ +

    +

    Inherited from exceptions.BaseException: + __delattr__, + __getattribute__, + __getitem__, + __getslice__, + __reduce__, + __repr__, + __setattr__, + __setstate__, + __str__ +

    +

    Inherited from object: + __hash__, + __reduce_ex__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from exceptions.BaseException: + args, + message +

    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + layer='', + code='', + output='', + environment={}) +
    (Constructor) +

    +
    source code  +
    + + layer here is some description of where in the system the exception + occurred. +
    +
    Overrides: + exceptions.Exception.__init__ +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.restricted.TicketStorage-class.html Index: applications/examples/static/epydoc/web2py.gluon.restricted.TicketStorage-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.restricted.TicketStorage-class.html +++ applications/examples/static/epydoc/web2py.gluon.restricted.TicketStorage-class.html @@ -0,0 +1,436 @@ + + + + + web2py.gluon.restricted.TicketStorage + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module restricted :: + Class TicketStorage + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class TicketStorage

    source code

    +
    + object --+        
    +          |        
    +       dict --+    
    +              |    
    +storage.Storage --+
    +                  |
    +                 TicketStorage
    +
    + +
    +defines the ticket object and the default values of its members + (None)

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + db=1, + tablename='web2py_ticket')
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    store(self, + request, + ticket_id, + ticket_data)
    + stores the ticket.
    + source code + +
    + +
    +   + + + + + + +
    _store_in_db(self, + request, + ticket_id, + ticket_data) + source code + +
    + +
    +   + + + + + + +
    _store_on_disk(self, + request, + ticket_id, + ticket_data) + source code + +
    + +
    +   + + + + + + +
    _error_file(self, + request, + ticket_id, + mode, + app=1) + source code + +
    + +
    +   + + + + + + +
    _get_table(self, + db, + tablename, + app) + source code + +
    + +
    +   + + + + + + +
    load(self, + request, + app, + ticket_id) + source code + +
    + +
    +

    Inherited from storage.Storage: + __delattr__, + __getattr__, + __getstate__, + __repr__, + __setattr__, + __setstate__, + getfirst, + getlast, + getlist +

    +

    Inherited from dict: + __cmp__, + __contains__, + __delitem__, + __eq__, + __ge__, + __getattribute__, + __getitem__, + __gt__, + __hash__, + __iter__, + __le__, + __len__, + __lt__, + __ne__, + __new__, + __setitem__, + clear, + copy, + fromkeys, + get, + has_key, + items, + iteritems, + iterkeys, + itervalues, + keys, + pop, + popitem, + setdefault, + update, + values +

    +

    Inherited from object: + __reduce__, + __reduce_ex__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + db=1, + tablename='web2py_ticket') +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Returns:
    +
    +new empty dictionary
    +
    +
    +
    Overrides: + dict.__init__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    store(self, + request, + ticket_id, + ticket_data) +

    +
    source code  +
    + + stores the ticket. It will figure out if this must be on disk or in + db +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.rewrite-module.html Index: applications/examples/static/epydoc/web2py.gluon.rewrite-module.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.rewrite-module.html +++ applications/examples/static/epydoc/web2py.gluon.rewrite-module.html @@ -0,0 +1,915 @@ + + + + + web2py.gluon.rewrite + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module rewrite + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Module rewrite

    source code

    +

    This file is part of the web2py Web Framework Copyrighted by Massimo + Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)

    +

    gluon.rewrite parses incoming URLs and formats outgoing URLs for + gluon.html.URL.

    +

    In addition, it rewrites both incoming and outgoing URLs based on the + (optional) user-supplied routes.py, which also allows for rewriting of + certain error messages.

    + routes.py supports two styles of URL rewriting, depending on whether + 'routers' is defined. Refer to router.example.py and routes.example.py + for additional documentation.

    + + + + + + + + + + + + + +
    + + + + + +
    Classes[hide private]
    +
    +   + + MapUrlIn
    + logic for mapping incoming URLs +
    +   + + MapUrlOut
    + logic for mapping outgoing URLs +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Functions[hide private]
    +
    +   + + + + + + +
    _router_default()
    + return new copy of default base router
    + source code + +
    + +
    +   + + + + + + +
    _params_default(app=1)
    + return new copy of default parameters
    + source code + +
    + +
    +   + + + + + + +
    url_in(request, + environ)
    + parse and rewrite incoming URL
    + source code + +
    + +
    +   + + + + + + +
    url_out(request, + env, + application, + controller, + function, + args, + other, + scheme, + host, + port)
    + assemble and rewrite outgoing URL
    + source code + +
    + +
    +   + + + + + + +
    try_rewrite_on_error(http_response, + request, + environ, + ticket=1)
    + called from main.wsgibase to rewrite the http response.
    + source code + +
    + +
    +   + + + + + + +
    try_redirect_on_error(http_object, + request, + ticket=1)
    + called from main.wsgibase to rewrite the http response
    + source code + +
    + +
    +   + + + + + + +
    load(routes='routes.py', + app=1, + data=1, + rdict=1)
    + load: read (if file) and parse routes store results in params + (called from main.py at web2py initialization time) If data is + present, it's used instead of the routes.py contents.
    + source code + +
    + +
    +   + + + + + + +
    compile_regex(k, + v)
    + Preprocess and compile the regular expressions in routes_app/in/out + +The resulting regex will match a pattern of the form: + + [remote address]:[protocol]://[host]:[method] [path] + +We allow abbreviated regexes on input; here we try to complete them.
    + source code + +
    + +
    +   + + + + + + +
    load_routers(all_apps)
    + load-time post-processing of routers
    + source code + +
    + +
    +   + + + + + + +
    regex_uri(e, + regexes, + tag, + default=1)
    + filter incoming URI against a list of regexes
    + source code + +
    + +
    +   + + + + + + +
    regex_select(env=1, + app=1, + request=1)
    + select a set of regex rewrite params for the current request
    + source code + +
    + +
    +   + + + + + + +
    regex_filter_in(e)
    + regex rewrite incoming URL
    + source code + +
    + +
    +   + + + + + + +
    regex_url_in(request, + environ)
    + rewrite and parse incoming URL
    + source code + +
    + +
    +   + + + + + + +
    regex_filter_out(url, + e=1)
    + regex rewrite outgoing URL
    + source code + +
    + +
    +   + + + + + + +
    filter_url(url, + method='get', + remote='0.0.0.0', + out=True, + app=True, + lang=1, + domain=(None, None), + env=True, + scheme=1, + host=1, + port=1)
    + doctest/unittest interface to regex_filter_in() and + regex_filter_out()
    + source code + +
    + +
    +   + + + + + + +
    filter_err(status, + application='app', + ticket='tkt')
    + doctest/unittest interface to routes_onerror
    + source code + +
    + +
    +   + + + + + + +
    map_url_in(request, + env, + app=True)
    + route incoming URL
    + source code + +
    + +
    +   + + + + + + +
    map_url_out(request, + env, + application, + controller, + function, + args, + other, + scheme, + host, + port)
    + supply /a/c/f (or /a/lang/c/f) portion of outgoing url + +The basic rule is that we can only make transformations +that map_url_in can reverse.
    + source code + +
    + +
    +   + + + + + + +
    get_effective_router(appname)
    + return a private copy of the effective router for the specified + application
    + source code + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Variables[hide private]
    +
    +   + + logger = logging.getLogger('web2py.rewrite') +
    +   + + thread = threading.local() +
    +   + + params_apps = {} +
    +   + + params = <Storage {'routes_out': [], 'name': 'BASE', 'routes_a... +
    +   + + routers = 1
    + PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    +   + + ROUTER_KEYS = set(['acfe_match', 'applications', 'args_match',... +
    +   + + ROUTER_BASE_KEYS = set(['applications', 'default_application',... +
    +   + + regex_at = re.compile(r'(?<!\\)\$[a-zA-Z]\w*') +
    +   + + regex_anything = re.compile(r'(?<!\\)\$anything') +
    +   + + regex_space = re.compile(r'(\+|\s|%20)+') +
    +   + + regex_static = re.compile(r'(?x)(^/(?P<b>\w+)/static/(?P<x>(\w... +
    +   + + regex_url = re.compile(r'(?x)(^(/(?P<a>[\w\s\+]+)(/(?P<c>[\w\s... +
    +   + + regex_args = re.compile(r'(?x)(^(?P<s>([\w@/-][=\.]?)*)?/?$)') +
    + + + + + + +
    + + + + + +
    Function Details[hide private]
    +
    + +
    + +
    + + +
    +

    load(routes='routes.py', + app=1, + data=1, + rdict=1) +

    +
    source code  +
    + + load: read (if file) and parse routes store results in params (called + from main.py at web2py initialization time) If data is present, it's used + instead of the routes.py contents. If rdict is present, it must be a dict + to be used for routers (unit test) +
    +
    +
    +
    + +
    + +
    + + +
    +

    map_url_out(request, + env, + application, + controller, + function, + args, + other, + scheme, + host, + port) +

    +
    source code  +
    + +
    +
    +supply /a/c/f (or /a/lang/c/f) portion of outgoing url
    +
    +The basic rule is that we can only make transformations
    +that map_url_in can reverse.
    +
    +Suppose that the incoming arguments are a,c,f,args,lang
    +and that the router defaults are da, dc, df, dl.
    +
    +We can perform these transformations trivially if args=[] and lang=None or dl:
    +
    +/da/dc/df => /
    +/a/dc/df => /a
    +/a/c/df => /a/c
    +
    +We would also like to be able to strip the default application or application/controller
    +from URLs with function/args present, thus:
    +
    +    /da/c/f/args  => /c/f/args
    +    /da/dc/f/args => /f/args
    +
    +We use [applications] and [controllers] and [functions] to suppress ambiguous omissions.
    +
    +We assume that language names do not collide with a/c/f names.
    +
    +
    +
    +
    +
    +
    +
    + + + + + + +
    + + + + + +
    Variables Details[hide private]
    +
    + +
    + +
    +

    params

    + +
    +
    +
    +
    Value:
    +
    +<Storage {'routes_out': [], 'name': 'BASE', 'routes_apps_raw': [], 'er\
    +ror_message': '<html><body><h1>%s</h1></body></html>', 'default_functi\
    +on': 'index', 'default_controller': 'default', 'routes_app': [], 'rout\
    +es_in': [], 'default_application': 'init', 'error_message_ticket': '<h\
    +tml><body><h1>Internal error</h1>Ticket issued: <a href="/admin/defaul\
    +t/ticket/%(ticket)s" target="_blank">%(ticket)s</a></body><!-- this is\
    + junk text else IE does not display the page: xxxxxxxxxxxxxxxxxxxxxxxx\
    +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\
    +...
    +
    +
    +
    +
    +
    + +
    + +
    +

    ROUTER_KEYS

    + +
    +
    +
    +
    Value:
    +
    +set(['acfe_match',
    +     'applications',
    +     'args_match',
    +     'controllers',
    +     'default_application',
    +     'default_controller',
    +     'default_function',
    +     'default_language',
    +...
    +
    +
    +
    +
    +
    + +
    + +
    +

    ROUTER_BASE_KEYS

    + +
    +
    +
    +
    Value:
    +
    +set(['applications', 'default_application', 'domains', 'path_prefix'])
    +
    +
    +
    +
    +
    + +
    + +
    +

    regex_static

    + +
    +
    +
    +
    Value:
    +
    +re.compile(r'(?x)(^/(?P<b>\w+)/static/(?P<x>(\w[-=\./]?)*)$)')
    +
    +
    +
    +
    +
    + +
    + +
    +

    regex_url

    + +
    +
    +
    +
    Value:
    +
    +re.compile(r'(?x)(^(/(?P<a>[\w\s\+]+)(/(?P<c>[\w\s\+]+)(/(?P<f>[\w\s\+\
    +]+)(\.(?P<e>[\w\s\+]+))?(/(?P<r>.*))?)?)?)?/?$)')
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.rewrite-pysrc.html Index: applications/examples/static/epydoc/web2py.gluon.rewrite-pysrc.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.rewrite-pysrc.html +++ applications/examples/static/epydoc/web2py.gluon.rewrite-pysrc.html @@ -0,0 +1,1746 @@ + + + + + web2py.gluon.rewrite + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module rewrite + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Module web2py.gluon.rewrite

    +
    +   1  #!/bin/env python 
    +   2  # -*- coding: utf-8 -*- 
    +   3   
    +   4  """ 
    +   5  This file is part of the web2py Web Framework 
    +   6  Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> 
    +   7  License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) 
    +   8   
    +   9  gluon.rewrite parses incoming URLs and formats outgoing URLs for gluon.html.URL. 
    +  10   
    +  11  In addition, it rewrites both incoming and outgoing URLs based on the (optional) user-supplied routes.py, 
    +  12  which also allows for rewriting of certain error messages. 
    +  13   
    +  14  routes.py supports two styles of URL rewriting, depending on whether 'routers' is defined. 
    +  15  Refer to router.example.py and routes.example.py for additional documentation. 
    +  16   
    +  17  """ 
    +  18   
    +  19  import os 
    +  20  import re 
    +  21  import logging 
    +  22  import traceback 
    +  23  import threading 
    +  24  import urllib 
    +  25  from storage import Storage, List 
    +  26  from http import HTTP 
    +  27  from fileutils import abspath, read_file 
    +  28  from settings import global_settings 
    +  29   
    +  30  logger = logging.getLogger('web2py.rewrite') 
    +  31   
    +  32  thread = threading.local()  # thread-local storage for routing parameters 
    +  33   
    +
    34 -def _router_default(): +
    35 "return new copy of default base router" + 36 router = Storage( + 37 default_application = 'init', + 38 applications = 'ALL', + 39 default_controller = 'default', + 40 controllers = 'DEFAULT', + 41 default_function = 'index', + 42 functions = None, + 43 default_language = None, + 44 languages = None, + 45 root_static = ['favicon.ico', 'robots.txt'], + 46 domains = None, + 47 exclusive_domain = False, + 48 map_hyphen = False, + 49 acfe_match = r'\w+$', # legal app/ctlr/fcn/ext + 50 file_match = r'(\w+[-=./]?)+$', # legal file (path) name + 51 args_match = r'([\w@ -]+[=.]?)*$', # legal arg in args + 52 ) + 53 return router +
    54 +
    55 -def _params_default(app=None): +
    56 "return new copy of default parameters" + 57 p = Storage() + 58 p.name = app or "BASE" + 59 p.default_application = app or "init" + 60 p.default_controller = "default" + 61 p.default_function = "index" + 62 p.routes_app = [] + 63 p.routes_in = [] + 64 p.routes_out = [] + 65 p.routes_onerror = [] + 66 p.routes_apps_raw = [] + 67 p.error_handler = None + 68 p.error_message = '<html><body><h1>%s</h1></body></html>' + 69 p.error_message_ticket = \ + 70 '<html><body><h1>Internal error</h1>Ticket issued: <a href="/admin/default/ticket/%(ticket)s" target="_blank">%(ticket)s</a></body><!-- this is junk text else IE does not display the page: '+('x'*512)+' //--></html>' + 71 p.routers = None + 72 return p +
    73 + 74 params_apps = dict() + 75 params = _params_default(app=None) # regex rewrite parameters + 76 thread.routes = params # default to base regex rewrite parameters + 77 routers = None + 78 + 79 ROUTER_KEYS = set(('default_application', 'applications', 'default_controller', 'controllers', + 80 'default_function', 'functions', 'default_language', 'languages', + 81 'domain', 'domains', 'root_static', 'path_prefix', + 82 'exclusive_domain', 'map_hyphen', 'map_static', + 83 'acfe_match', 'file_match', 'args_match')) + 84 + 85 ROUTER_BASE_KEYS = set(('applications', 'default_application', 'domains', 'path_prefix')) + 86 + 87 # The external interface to rewrite consists of: + 88 # + 89 # load: load routing configuration file(s) + 90 # url_in: parse and rewrite incoming URL + 91 # url_out: assemble and rewrite outgoing URL + 92 # + 93 # thread.routes.default_application + 94 # thread.routes.error_message + 95 # thread.routes.error_message_ticket + 96 # thread.routes.try_redirect_on_error + 97 # thread.routes.error_handler + 98 # + 99 # filter_url: helper for doctest & unittest + 100 # filter_err: helper for doctest & unittest + 101 # regex_filter_out: doctest + 102 +
    103 -def url_in(request, environ): +
    104 "parse and rewrite incoming URL" + 105 if routers: + 106 return map_url_in(request, environ) + 107 return regex_url_in(request, environ) +
    108 +
    109 -def url_out(request, env, application, controller, function, args, other, scheme, host, port): +
    110 "assemble and rewrite outgoing URL" + 111 if routers: + 112 acf = map_url_out(request, env, application, controller, function, args, other, scheme, host, port) + 113 url = '%s%s' % (acf, other) + 114 else: + 115 url = '/%s/%s/%s%s' % (application, controller, function, other) + 116 url = regex_filter_out(url, env) + 117 # + 118 # fill in scheme and host if absolute URL is requested + 119 # scheme can be a string, eg 'http', 'https', 'ws', 'wss' + 120 # + 121 if scheme or port is not None: + 122 if host is None: # scheme or port implies host + 123 host = True + 124 if not scheme or scheme is True: + 125 if request and request.env: + 126 scheme = request.env.get('WSGI_URL_SCHEME', 'http').lower() + 127 else: + 128 scheme = 'http' # some reasonable default in case we need it + 129 if host is not None: + 130 if host is True: + 131 host = request.env.http_host + 132 if host: + 133 if port is None: + 134 port = '' + 135 else: + 136 port = ':%s' % port + 137 url = '%s://%s%s%s' % (scheme, host, port, url) + 138 return url +
    139 +
    140 -def try_rewrite_on_error(http_response, request, environ, ticket=None): +
    141 """ + 142 called from main.wsgibase to rewrite the http response. + 143 """ + 144 status = int(str(http_response.status).split()[0]) + 145 if status>=399 and thread.routes.routes_onerror: + 146 keys=set(('%s/%s' % (request.application, status), + 147 '%s/*' % (request.application), + 148 '*/%s' % (status), + 149 '*/*')) + 150 for (key,uri) in thread.routes.routes_onerror: + 151 if key in keys: + 152 if uri == '!': + 153 # do nothing! + 154 return http_response, environ + 155 elif '?' in uri: + 156 path_info, query_string = uri.split('?',1) + 157 query_string += '&' + 158 else: + 159 path_info, query_string = uri, '' + 160 query_string += \ + 161 'code=%s&ticket=%s&requested_uri=%s&request_url=%s' % \ + 162 (status,ticket,request.env.request_uri,request.url) + 163 if uri.startswith('http://') or uri.startswith('https://'): + 164 # make up a response + 165 url = path_info+'?'+query_string + 166 message = 'You are being redirected <a href="%s">here</a>' + 167 return HTTP(303, message % url, Location=url), environ + 168 elif path_info!=environ['PATH_INFO']: + 169 # rewrite request, call wsgibase recursively, avoid loop + 170 environ['PATH_INFO'] = path_info + 171 environ['QUERY_STRING'] = query_string + 172 return None, environ + 173 # do nothing! + 174 return http_response, environ +
    175 +
    176 -def try_redirect_on_error(http_object, request, ticket=None): +
    177 "called from main.wsgibase to rewrite the http response" + 178 status = int(str(http_object.status).split()[0]) + 179 if status>399 and thread.routes.routes_onerror: + 180 keys=set(('%s/%s' % (request.application, status), + 181 '%s/*' % (request.application), + 182 '*/%s' % (status), + 183 '*/*')) + 184 for (key,redir) in thread.routes.routes_onerror: + 185 if key in keys: + 186 if redir == '!': + 187 break + 188 elif '?' in redir: + 189 url = '%s&code=%s&ticket=%s&requested_uri=%s&request_url=%s' % \ + 190 (redir,status,ticket,request.env.request_uri,request.url) + 191 else: + 192 url = '%s?code=%s&ticket=%s&requested_uri=%s&request_url=%s' % \ + 193 (redir,status,ticket,request.env.request_uri,request.url) + 194 return HTTP(303, + 195 'You are being redirected <a href="%s">here</a>' % url, + 196 Location=url) + 197 return http_object +
    198 + 199 +
    200 -def load(routes='routes.py', app=None, data=None, rdict=None): +
    201 """ + 202 load: read (if file) and parse routes + 203 store results in params + 204 (called from main.py at web2py initialization time) + 205 If data is present, it's used instead of the routes.py contents. + 206 If rdict is present, it must be a dict to be used for routers (unit test) + 207 """ + 208 global params + 209 global routers + 210 if app is None: + 211 # reinitialize + 212 global params_apps + 213 params_apps = dict() + 214 params = _params_default(app=None) # regex rewrite parameters + 215 thread.routes = params # default to base regex rewrite parameters + 216 routers = None + 217 + 218 if isinstance(rdict, dict): + 219 symbols = dict(routers=rdict) + 220 path = 'rdict' + 221 else: + 222 if data is not None: + 223 path = 'routes' + 224 else: + 225 if app is None: + 226 path = abspath(routes) + 227 else: + 228 path = abspath('applications', app, routes) + 229 if not os.path.exists(path): + 230 return + 231 data = read_file(path).replace('\r\n','\n') + 232 + 233 symbols = {} + 234 try: + 235 exec (data + '\n') in symbols + 236 except SyntaxError, e: + 237 logger.error( + 238 '%s has a syntax error and will not be loaded\n' % path + 239 + traceback.format_exc()) + 240 raise e + 241 + 242 p = _params_default(app) + 243 + 244 for sym in ('routes_app', 'routes_in', 'routes_out'): + 245 if sym in symbols: + 246 for (k, v) in symbols[sym]: + 247 p[sym].append(compile_regex(k, v)) + 248 for sym in ('routes_onerror', 'routes_apps_raw', + 249 'error_handler','error_message', 'error_message_ticket', + 250 'default_application','default_controller', 'default_function'): + 251 if sym in symbols: + 252 p[sym] = symbols[sym] + 253 if 'routers' in symbols: + 254 p.routers = Storage(symbols['routers']) + 255 for key in p.routers: + 256 if isinstance(p.routers[key], dict): + 257 p.routers[key] = Storage(p.routers[key]) + 258 + 259 if app is None: + 260 params = p # install base rewrite parameters + 261 thread.routes = params # install default as current routes + 262 # + 263 # create the BASE router if routers in use + 264 # + 265 routers = params.routers # establish routers if present + 266 if isinstance(routers, dict): + 267 routers = Storage(routers) + 268 if routers is not None: + 269 router = _router_default() + 270 if routers.BASE: + 271 router.update(routers.BASE) + 272 routers.BASE = router + 273 + 274 # scan each app in applications/ + 275 # create a router, if routers are in use + 276 # parse the app-specific routes.py if present + 277 # + 278 all_apps = [] + 279 for appname in [app for app in os.listdir(abspath('applications')) if not app.startswith('.')]: + 280 if os.path.isdir(abspath('applications', appname)) and \ + 281 os.path.isdir(abspath('applications', appname, 'controllers')): + 282 all_apps.append(appname) + 283 if routers: + 284 router = Storage(routers.BASE) # new copy + 285 if appname in routers: + 286 for key in routers[appname].keys(): + 287 if key in ROUTER_BASE_KEYS: + 288 raise SyntaxError, "BASE-only key '%s' in router '%s'" % (key, appname) + 289 router.update(routers[appname]) + 290 routers[appname] = router + 291 if os.path.exists(abspath('applications', appname, routes)): + 292 load(routes, appname) + 293 + 294 if routers: + 295 load_routers(all_apps) + 296 + 297 else: # app + 298 params_apps[app] = p + 299 if routers and p.routers: + 300 if app in p.routers: + 301 routers[app].update(p.routers[app]) + 302 + 303 logger.debug('URL rewrite is on. configuration in %s' % path) +
    304 + 305 + 306 regex_at = re.compile(r'(?<!\\)\$[a-zA-Z]\w*') + 307 regex_anything = re.compile(r'(?<!\\)\$anything') + 308 +
    309 -def compile_regex(k, v): +
    310 """ + 311 Preprocess and compile the regular expressions in routes_app/in/out + 312 + 313 The resulting regex will match a pattern of the form: + 314 + 315 [remote address]:[protocol]://[host]:[method] [path] + 316 + 317 We allow abbreviated regexes on input; here we try to complete them. + 318 """ + 319 k0 = k # original k for error reporting + 320 # bracket regex in ^...$ if not already done + 321 if not k[0] == '^': + 322 k = '^%s' % k + 323 if not k[-1] == '$': + 324 k = '%s$' % k + 325 # if there are no :-separated parts, prepend a catch-all for the IP address + 326 if k.find(':') < 0: + 327 # k = '^.*?:%s' % k[1:] + 328 k = '^.*?:https?://[^:/]+:[a-z]+ %s' % k[1:] + 329 # if there's no ://, provide a catch-all for the protocol, host & method + 330 if k.find('://') < 0: + 331 i = k.find(':/') + 332 if i < 0: + 333 raise SyntaxError, "routes pattern syntax error: path needs leading '/' [%s]" % k0 + 334 k = r'%s:https?://[^:/]+:[a-z]+ %s' % (k[:i], k[i+1:]) + 335 # $anything -> ?P<anything>.* + 336 for item in regex_anything.findall(k): + 337 k = k.replace(item, '(?P<anything>.*)') + 338 # $a (etc) -> ?P<a>\w+ + 339 for item in regex_at.findall(k): + 340 k = k.replace(item, r'(?P<%s>\w+)' % item[1:]) + 341 # same for replacement pattern, but with \g + 342 for item in regex_at.findall(v): + 343 v = v.replace(item, r'\g<%s>' % item[1:]) + 344 return (re.compile(k, re.DOTALL), v) +
    345 +
    346 -def load_routers(all_apps): +
    347 "load-time post-processing of routers" + 348 + 349 for app in routers.keys(): + 350 # initialize apps with routers that aren't present, on behalf of unit tests + 351 if app not in all_apps: + 352 all_apps.append(app) + 353 router = Storage(routers.BASE) # new copy + 354 if app != 'BASE': + 355 for key in routers[app].keys(): + 356 if key in ROUTER_BASE_KEYS: + 357 raise SyntaxError, "BASE-only key '%s' in router '%s'" % (key, app) + 358 router.update(routers[app]) + 359 routers[app] = router + 360 router = routers[app] + 361 for key in router.keys(): + 362 if key not in ROUTER_KEYS: + 363 raise SyntaxError, "unknown key '%s' in router '%s'" % (key, app) + 364 if not router.controllers: + 365 router.controllers = set() + 366 elif not isinstance(router.controllers, str): + 367 router.controllers = set(router.controllers) + 368 if router.functions: + 369 router.functions = set(router.functions) + 370 else: + 371 router.functions = set() + 372 if router.languages: + 373 router.languages = set(router.languages) + 374 else: + 375 router.languages = set() + 376 if app != 'BASE': + 377 for base_only in ROUTER_BASE_KEYS: + 378 router.pop(base_only, None) + 379 if 'domain' in router: + 380 routers.BASE.domains[router.domain] = app + 381 if isinstance(router.controllers, str) and router.controllers == 'DEFAULT': + 382 router.controllers = set() + 383 if os.path.isdir(abspath('applications', app)): + 384 cpath = abspath('applications', app, 'controllers') + 385 for cname in os.listdir(cpath): + 386 if os.path.isfile(abspath(cpath, cname)) and cname.endswith('.py'): + 387 router.controllers.add(cname[:-3]) + 388 if router.controllers: + 389 router.controllers.add('static') + 390 router.controllers.add(router.default_controller) + 391 if router.functions: + 392 router.functions.add(router.default_function) + 393 + 394 if isinstance(routers.BASE.applications, str) and routers.BASE.applications == 'ALL': + 395 routers.BASE.applications = list(all_apps) + 396 if routers.BASE.applications: + 397 routers.BASE.applications = set(routers.BASE.applications) + 398 else: + 399 routers.BASE.applications = set() + 400 + 401 for app in routers.keys(): + 402 # set router name + 403 router = routers[app] + 404 router.name = app + 405 # compile URL validation patterns + 406 router._acfe_match = re.compile(router.acfe_match) + 407 router._file_match = re.compile(router.file_match) + 408 if router.args_match: + 409 router._args_match = re.compile(router.args_match) + 410 # convert path_prefix to a list of path elements + 411 if router.path_prefix: + 412 if isinstance(router.path_prefix, str): + 413 router.path_prefix = router.path_prefix.strip('/').split('/') + 414 + 415 # rewrite BASE.domains as tuples + 416 # + 417 # key: 'domain[:port]' -> (domain, port) + 418 # value: 'application[/controller] -> (application, controller) + 419 # (port and controller may be None) + 420 # + 421 domains = dict() + 422 if routers.BASE.domains: + 423 for (domain, app) in [(d.strip(':'), a.strip('/')) for (d, a) in routers.BASE.domains.items()]: + 424 port = None + 425 if ':' in domain: + 426 (domain, port) = domain.split(':') + 427 ctlr = None + 428 if '/' in app: + 429 (app, ctlr) = app.split('/') + 430 if app not in all_apps and app not in routers: + 431 raise SyntaxError, "unknown app '%s' in domains" % app + 432 domains[(domain, port)] = (app, ctlr) + 433 routers.BASE.domains = domains +
    434 +
    435 -def regex_uri(e, regexes, tag, default=None): +
    436 "filter incoming URI against a list of regexes" + 437 path = e['PATH_INFO'] + 438 host = e.get('HTTP_HOST', 'localhost').lower() + 439 i = host.find(':') + 440 if i > 0: + 441 host = host[:i] + 442 key = '%s:%s://%s:%s %s' % \ + 443 (e.get('REMOTE_ADDR','localhost'), + 444 e.get('WSGI_URL_SCHEME', 'http').lower(), host, + 445 e.get('REQUEST_METHOD', 'get').lower(), path) + 446 for (regex, value) in regexes: + 447 if regex.match(key): + 448 rewritten = regex.sub(value, key) + 449 logger.debug('%s: [%s] [%s] -> %s' % (tag, key, value, rewritten)) + 450 return rewritten + 451 logger.debug('%s: [%s] -> %s (not rewritten)' % (tag, key, default)) + 452 return default +
    453 +
    454 -def regex_select(env=None, app=None, request=None): +
    455 """ + 456 select a set of regex rewrite params for the current request + 457 """ + 458 if app: + 459 thread.routes = params_apps.get(app, params) + 460 elif env and params.routes_app: + 461 if routers: + 462 map_url_in(request, env, app=True) + 463 else: + 464 app = regex_uri(env, params.routes_app, "routes_app") + 465 thread.routes = params_apps.get(app, params) + 466 else: + 467 thread.routes = params # default to base rewrite parameters + 468 logger.debug("select routing parameters: %s" % thread.routes.name) + 469 return app # for doctest +
    470 +
    471 -def regex_filter_in(e): +
    472 "regex rewrite incoming URL" + 473 query = e.get('QUERY_STRING', None) + 474 e['WEB2PY_ORIGINAL_URI'] = e['PATH_INFO'] + (query and ('?' + query) or '') + 475 if thread.routes.routes_in: + 476 path = regex_uri(e, thread.routes.routes_in, "routes_in", e['PATH_INFO']) + 477 items = path.split('?', 1) + 478 e['PATH_INFO'] = items[0] + 479 if len(items) > 1: + 480 if query: + 481 query = items[1] + '&' + query + 482 else: + 483 query = items[1] + 484 e['QUERY_STRING'] = query + 485 e['REQUEST_URI'] = e['PATH_INFO'] + (query and ('?' + query) or '') + 486 return e +
    487 + 488 + 489 # pattern to replace spaces with underscore in URL + 490 # also the html escaped variants '+' and '%20' are covered + 491 regex_space = re.compile('(\+|\s|%20)+') + 492 + 493 # pattern to find valid paths in url /application/controller/... + 494 # this could be: + 495 # for static pages: + 496 # /<b:application>/static/<x:file> + 497 # for dynamic pages: + 498 # /<a:application>[/<c:controller>[/<f:function>[.<e:ext>][/<s:args>]]] + 499 # application, controller, function and ext may only contain [a-zA-Z0-9_] + 500 # file and args may also contain '-', '=', '.' and '/' + 501 # apps in routes_apps_raw must parse raw_args into args + 502 + 503 regex_static = re.compile(r''' + 504 (^ # static pages + 505 /(?P<b> \w+) # b=app + 506 /static # /b/static + 507 /(?P<x> (\w[\-\=\./]?)* ) # x=file + 508 $) + 509 ''', re.X) + 510 + 511 regex_url = re.compile(r''' + 512 (^( # (/a/c/f.e/s) + 513 /(?P<a> [\w\s+]+ ) # /a=app + 514 ( # (/c.f.e/s) + 515 /(?P<c> [\w\s+]+ ) # /a/c=controller + 516 ( # (/f.e/s) + 517 /(?P<f> [\w\s+]+ ) # /a/c/f=function + 518 ( # (.e) + 519 \.(?P<e> [\w\s+]+ ) # /a/c/f.e=extension + 520 )? + 521 ( # (/s) + 522 /(?P<r> # /a/c/f.e/r=raw_args + 523 .* + 524 ) + 525 )? + 526 )? + 527 )? + 528 )? + 529 /?$) + 530 ''', re.X) + 531 + 532 regex_args = re.compile(r''' + 533 (^ + 534 (?P<s> + 535 ( [\w@/-][=.]? )* # s=args + 536 )? + 537 /?$) # trailing slash + 538 ''', re.X) + 539 +
    540 -def regex_url_in(request, environ): +
    541 "rewrite and parse incoming URL" + 542 + 543 # ################################################## + 544 # select application + 545 # rewrite URL if routes_in is defined + 546 # update request.env + 547 # ################################################## + 548 + 549 regex_select(env=environ, request=request) + 550 + 551 if thread.routes.routes_in: + 552 environ = regex_filter_in(environ) + 553 + 554 for (key, value) in environ.items(): + 555 request.env[key.lower().replace('.', '_')] = value + 556 + 557 path = request.env.path_info.replace('\\', '/') + 558 + 559 # ################################################## + 560 # serve if a static file + 561 # ################################################## + 562 + 563 match = regex_static.match(regex_space.sub('_', path)) + 564 if match and match.group('x'): + 565 static_file = os.path.join(request.env.applications_parent, + 566 'applications', match.group('b'), + 567 'static', match.group('x')) + 568 return (static_file, environ) + 569 + 570 # ################################################## + 571 # parse application, controller and function + 572 # ################################################## + 573 + 574 path = re.sub('%20', ' ', path) + 575 match = regex_url.match(path) + 576 if not match or match.group('c') == 'static': + 577 raise HTTP(400, + 578 thread.routes.error_message % 'invalid request', + 579 web2py_error='invalid path') + 580 + 581 request.application = \ + 582 regex_space.sub('_', match.group('a') or thread.routes.default_application) + 583 request.controller = \ + 584 regex_space.sub('_', match.group('c') or thread.routes.default_controller) + 585 request.function = \ + 586 regex_space.sub('_', match.group('f') or thread.routes.default_function) + 587 group_e = match.group('e') + 588 request.raw_extension = group_e and regex_space.sub('_', group_e) or None + 589 request.extension = request.raw_extension or 'html' + 590 request.raw_args = match.group('r') + 591 request.args = List([]) + 592 if request.application in thread.routes.routes_apps_raw: + 593 # application is responsible for parsing args + 594 request.args = None + 595 elif request.raw_args: + 596 match = regex_args.match(request.raw_args.replace(' ', '_')) + 597 if match: + 598 group_s = match.group('s') + 599 request.args = \ + 600 List((group_s and group_s.split('/')) or []) + 601 if request.args and request.args[-1] == '': + 602 request.args.pop() # adjust for trailing empty arg + 603 else: + 604 raise HTTP(400, + 605 thread.routes.error_message % 'invalid request', + 606 web2py_error='invalid path (args)') + 607 return (None, environ) +
    608 + 609 +
    610 -def regex_filter_out(url, e=None): +
    611 "regex rewrite outgoing URL" + 612 if not hasattr(thread, 'routes'): + 613 regex_select() # ensure thread.routes is set (for application threads) + 614 if routers: + 615 return url # already filtered + 616 if thread.routes.routes_out: + 617 items = url.split('?', 1) + 618 if e: + 619 host = e.get('http_host', 'localhost').lower() + 620 i = host.find(':') + 621 if i > 0: + 622 host = host[:i] + 623 items[0] = '%s:%s://%s:%s %s' % \ + 624 (e.get('remote_addr', ''), + 625 e.get('wsgi_url_scheme', 'http').lower(), host, + 626 e.get('request_method', 'get').lower(), items[0]) + 627 else: + 628 items[0] = ':http://localhost:get %s' % items[0] + 629 for (regex, value) in thread.routes.routes_out: + 630 if regex.match(items[0]): + 631 rewritten = '?'.join([regex.sub(value, items[0])] + items[1:]) + 632 logger.debug('routes_out: [%s] -> %s' % (url, rewritten)) + 633 return rewritten + 634 logger.debug('routes_out: [%s] not rewritten' % url) + 635 return url +
    636 + 637 +
    638 -def filter_url(url, method='get', remote='0.0.0.0', out=False, app=False, lang=None, + 639 domain=(None,None), env=False, scheme=None, host=None, port=None): +
    640 "doctest/unittest interface to regex_filter_in() and regex_filter_out()" + 641 regex_url = re.compile(r'^(?P<scheme>http|https|HTTP|HTTPS)\://(?P<host>[^/]*)(?P<uri>.*)') + 642 match = regex_url.match(url) + 643 urlscheme = match.group('scheme').lower() + 644 urlhost = match.group('host').lower() + 645 uri = match.group('uri') + 646 k = uri.find('?') + 647 if k < 0: + 648 k = len(uri) + 649 (path_info, query_string) = (uri[:k], uri[k+1:]) + 650 path_info = urllib.unquote(path_info) # simulate server + 651 e = { + 652 'REMOTE_ADDR': remote, + 653 'REQUEST_METHOD': method, + 654 'WSGI_URL_SCHEME': urlscheme, + 655 'HTTP_HOST': urlhost, + 656 'REQUEST_URI': uri, + 657 'PATH_INFO': path_info, + 658 'QUERY_STRING': query_string, + 659 #for filter_out request.env use lowercase + 660 'remote_addr': remote, + 661 'request_method': method, + 662 'wsgi_url_scheme': urlscheme, + 663 'http_host': urlhost + 664 } + 665 + 666 request = Storage() + 667 e["applications_parent"] = global_settings.applications_parent + 668 request.env = Storage(e) + 669 request.uri_language = lang + 670 + 671 # determine application only + 672 # + 673 if app: + 674 if routers: + 675 return map_url_in(request, e, app=True) + 676 return regex_select(e) + 677 + 678 # rewrite outbound URL + 679 # + 680 if out: + 681 (request.env.domain_application, request.env.domain_controller) = domain + 682 items = path_info.lstrip('/').split('/') + 683 if items[-1] == '': + 684 items.pop() # adjust trailing empty args + 685 assert len(items) >= 3, "at least /a/c/f is required" + 686 a = items.pop(0) + 687 c = items.pop(0) + 688 f = items.pop(0) + 689 if not routers: + 690 return regex_filter_out(uri, e) + 691 acf = map_url_out(request, None, a, c, f, items, None, scheme, host, port) + 692 if items: + 693 url = '%s/%s' % (acf, '/'.join(items)) + 694 if items[-1] == '': + 695 url += '/' + 696 else: + 697 url = acf + 698 if query_string: + 699 url += '?' + query_string + 700 return url + 701 + 702 # rewrite inbound URL + 703 # + 704 (static, e) = url_in(request, e) + 705 if static: + 706 return static + 707 result = "/%s/%s/%s" % (request.application, request.controller, request.function) + 708 if request.extension and request.extension != 'html': + 709 result += ".%s" % request.extension + 710 if request.args: + 711 result += " %s" % request.args + 712 if e['QUERY_STRING']: + 713 result += " ?%s" % e['QUERY_STRING'] + 714 if request.uri_language: + 715 result += " (%s)" % request.uri_language + 716 if env: + 717 return request.env + 718 return result +
    719 + 720 +
    721 -def filter_err(status, application='app', ticket='tkt'): +
    722 "doctest/unittest interface to routes_onerror" + 723 if status > 399 and thread.routes.routes_onerror: + 724 keys = set(('%s/%s' % (application, status), + 725 '%s/*' % (application), + 726 '*/%s' % (status), + 727 '*/*')) + 728 for (key,redir) in thread.routes.routes_onerror: + 729 if key in keys: + 730 if redir == '!': + 731 break + 732 elif '?' in redir: + 733 url = redir + '&' + 'code=%s&ticket=%s' % (status,ticket) + 734 else: + 735 url = redir + '?' + 'code=%s&ticket=%s' % (status,ticket) + 736 return url # redirection + 737 return status # no action +
    738 + 739 # router support + 740 # +
    741 -class MapUrlIn(object): +
    742 "logic for mapping incoming URLs" + 743 +
    744 - def __init__(self, request=None, env=None): +
    745 "initialize a map-in object" + 746 self.request = request + 747 self.env = env + 748 + 749 self.router = None + 750 self.application = None + 751 self.language = None + 752 self.controller = None + 753 self.function = None + 754 self.extension = 'html' + 755 + 756 self.controllers = set() + 757 self.functions = set() + 758 self.languages = set() + 759 self.default_language = None + 760 self.map_hyphen = False + 761 self.exclusive_domain = False + 762 + 763 path = self.env['PATH_INFO'] + 764 self.query = self.env.get('QUERY_STRING', None) + 765 path = path.lstrip('/') + 766 self.env['PATH_INFO'] = '/' + path + 767 self.env['WEB2PY_ORIGINAL_URI'] = self.env['PATH_INFO'] + (self.query and ('?' + self.query) or '') + 768 + 769 # to handle empty args, strip exactly one trailing slash, if present + 770 # .../arg1// represents one trailing empty arg + 771 # + 772 if path.endswith('/'): + 773 path = path[:-1] + 774 self.args = List(path and path.split('/') or []) + 775 + 776 # see http://www.python.org/dev/peps/pep-3333/#url-reconstruction for URL composition + 777 self.remote_addr = self.env.get('REMOTE_ADDR','localhost') + 778 self.scheme = self.env.get('WSGI_URL_SCHEME', 'http').lower() + 779 self.method = self.env.get('REQUEST_METHOD', 'get').lower() + 780 self.host = self.env.get('HTTP_HOST') + 781 self.port = None + 782 if not self.host: + 783 self.host = self.env.get('SERVER_NAME') + 784 self.port = self.env.get('SERVER_PORT') + 785 if not self.host: + 786 self.host = 'localhost' + 787 self.port = '80' + 788 if ':' in self.host: + 789 (self.host, self.port) = self.host.split(':') + 790 if not self.port: + 791 if self.scheme == 'https': + 792 self.port = '443' + 793 else: + 794 self.port = '80' +
    795 +
    796 - def map_prefix(self): +
    797 "strip path prefix, if present in its entirety" + 798 prefix = routers.BASE.path_prefix + 799 if prefix: + 800 prefixlen = len(prefix) + 801 if prefixlen > len(self.args): + 802 return + 803 for i in xrange(prefixlen): + 804 if prefix[i] != self.args[i]: + 805 return # prefix didn't match + 806 self.args = List(self.args[prefixlen:]) # strip the prefix +
    807 +
    808 - def map_app(self): +
    809 "determine application name" + 810 base = routers.BASE # base router + 811 self.domain_application = None + 812 self.domain_controller = None + 813 arg0 = self.harg0 + 814 if base.applications and arg0 in base.applications: + 815 self.application = arg0 + 816 elif (self.host, self.port) in base.domains: + 817 (self.application, self.domain_controller) = base.domains[(self.host, self.port)] + 818 self.env['domain_application'] = self.application + 819 self.env['domain_controller'] = self.domain_controller + 820 elif (self.host, None) in base.domains: + 821 (self.application, self.domain_controller) = base.domains[(self.host, None)] + 822 self.env['domain_application'] = self.application + 823 self.env['domain_controller'] = self.domain_controller + 824 elif arg0 and not base.applications: + 825 self.application = arg0 + 826 else: + 827 self.application = base.default_application or '' + 828 self.pop_arg_if(self.application == arg0) + 829 + 830 if not base._acfe_match.match(self.application): + 831 raise HTTP(400, thread.routes.error_message % 'invalid request', + 832 web2py_error="invalid application: '%s'" % self.application) + 833 + 834 if self.application not in routers and \ + 835 (self.application != thread.routes.default_application or self.application == 'welcome'): + 836 raise HTTP(400, thread.routes.error_message % 'invalid request', + 837 web2py_error="unknown application: '%s'" % self.application) + 838 + 839 # set the application router + 840 # + 841 logger.debug("select application=%s" % self.application) + 842 self.request.application = self.application + 843 if self.application not in routers: + 844 self.router = routers.BASE # support gluon.main.wsgibase init->welcome + 845 else: + 846 self.router = routers[self.application] # application router + 847 self.controllers = self.router.controllers + 848 self.default_controller = self.domain_controller or self.router.default_controller + 849 self.functions = self.router.functions + 850 self.languages = self.router.languages + 851 self.default_language = self.router.default_language + 852 self.map_hyphen = self.router.map_hyphen + 853 self.exclusive_domain = self.router.exclusive_domain + 854 self._acfe_match = self.router._acfe_match + 855 self._file_match = self.router._file_match + 856 self._args_match = self.router._args_match +
    857 +
    858 - def map_root_static(self): +
    859 ''' + 860 handle root-static files (no hyphen mapping) + 861 + 862 a root-static file is one whose incoming URL expects it to be at the root, + 863 typically robots.txt & favicon.ico + 864 ''' + 865 if len(self.args) == 1 and self.arg0 in self.router.root_static: + 866 self.controller = self.request.controller = 'static' + 867 root_static_file = os.path.join(self.request.env.applications_parent, + 868 'applications', self.application, + 869 self.controller, self.arg0) + 870 logger.debug("route: root static=%s" % root_static_file) + 871 return root_static_file + 872 return None +
    873 +
    874 - def map_language(self): +
    875 "handle language (no hyphen mapping)" + 876 arg0 = self.arg0 # no hyphen mapping + 877 if arg0 and self.languages and arg0 in self.languages: + 878 self.language = arg0 + 879 else: + 880 self.language = self.default_language + 881 if self.language: + 882 logger.debug("route: language=%s" % self.language) + 883 self.pop_arg_if(self.language == arg0) + 884 arg0 = self.arg0 +
    885 +
    886 - def map_controller(self): +
    887 "identify controller" + 888 # handle controller + 889 # + 890 arg0 = self.harg0 # map hyphens + 891 if not arg0 or (self.controllers and arg0 not in self.controllers): + 892 self.controller = self.default_controller or '' + 893 else: + 894 self.controller = arg0 + 895 self.pop_arg_if(arg0 == self.controller) + 896 logger.debug("route: controller=%s" % self.controller) + 897 if not self.router._acfe_match.match(self.controller): + 898 raise HTTP(400, thread.routes.error_message % 'invalid request', + 899 web2py_error='invalid controller') +
    900 +
    901 - def map_static(self): +
    902 ''' + 903 handle static files + 904 file_match but no hyphen mapping + 905 ''' + 906 if self.controller != 'static': + 907 return None + 908 file = '/'.join(self.args) + 909 if not self.router._file_match.match(file): + 910 raise HTTP(400, thread.routes.error_message % 'invalid request', + 911 web2py_error='invalid static file') + 912 # + 913 # support language-specific static subdirectories, + 914 # eg /appname/en/static/filename => applications/appname/static/en/filename + 915 # if language-specific file doesn't exist, try same file in static + 916 # + 917 if self.language: + 918 static_file = os.path.join(self.request.env.applications_parent, + 919 'applications', self.application, + 920 'static', self.language, file) + 921 if not self.language or not os.path.isfile(static_file): + 922 static_file = os.path.join(self.request.env.applications_parent, + 923 'applications', self.application, + 924 'static', file) + 925 logger.debug("route: static=%s" % static_file) + 926 return static_file +
    927 +
    928 - def map_function(self): +
    929 "handle function.extension" + 930 arg0 = self.harg0 # map hyphens + 931 if not arg0 or self.functions and arg0 not in self.functions and self.controller == self.default_controller: + 932 self.function = self.router.default_function or "" + 933 self.pop_arg_if(arg0 and self.function == arg0) + 934 else: + 935 func_ext = arg0.split('.') + 936 if len(func_ext) > 1: + 937 self.function = func_ext[0] + 938 self.extension = func_ext[-1] + 939 else: + 940 self.function = arg0 + 941 self.pop_arg_if(True) + 942 logger.debug("route: function.ext=%s.%s" % (self.function, self.extension)) + 943 + 944 if not self.router._acfe_match.match(self.function): + 945 raise HTTP(400, thread.routes.error_message % 'invalid request', + 946 web2py_error='invalid function') + 947 if self.extension and not self.router._acfe_match.match(self.extension): + 948 raise HTTP(400, thread.routes.error_message % 'invalid request', + 949 web2py_error='invalid extension') +
    950 +
    951 - def validate_args(self): +
    952 ''' + 953 check args against validation pattern + 954 ''' + 955 for arg in self.args: + 956 if not self.router._args_match.match(arg): + 957 raise HTTP(400, thread.routes.error_message % 'invalid request', + 958 web2py_error='invalid arg <%s>' % arg) +
    959 +
    960 - def update_request(self): +
    961 ''' + 962 update request from self + 963 build env.request_uri + 964 make lower-case versions of http headers in env + 965 ''' + 966 self.request.application = self.application + 967 self.request.controller = self.controller + 968 self.request.function = self.function + 969 self.request.extension = self.extension + 970 self.request.args = self.args + 971 if self.language: + 972 self.request.uri_language = self.language + 973 uri = '/%s/%s/%s' % (self.application, self.controller, self.function) + 974 if self.map_hyphen: + 975 uri = uri.replace('_', '-') + 976 if self.extension != 'html': + 977 uri += '.' + self.extension + 978 if self.language: + 979 uri = '/%s%s' % (self.language, uri) + 980 uri += self.args and urllib.quote('/' + '/'.join([str(x) for x in self.args])) or '' + 981 uri += (self.query and ('?' + self.query) or '') + 982 self.env['REQUEST_URI'] = uri + 983 for (key, value) in self.env.items(): + 984 self.request.env[key.lower().replace('.', '_')] = value +
    985 + 986 @property +
    987 - def arg0(self): +
    988 "return first arg" + 989 return self.args(0) +
    990 + 991 @property +
    992 - def harg0(self): +
    993 "return first arg with optional hyphen mapping" + 994 if self.map_hyphen and self.args(0): + 995 return self.args(0).replace('-', '_') + 996 return self.args(0) +
    997 +
    998 - def pop_arg_if(self, dopop): +
    999 "conditionally remove first arg and return new first arg" +1000 if dopop: +1001 self.args.pop(0) +
    1002 +
    1003 -class MapUrlOut(object): +
    1004 "logic for mapping outgoing URLs" +1005 +
    1006 - def __init__(self, request, env, application, controller, function, args, other, scheme, host, port): +
    1007 "initialize a map-out object" +1008 self.default_application = routers.BASE.default_application +1009 if application in routers: +1010 self.router = routers[application] +1011 else: +1012 self.router = routers.BASE +1013 self.request = request +1014 self.env = env +1015 self.application = application +1016 self.controller = controller +1017 self.function = function +1018 self.args = args +1019 self.other = other +1020 self.scheme = scheme +1021 self.host = host +1022 self.port = port +1023 +1024 self.applications = routers.BASE.applications +1025 self.controllers = self.router.controllers +1026 self.functions = self.router.functions +1027 self.languages = self.router.languages +1028 self.default_language = self.router.default_language +1029 self.exclusive_domain = self.router.exclusive_domain +1030 self.map_hyphen = self.router.map_hyphen +1031 self.map_static = self.router.map_static +1032 self.path_prefix = routers.BASE.path_prefix +1033 +1034 self.domain_application = request and self.request.env.domain_application +1035 self.domain_controller = request and self.request.env.domain_controller +1036 self.default_function = self.router.default_function +1037 +1038 if (self.router.exclusive_domain and self.domain_application and self.domain_application != self.application and not self.host): +1039 raise SyntaxError, 'cross-domain conflict: must specify host' +1040 +1041 lang = request and request.uri_language +1042 if lang and self.languages and lang in self.languages: +1043 self.language = lang +1044 else: +1045 self.language = None +1046 +1047 self.omit_application = False +1048 self.omit_language = False +1049 self.omit_controller = False +1050 self.omit_function = False +
    1051 +
    1052 - def omit_lang(self): +
    1053 "omit language if possible" +1054 +1055 if not self.language or self.language == self.default_language: +1056 self.omit_language = True +
    1057 +
    1058 - def omit_acf(self): +
    1059 "omit what we can of a/c/f" +1060 +1061 router = self.router +1062 +1063 # Handle the easy no-args case of tail-defaults: /a/c /a / +1064 # +1065 if not self.args and self.function == router.default_function: +1066 self.omit_function = True +1067 if self.controller == router.default_controller: +1068 self.omit_controller = True +1069 if self.application == self.default_application: +1070 self.omit_application = True +1071 +1072 # omit default application +1073 # (which might be the domain default application) +1074 # +1075 default_application = self.domain_application or self.default_application +1076 if self.application == default_application: +1077 self.omit_application = True +1078 +1079 # omit controller if default controller +1080 # +1081 default_controller = ((self.application == self.domain_application) and self.domain_controller) or router.default_controller or '' +1082 if self.controller == default_controller: +1083 self.omit_controller = True +1084 +1085 # omit function if default controller/function +1086 # +1087 if self.functions and self.function == self.default_function and self.omit_controller: +1088 self.omit_function = True +1089 +1090 # prohibit ambiguous cases +1091 # +1092 # because we presume the lang string to be unambiguous, its presence protects application omission +1093 # +1094 if self.omit_language: +1095 if not self.applications or self.controller in self.applications: +1096 self.omit_application = False +1097 if self.omit_application: +1098 if not self.applications or self.function in self.applications: +1099 self.omit_controller = False +1100 if not self.controllers or self.function in self.controllers: +1101 self.omit_controller = False +1102 if self.args: +1103 if self.args[0] in self.functions or self.args[0] in self.controllers or self.args[0] in self.applications: +1104 self.omit_function = False +1105 if self.omit_controller: +1106 if self.function in self.controllers or self.function in self.applications: +1107 self.omit_controller = False +1108 if self.omit_application: +1109 if self.controller in self.applications: +1110 self.omit_application = False +1111 +1112 # handle static as a special case +1113 # (easier for external static handling) +1114 # +1115 if self.controller == 'static' or self.controller.startswith('static/'): +1116 if not self.map_static: +1117 self.omit_application = False +1118 if self.language: +1119 self.omit_language = False +1120 self.omit_controller = False +1121 self.omit_function = False +
    1122 +
    1123 - def build_acf(self): +
    1124 "build acf from components" +1125 acf = '' +1126 if self.map_hyphen: +1127 self.application = self.application.replace('_', '-') +1128 self.controller = self.controller.replace('_', '-') +1129 if self.controller != 'static' and not self.controller.startswith('static/'): +1130 self.function = self.function.replace('_', '-') +1131 if not self.omit_application: +1132 acf += '/' + self.application +1133 if not self.omit_language: +1134 acf += '/' + self.language +1135 if not self.omit_controller: +1136 acf += '/' + self.controller +1137 if not self.omit_function: +1138 acf += '/' + self.function +1139 if self.path_prefix: +1140 acf = '/' + '/'.join(self.path_prefix) + acf +1141 if self.args: +1142 return acf +1143 return acf or '/' +
    1144 +
    1145 - def acf(self): +
    1146 "convert components to /app/lang/controller/function" +1147 +1148 if not routers: +1149 return None # use regex filter +1150 self.omit_lang() # try to omit language +1151 self.omit_acf() # try to omit a/c/f +1152 return self.build_acf() # build and return the /a/lang/c/f string +
    1153 +1154 +
    1155 -def map_url_in(request, env, app=False): +
    1156 "route incoming URL" +1157 +1158 # initialize router-url object +1159 # +1160 thread.routes = params # default to base routes +1161 map = MapUrlIn(request=request, env=env) +1162 map.map_prefix() # strip prefix if present +1163 map.map_app() # determine application +1164 +1165 # configure thread.routes for error rewrite +1166 # +1167 if params.routes_app: +1168 thread.routes = params_apps.get(app, params) +1169 +1170 if app: +1171 return map.application +1172 +1173 root_static_file = map.map_root_static() # handle root-static files +1174 if root_static_file: +1175 return (root_static_file, map.env) +1176 map.map_language() +1177 map.map_controller() +1178 static_file = map.map_static() +1179 if static_file: +1180 return (static_file, map.env) +1181 map.map_function() +1182 map.validate_args() +1183 map.update_request() +1184 return (None, map.env) +
    1185 +
    1186 -def map_url_out(request, env, application, controller, function, args, other, scheme, host, port): +
    1187 ''' +1188 supply /a/c/f (or /a/lang/c/f) portion of outgoing url +1189 +1190 The basic rule is that we can only make transformations +1191 that map_url_in can reverse. +1192 +1193 Suppose that the incoming arguments are a,c,f,args,lang +1194 and that the router defaults are da, dc, df, dl. +1195 +1196 We can perform these transformations trivially if args=[] and lang=None or dl: +1197 +1198 /da/dc/df => / +1199 /a/dc/df => /a +1200 /a/c/df => /a/c +1201 +1202 We would also like to be able to strip the default application or application/controller +1203 from URLs with function/args present, thus: +1204 +1205 /da/c/f/args => /c/f/args +1206 /da/dc/f/args => /f/args +1207 +1208 We use [applications] and [controllers] and [functions] to suppress ambiguous omissions. +1209 +1210 We assume that language names do not collide with a/c/f names. +1211 ''' +1212 map = MapUrlOut(request, env, application, controller, function, args, other, scheme, host, port) +1213 return map.acf() +
    1214 +
    1215 -def get_effective_router(appname): +
    1216 "return a private copy of the effective router for the specified application" +1217 if not routers or appname not in routers: +1218 return None +1219 return Storage(routers[appname]) # return a copy +
    1220 +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.rewrite.MapUrlIn-class.html Index: applications/examples/static/epydoc/web2py.gluon.rewrite.MapUrlIn-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.rewrite.MapUrlIn-class.html +++ applications/examples/static/epydoc/web2py.gluon.rewrite.MapUrlIn-class.html @@ -0,0 +1,532 @@ + + + + + web2py.gluon.rewrite.MapUrlIn + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module rewrite :: + Class MapUrlIn + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class MapUrlIn

    source code

    +
    +object --+
    +         |
    +        MapUrlIn
    +
    + +
    +logic for mapping incoming URLs

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + request=1, + env=1)
    + initialize a map-in object
    + source code + +
    + +
    +   + + + + + + +
    map_prefix(self)
    + strip path prefix, if present in its entirety
    + source code + +
    + +
    +   + + + + + + +
    map_app(self)
    + determine application name
    + source code + +
    + +
    +   + + + + + + +
    map_root_static(self)
    + handle root-static files (no hyphen mapping)
    + source code + +
    + +
    +   + + + + + + +
    map_language(self)
    + handle language (no hyphen mapping)
    + source code + +
    + +
    +   + + + + + + +
    map_controller(self)
    + identify controller
    + source code + +
    + +
    +   + + + + + + +
    map_static(self)
    + handle static files file_match but no hyphen mapping
    + source code + +
    + +
    +   + + + + + + +
    map_function(self)
    + handle function.extension
    + source code + +
    + +
    +   + + + + + + +
    validate_args(self)
    + check args against validation pattern
    + source code + +
    + +
    +   + + + + + + +
    update_request(self)
    + update request from self build env.request_uri make lower-case + versions of http headers in env
    + source code + +
    + +
    +   + + + + + + +
    pop_arg_if(self, + dopop)
    + conditionally remove first arg and return new first arg
    + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +   + + arg0
    + return first arg +
    +   + + harg0
    + return first arg with optional hyphen mapping +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + request=1, + env=1) +
    (Constructor) +

    +
    source code  +
    + + initialize a map-in object +
    +
    Overrides: + object.__init__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    map_root_static(self) +

    +
    source code  +
    + +

    handle root-static files (no hyphen mapping)

    + a root-static file is one whose incoming URL expects it to be at the + root, typically robots.txt & favicon.ico +
    +
    +
    +
    +
    + + + + + + +
    + + + + + +
    Property Details[hide private]
    +
    + +
    + +
    +

    arg0

    + return first arg +
    +
    Get Method:
    +
    unreachable.arg0(self) + - return first arg +
    +
    Set Method:
    +
    +1
    +
    + + - PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    +
    Delete Method:
    +
    +1
    +
    + + - PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    +
    +
    +
    + +
    + +
    +

    harg0

    + return first arg with optional hyphen mapping +
    +
    Get Method:
    +
    unreachable.harg0(self) + - return first arg with optional hyphen mapping +
    +
    Set Method:
    +
    +1
    +
    + + - PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    +
    Delete Method:
    +
    +1
    +
    + + - PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.rewrite.MapUrlOut-class.html Index: applications/examples/static/epydoc/web2py.gluon.rewrite.MapUrlOut-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.rewrite.MapUrlOut-class.html +++ applications/examples/static/epydoc/web2py.gluon.rewrite.MapUrlOut-class.html @@ -0,0 +1,330 @@ + + + + + web2py.gluon.rewrite.MapUrlOut + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module rewrite :: + Class MapUrlOut + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class MapUrlOut

    source code

    +
    +object --+
    +         |
    +        MapUrlOut
    +
    + +
    +logic for mapping outgoing URLs

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + request, + env, + application, + controller, + function, + args, + other, + scheme, + host, + port)
    + initialize a map-out object
    + source code + +
    + +
    +   + + + + + + +
    omit_lang(self)
    + omit language if possible
    + source code + +
    + +
    +   + + + + + + +
    omit_acf(self)
    + omit what we can of a/c/f
    + source code + +
    + +
    +   + + + + + + +
    build_acf(self)
    + build acf from components
    + source code + +
    + +
    +   + + + + + + +
    acf(self)
    + convert components to /app/lang/controller/function
    + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + request, + env, + application, + controller, + function, + args, + other, + scheme, + host, + port) +
    (Constructor) +

    +
    source code  +
    + + initialize a map-out object +
    +
    Overrides: + object.__init__ +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.rocket-module.html Index: applications/examples/static/epydoc/web2py.gluon.rocket-module.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.rocket-module.html +++ applications/examples/static/epydoc/web2py.gluon.rocket-module.html @@ -0,0 +1,837 @@ + + + + + web2py.gluon.rocket + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module rocket + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Module rocket

    source code

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Classes[hide private]
    +
    +   + + NullHandler
    + A Logging handler to prevent library errors. +
    +   + + Connection +
    +   + + Listener
    + The Listener class is a class responsible for accepting + connections and queuing them to be processed by a worker + thread. +
    +   + + Rocket
    + The Rocket class is responsible for handling threads and + accepting and dispatching connections. +
    +   + + Monitor +
    +   + + ThreadPool
    + The ThreadPool class is a container class for all the worker + threads. +
    +   + + SSLError +
    +   + + Headers +
    +   + + FileWrapper
    + Wrapper to convert file-like objects to iterables +
    +   + + Worker
    + The Worker class is a base class responsible for receiving + connections and (a subclass) will run an application to process the + the connection +
    +   + + SocketTimeout
    + Exception for when a socket times out between requests. +
    +   + + BadRequest
    + Exception for when a client sends an incomprehensible + request. +
    +   + + SocketClosed
    + Exception for when a socket is closed by the client. +
    +   + + ChunkedReader +
    +   + + WSGIWorker +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Functions[hide private]
    +
    +   + + + + + + +
    b(val)
    + Convert string/unicode/bytes literals into bytes.
    + source code + +
    + +
    +   + + + + + + +
    u(val, + encoding='us-ascii')
    + Convert bytes into string/unicode.
    + source code + +
    + +
    +   + + + + + + +
    CherryPyWSGIServer(bind_addr, + wsgi_app, + numthreads=10, + server_name=1, + max=-1, + request_queue_size=5, + timeout=10, + shutdown_timeout=5)
    + A Cherrypy wsgiserver-compatible wrapper.
    + source code + +
    + +
    +   + + + + + + +
    _formatparam(param, + value=1, + quote=1)
    + Convenience function to format and return a key=value pair.
    + source code + +
    + +
    +   + + + + + + +
    get_method(method) + source code + +
    + +
    +   + + + + + + +
    demo_app(environ, + start_response) + source code + +
    + +
    +   + + + + + + +
    demo() + source code + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Variables[hide private]
    +
    +   + + VERSION = '1.2.2' +
    +   + + SERVER_NAME = 'www' +
    +   + + SERVER_SOFTWARE = 'Rocket 1.2.2' +
    +   + + HTTP_SERVER_SOFTWARE = 'Rocket 1.2.2 Python/2.5.2' +
    +   + + BUF_SIZE = 16384 +
    +   + + SOCKET_TIMEOUT = 1
    + PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    +   + + THREAD_STOP_CHECK_INTERVAL = 1
    + PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    +   + + IS_JYTHON = True +
    +   + + IGNORE_ERRORS_ON_CLOSE = set([103, 104]) +
    +   + + DEFAULT_LISTEN_QUEUE_SIZE = 5 +
    +   + + DEFAULT_MIN_THREADS = 10 +
    +   + + DEFAULT_MAX_THREADS = 0 +
    +   + + DEFAULTS = {'LISTEN_QUEUE_SIZE': 5, 'MAX_THREADS': 0, 'MIN_THR... +
    +   + + PY3K = True +
    +   + + has_ssl = True +
    +   + + log = logging.getLogger('Rocket.Errors.ThreadPool') +
    +   + + re_SLASH = re.compile(r'(?i)%2F') +
    +   + + re_REQUEST_LINE = re.compile(r'(?x)^(?P<method>OPTIONS|GET|HEA... +
    +   + + LOG_LINE = '%(client_ip)s - "%(request_line)s" - %(status)s %(... +
    +   + + RESPONSE = 'HTTP/1.1 %s\nContent-Length: %i\nContent-Type: %s\... +
    +   + + HTTP_METHODS = set(['OPTIONS', 'GET', 'HEAD', 'POST', 'PUT', '... +
    +   + + _tspecials = re.compile(r'[ \(\)<>@,;:\\"/\[\]\?=]') +
    +   + + NEWLINE = '\r\n' +
    +   + + HEADER_RESPONSE = 'HTTP/1.1 %s\r\n%s' +
    +   + + BASE_ENV = {'SCRIPT_NAME': '', 'SERVER_NAME': 'www', 'wsgi.err... +
    + + + + + + +
    + + + + + +
    Function Details[hide private]
    +
    + +
    + +
    + + +
    +

    b(val) +

    +
    source code  +
    + + Convert string/unicode/bytes literals into bytes. This allows for the + same code to run on Python 2.x and 3.x. +
    +
    +
    +
    + +
    + +
    + + +
    +

    u(val, + encoding='us-ascii') +

    +
    source code  +
    + + Convert bytes into string/unicode. This allows for the same code to + run on Python 2.x and 3.x. +
    +
    +
    +
    + +
    + +
    + + +
    +

    _formatparam(param, + value=1, + quote=1) +

    +
    source code  +
    + +

    Convenience function to format and return a key=value pair.

    + This will quote the value if needed or if quote is true. +
    +
    +
    +
    +
    + + + + + + +
    + + + + + +
    Variables Details[hide private]
    +
    + +
    + +
    +

    DEFAULTS

    + +
    +
    +
    +
    Value:
    +
    +{'LISTEN_QUEUE_SIZE': 5, 'MAX_THREADS': 0, 'MIN_THREADS': 10}
    +
    +
    +
    +
    +
    + +
    + +
    +

    re_REQUEST_LINE

    + +
    +
    +
    +
    Value:
    +
    +re.compile(r'(?x)^(?P<method>OPTIONS|GET|HEAD|POST|PUT|DELETE|TRACE|CO\
    +NNECT) ((?P<scheme>[^:/]+)(://)(?P<host>[^/]+))?(?P<path>(\*|/[^ \?]*)\
    +)(\?(?P<query_string>[^ ]+))? (?P<protocol>HTTPS?/1\.[01])$')
    +
    +
    +
    +
    +
    + +
    + +
    +

    LOG_LINE

    + +
    +
    +
    +
    Value:
    +
    +'%(client_ip)s - "%(request_line)s" - %(status)s %(size)s'
    +
    +
    +
    +
    +
    + +
    + +
    +

    RESPONSE

    + +
    +
    +
    +
    Value:
    +
    +'''HTTP/1.1 %s
    +Content-Length: %i
    +Content-Type: %s
    +
    +%s
    +'''
    +
    +
    +
    +
    +
    + +
    + +
    +

    HTTP_METHODS

    + +
    +
    +
    +
    Value:
    +
    +set(['OPTIONS', 'GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'TRACE', 'CONN\
    +ECT'])
    +
    +
    +
    +
    +
    + +
    + +
    +

    BASE_ENV

    + +
    +
    +
    +
    Value:
    +
    +{'SCRIPT_NAME': '',
    + 'SERVER_NAME': 'www',
    + 'wsgi.errors': <epydoc.docintrospecter._DevNull instance at 0x2b630b3\
    +2a440>,
    + 'wsgi.file_wrapper': <class web2py.gluon.rocket.FileWrapper at 0x13da\
    +350>,
    + 'wsgi.multiprocess': False,
    + 'wsgi.run_once': False,
    +...
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.rocket-pysrc.html Index: applications/examples/static/epydoc/web2py.gluon.rocket-pysrc.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.rocket-pysrc.html +++ applications/examples/static/epydoc/web2py.gluon.rocket-pysrc.html @@ -0,0 +1,2594 @@ + + + + + web2py.gluon.rocket + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module rocket + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Module web2py.gluon.rocket

    +
    +   1  # -*- coding: utf-8 -*- 
    +   2   
    +   3  # This file is part of the Rocket Web Server 
    +   4  # Copyright (c) 2010 Timothy Farrell 
    +   5   
    +   6  # Import System Modules 
    +   7  import sys 
    +   8  import errno 
    +   9  import socket 
    +  10  import logging 
    +  11  import platform 
    +  12   
    +  13  # Define Constants 
    +  14  VERSION = '1.2.2' 
    +  15  SERVER_NAME = socket.gethostname() 
    +  16  SERVER_SOFTWARE = 'Rocket %s' % VERSION 
    +  17  HTTP_SERVER_SOFTWARE = '%s Python/%s' % (SERVER_SOFTWARE, sys.version.split(' ')[0]) 
    +  18  BUF_SIZE = 16384 
    +  19  SOCKET_TIMEOUT = 1 # in secs 
    +  20  THREAD_STOP_CHECK_INTERVAL = 1 # in secs, How often should threads check for a server stop message? 
    +  21  IS_JYTHON = platform.system() == 'Java' # Handle special cases for Jython 
    +  22  IGNORE_ERRORS_ON_CLOSE = set([errno.ECONNABORTED, errno.ECONNRESET]) 
    +  23  DEFAULT_LISTEN_QUEUE_SIZE = 5 
    +  24  DEFAULT_MIN_THREADS = 10 
    +  25  DEFAULT_MAX_THREADS = 0 
    +  26  DEFAULTS = dict(LISTEN_QUEUE_SIZE = DEFAULT_LISTEN_QUEUE_SIZE, 
    +  27                  MIN_THREADS = DEFAULT_MIN_THREADS, 
    +  28                  MAX_THREADS = DEFAULT_MAX_THREADS) 
    +  29   
    +  30  PY3K = sys.version_info[0] > 2 
    +  31   
    +
    32 -class NullHandler(logging.Handler): +
    33 "A Logging handler to prevent library errors." +
    34 - def emit(self, record): +
    35 pass +
    36 + 37 if PY3K: +
    38 - def b(val): +
    39 """ Convert string/unicode/bytes literals into bytes. This allows for + 40 the same code to run on Python 2.x and 3.x. """ + 41 if isinstance(val, str): + 42 return val.encode() + 43 else: + 44 return val +
    45 +
    46 - def u(val, encoding="us-ascii"): +
    47 """ Convert bytes into string/unicode. This allows for the + 48 same code to run on Python 2.x and 3.x. """ + 49 if isinstance(val, bytes): + 50 return val.decode(encoding) + 51 else: + 52 return val +
    53 + 54 else: +
    55 - def b(val): +
    56 """ Convert string/unicode/bytes literals into bytes. This allows for + 57 the same code to run on Python 2.x and 3.x. """ + 58 if isinstance(val, unicode): + 59 return val.encode() + 60 else: + 61 return val +
    62 +
    63 - def u(val, encoding="us-ascii"): +
    64 """ Convert bytes into string/unicode. This allows for the + 65 same code to run on Python 2.x and 3.x. """ + 66 if isinstance(val, str): + 67 return val.decode(encoding) + 68 else: + 69 return val +
    70 + 71 # Import Package Modules + 72 # package imports removed in monolithic build + 73 + 74 __all__ = ['VERSION', 'SERVER_SOFTWARE', 'HTTP_SERVER_SOFTWARE', 'BUF_SIZE', + 75 'IS_JYTHON', 'IGNORE_ERRORS_ON_CLOSE', 'DEFAULTS', 'PY3K', 'b', 'u', + 76 'Rocket', 'CherryPyWSGIServer', 'SERVER_NAME', 'NullHandler'] + 77 + 78 # Monolithic build...end of module: rocket\__init__.py + 79 # Monolithic build...start of module: rocket\connection.py + 80 + 81 # Import System Modules + 82 import sys + 83 import time + 84 import socket + 85 try: + 86 import ssl + 87 has_ssl = True + 88 except ImportError: + 89 has_ssl = False + 90 # package imports removed in monolithic build + 91 +
    92 -class Connection(object): +
    93 __slots__ = [ + 94 'setblocking', + 95 'sendall', + 96 'shutdown', + 97 'makefile', + 98 'fileno', + 99 'client_addr', + 100 'client_port', + 101 'server_port', + 102 'socket', + 103 'start_time', + 104 'ssl', + 105 'secure' + 106 ] + 107 +
    108 - def __init__(self, sock_tuple, port, secure=False): +
    109 self.client_addr, self.client_port = sock_tuple[1] + 110 self.server_port = port + 111 self.socket = sock_tuple[0] + 112 self.start_time = time.time() + 113 self.ssl = has_ssl and isinstance(self.socket, ssl.SSLSocket) + 114 self.secure = secure + 115 + 116 if IS_JYTHON: + 117 # In Jython we must set TCP_NODELAY here since it does not + 118 # inherit from the listening socket. + 119 # See: http://bugs.jython.org/issue1309 + 120 self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + 121 + 122 self.socket.settimeout(SOCKET_TIMEOUT) + 123 + 124 self.sendall = self.socket.sendall + 125 self.shutdown = self.socket.shutdown + 126 self.fileno = self.socket.fileno + 127 self.makefile = self.socket.makefile + 128 self.setblocking = self.socket.setblocking +
    129 +
    130 - def close(self): +
    131 if hasattr(self.socket, '_sock'): + 132 try: + 133 self.socket._sock.close() + 134 except socket.error: + 135 info = sys.exc_info() + 136 if info[1].errno != socket.EBADF: + 137 raise info[1] + 138 else: + 139 pass + 140 self.socket.close() +
    141 + 142 # Monolithic build...end of module: rocket\connection.py + 143 # Monolithic build...start of module: rocket\listener.py + 144 + 145 # Import System Modules + 146 import os + 147 import socket + 148 import logging + 149 import traceback + 150 from threading import Thread + 151 + 152 try: + 153 import ssl + 154 from ssl import SSLError + 155 has_ssl = True + 156 except ImportError: + 157 has_ssl = False +
    158 - class SSLError(socket.error): +
    159 pass +
    160 # Import Package Modules + 161 # package imports removed in monolithic build + 162 +
    163 -class Listener(Thread): +
    164 """The Listener class is a class responsible for accepting connections + 165 and queuing them to be processed by a worker thread.""" + 166 +
    167 - def __init__(self, interface, queue_size, active_queue, *args, **kwargs): +
    168 Thread.__init__(self, *args, **kwargs) + 169 + 170 # Instance variables + 171 self.active_queue = active_queue + 172 self.interface = interface + 173 self.addr = interface[0] + 174 self.port = interface[1] + 175 self.secure = len(interface) == 4 and \ + 176 os.path.exists(interface[2]) and \ + 177 os.path.exists(interface[3]) + 178 self.ready = False + 179 + 180 # Error Log + 181 self.err_log = logging.getLogger('Rocket.Errors.Port%i' % self.port) + 182 self.err_log.addHandler(NullHandler()) + 183 + 184 # Build the socket + 185 listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + 186 + 187 if not listener: + 188 self.err_log.error("Failed to get socket.") + 189 return + 190 + 191 if self.secure: + 192 if not has_ssl: + 193 self.err_log.error("ssl module required to serve HTTPS.") + 194 return + 195 elif not os.path.exists(interface[2]): + 196 data = (interface[2], interface[0], interface[1]) + 197 self.err_log.error("Cannot find key file " + 198 "'%s'. Cannot bind to %s:%s" % data) + 199 return + 200 elif not os.path.exists(interface[3]): + 201 data = (interface[3], interface[0], interface[1]) + 202 self.err_log.error("Cannot find certificate file " + 203 "'%s'. Cannot bind to %s:%s" % data) + 204 return + 205 + 206 # Set socket options + 207 try: + 208 listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + 209 except: + 210 msg = "Cannot share socket. Using %s:%i exclusively." + 211 self.err_log.warning(msg % (self.addr, self.port)) + 212 + 213 try: + 214 if not IS_JYTHON: + 215 listener.setsockopt(socket.IPPROTO_TCP, + 216 socket.TCP_NODELAY, + 217 1) + 218 except: + 219 msg = "Cannot set TCP_NODELAY, things might run a little slower" + 220 self.err_log.warning(msg) + 221 + 222 try: + 223 listener.bind((self.addr, self.port)) + 224 except: + 225 msg = "Socket %s:%i in use by other process and it won't share." + 226 self.err_log.error(msg % (self.addr, self.port)) + 227 else: + 228 # We want socket operations to timeout periodically so we can + 229 # check if the server is shutting down + 230 listener.settimeout(THREAD_STOP_CHECK_INTERVAL) + 231 # Listen for new connections allowing queue_size number of + 232 # connections to wait before rejecting a connection. + 233 listener.listen(queue_size) + 234 + 235 self.listener = listener + 236 + 237 self.ready = True +
    238 +
    239 - def wrap_socket(self, sock): +
    240 try: + 241 sock = ssl.wrap_socket(sock, + 242 keyfile = self.interface[2], + 243 certfile = self.interface[3], + 244 server_side = True, + 245 ssl_version = ssl.PROTOCOL_SSLv23) + 246 except SSLError: + 247 # Generally this happens when an HTTP request is received on a + 248 # secure socket. We don't do anything because it will be detected + 249 # by Worker and dealt with appropriately. + 250 pass + 251 + 252 return sock +
    253 + 254 +
    255 - def run(self): +
    256 if not self.ready: + 257 self.err_log.warning('Listener started when not ready.') + 258 return + 259 + 260 if __debug__: + 261 self.err_log.debug('Entering main loop.') + 262 while True: + 263 try: + 264 sock, addr = self.listener.accept() + 265 + 266 if self.secure: + 267 sock = self.wrap_socket(sock) + 268 + 269 self.active_queue.put(((sock, addr), + 270 self.interface[1], + 271 self.secure)) + 272 + 273 except socket.timeout: + 274 # socket.timeout will be raised every THREAD_STOP_CHECK_INTERVAL + 275 # seconds. When that happens, we check if it's time to die. + 276 + 277 if not self.ready: + 278 if __debug__: + 279 self.err_log.debug('Listener exiting.') + 280 return + 281 else: + 282 continue + 283 except: + 284 self.err_log.error(str(traceback.format_exc())) +
    285 + 286 # Monolithic build...end of module: rocket\listener.py + 287 # Monolithic build...start of module: rocket\main.py + 288 + 289 # Import System Modules + 290 import sys + 291 import time + 292 import socket + 293 import logging + 294 import traceback + 295 + 296 try: + 297 from queue import Queue + 298 except ImportError: + 299 from Queue import Queue + 300 + 301 # Import Package Modules + 302 # package imports removed in monolithic build + 303 + 304 + 305 + 306 + 307 + 308 # Setup Logging + 309 log = logging.getLogger('Rocket') + 310 log.addHandler(NullHandler()) + 311 +
    312 -class Rocket(object): +
    313 """The Rocket class is responsible for handling threads and accepting and + 314 dispatching connections.""" + 315 +
    316 - def __init__(self, + 317 interfaces = ('127.0.0.1', 8000), + 318 method = 'wsgi', + 319 app_info = None, + 320 min_threads = None, + 321 max_threads = None, + 322 queue_size = None, + 323 timeout = 600, + 324 handle_signals = True): +
    325 + 326 self.handle_signals = handle_signals + 327 + 328 if not isinstance(interfaces, list): + 329 self.interfaces = [interfaces] + 330 else: + 331 self.interfaces = interfaces + 332 + 333 if min_threads is None: + 334 min_threads = DEFAULTS['MIN_THREADS'] + 335 + 336 if max_threads is None: + 337 max_threads = DEFAULTS['MAX_THREADS'] + 338 + 339 if not queue_size: + 340 if hasattr(socket, 'SOMAXCONN'): + 341 queue_size = socket.SOMAXCONN + 342 else: + 343 queue_size = DEFAULTS['LISTEN_QUEUE_SIZE'] + 344 + 345 if max_threads and queue_size > max_threads: + 346 queue_size = max_threads + 347 + 348 if isinstance(app_info, dict): + 349 app_info['server_software'] = SERVER_SOFTWARE + 350 + 351 monitor_queue = Queue() + 352 active_queue = Queue() + 353 + 354 self._monitor = Monitor(monitor_queue, active_queue, timeout) + 355 + 356 self._threadpool = ThreadPool(get_method(method), + 357 app_info = app_info, + 358 active_queue=active_queue, + 359 monitor_queue = monitor_queue, + 360 min_threads=min_threads, + 361 max_threads=max_threads) + 362 + 363 # Build our socket listeners + 364 self.listeners = [Listener(i, queue_size, active_queue) for i in self.interfaces] + 365 for ndx in range(len(self.listeners)-1, 0, -1): + 366 if not self.listeners[ndx].ready: + 367 del self.listeners[ndx] + 368 + 369 if not self.listeners: + 370 log.critical("No interfaces to listen on...closing.") + 371 sys.exit(1) +
    372 +
    373 - def _sigterm(self, signum, frame): +
    374 log.info('Received SIGTERM') + 375 self.stop() +
    376 +
    377 - def _sighup(self, signum, frame): +
    378 log.info('Received SIGHUP') + 379 self.restart() +
    380 +
    381 - def start(self): +
    382 log.info('Starting %s' % SERVER_SOFTWARE) + 383 + 384 # Set up our shutdown signals + 385 if self.handle_signals: + 386 try: + 387 import signal + 388 signal.signal(signal.SIGTERM, self._sigterm) + 389 signal.signal(signal.SIGUSR1, self._sighup) + 390 except: + 391 log.debug('This platform does not support signals.') + 392 + 393 # Start our worker threads + 394 self._threadpool.start() + 395 + 396 # Start our monitor thread + 397 self._monitor.setDaemon(True) + 398 self._monitor.start() + 399 + 400 # I know that EXPR and A or B is bad but I'm keeping it for Py2.4 + 401 # compatibility. + 402 str_extract = lambda l: (l.addr, l.port, l.secure and '*' or '') + 403 + 404 msg = 'Listening on sockets: ' + 405 msg += ', '.join(['%s:%i%s' % str_extract(l) for l in self.listeners]) + 406 log.info(msg) + 407 + 408 for l in self.listeners: + 409 l.start() + 410 + 411 tp = self._threadpool + 412 dynamic_resize = tp.dynamic_resize + 413 + 414 while not tp.stop_server: + 415 try: + 416 dynamic_resize() + 417 time.sleep(THREAD_STOP_CHECK_INTERVAL) + 418 except KeyboardInterrupt: + 419 # Capture a keyboard interrupt when running from a console + 420 break + 421 except: + 422 if not tp.stop_server: + 423 log.error(str(traceback.format_exc())) + 424 continue + 425 + 426 return self.stop() +
    427 +
    428 - def stop(self, stoplogging = True): +
    429 log.info("Stopping Server") + 430 + 431 # Stop listeners + 432 for l in self.listeners: + 433 l.ready = False + 434 if l.isAlive(): + 435 l.join() + 436 + 437 # Stop Worker threads + 438 self._threadpool.stop() + 439 + 440 # Stop Monitor + 441 self._monitor.stop() + 442 if self._monitor.isAlive(): + 443 self._monitor.join() + 444 + 445 if stoplogging: + 446 logging.shutdown() +
    447 +
    448 - def restart(self): +
    449 self.stop(False) + 450 self.start() +
    451 +
    452 -def CherryPyWSGIServer(bind_addr, + 453 wsgi_app, + 454 numthreads = 10, + 455 server_name = None, + 456 max = -1, + 457 request_queue_size = 5, + 458 timeout = 10, + 459 shutdown_timeout = 5): +
    460 """ A Cherrypy wsgiserver-compatible wrapper. """ + 461 max_threads = max + 462 if max_threads < 0: + 463 max_threads = 0 + 464 return Rocket(bind_addr, 'wsgi', {'wsgi_app': wsgi_app}, + 465 min_threads = numthreads, + 466 max_threads = max_threads, + 467 queue_size = request_queue_size, + 468 timeout = timeout) +
    469 + 470 # Monolithic build...end of module: rocket\main.py + 471 # Monolithic build...start of module: rocket\monitor.py + 472 + 473 # Import System Modules + 474 import time + 475 import logging + 476 import select + 477 from threading import Thread + 478 + 479 # Import Package Modules + 480 # package imports removed in monolithic build + 481 +
    482 -class Monitor(Thread): +
    483 # Monitor worker class. + 484 +
    485 - def __init__(self, + 486 monitor_queue, + 487 active_queue, + 488 timeout, + 489 *args, + 490 **kwargs): +
    491 + 492 Thread.__init__(self, *args, **kwargs) + 493 + 494 # Instance Variables + 495 self.monitor_queue = monitor_queue + 496 self.active_queue = active_queue + 497 self.timeout = timeout + 498 + 499 self.connections = set() + 500 self.active = False +
    501 +
    502 - def run(self): +
    503 self.name = self.getName() + 504 self.log = logging.getLogger('Rocket.Monitor') + 505 self.log.addHandler(NullHandler()) + 506 + 507 self.active = True + 508 conn_list = list() + 509 list_changed = False + 510 + 511 if __debug__: + 512 self.log.debug('Entering monitor loop.') + 513 + 514 # Enter thread main loop + 515 while self.active: + 516 # Move the queued connections to the selection pool + 517 while not self.monitor_queue.empty() or not len(self.connections): + 518 if __debug__: + 519 self.log.debug('In "receive timed-out connections" loop.') + 520 + 521 c = self.monitor_queue.get() + 522 + 523 if c is None: + 524 # A non-client is a signal to die + 525 if __debug__: + 526 self.log.debug('Received a death threat.') + 527 return + 528 + 529 self.log.debug('Received a timed out connection.') + 530 + 531 if __debug__: + 532 assert(c not in self.connections) + 533 + 534 if IS_JYTHON: + 535 # Jython requires a socket to be in Non-blocking mode in + 536 # order to select on it. + 537 c.setblocking(False) + 538 + 539 if __debug__: + 540 self.log.debug('Adding connection to monitor list.') + 541 + 542 self.connections.add(c) + 543 list_changed = True + 544 + 545 # Wait on those connections + 546 self.log.debug('Blocking on connections') + 547 if list_changed: + 548 conn_list = list(self.connections) + 549 list_changed = False + 550 + 551 try: + 552 readable = select.select(conn_list, + 553 [], + 554 [], + 555 THREAD_STOP_CHECK_INTERVAL)[0] + 556 except: + 557 if self.active: + 558 raise + 559 else: + 560 break + 561 + 562 # If we have any readable connections, put them back + 563 for r in readable: + 564 if __debug__: + 565 self.log.debug('Restoring readable connection') + 566 + 567 if IS_JYTHON: + 568 # Jython requires a socket to be in Non-blocking mode in + 569 # order to select on it, but the rest of the code requires + 570 # that it be in blocking mode. + 571 r.setblocking(True) + 572 + 573 r.start_time = time.time() + 574 self.active_queue.put(r) + 575 + 576 self.connections.remove(r) + 577 list_changed = True + 578 + 579 # If we have any stale connections, kill them off. + 580 if self.timeout: + 581 now = time.time() + 582 stale = set() + 583 for c in self.connections: + 584 if (now - c.start_time) >= self.timeout: + 585 stale.add(c) + 586 + 587 for c in stale: + 588 if __debug__: + 589 # "EXPR and A or B" kept for Py2.4 compatibility + 590 data = (c.client_addr, c.server_port, c.ssl and '*' or '') + 591 self.log.debug('Flushing stale connection: %s:%i%s' % data) + 592 + 593 self.connections.remove(c) + 594 list_changed = True + 595 + 596 try: + 597 c.close() + 598 finally: + 599 del c +
    600 +
    601 - def stop(self): +
    602 self.active = False + 603 + 604 if __debug__: + 605 self.log.debug('Flushing waiting connections') + 606 + 607 for c in self.connections: + 608 try: + 609 c.close() + 610 finally: + 611 del c + 612 + 613 if __debug__: + 614 self.log.debug('Flushing queued connections') + 615 + 616 while not self.monitor_queue.empty(): + 617 c = self.monitor_queue.get() + 618 + 619 if c is None: + 620 continue + 621 + 622 try: + 623 c.close() + 624 finally: + 625 del c + 626 + 627 # Place a None sentry value to cause the monitor to die. + 628 self.monitor_queue.put(None) +
    629 + 630 # Monolithic build...end of module: rocket\monitor.py + 631 # Monolithic build...start of module: rocket\threadpool.py + 632 + 633 # Import System Modules + 634 import logging + 635 # Import Package Modules + 636 # package imports removed in monolithic build + 637 + 638 # Setup Logging + 639 log = logging.getLogger('Rocket.Errors.ThreadPool') + 640 log.addHandler(NullHandler()) + 641 +
    642 -class ThreadPool: +
    643 """The ThreadPool class is a container class for all the worker threads. It + 644 manages the number of actively running threads.""" + 645 +
    646 - def __init__(self, + 647 method, + 648 app_info, + 649 active_queue, + 650 monitor_queue, + 651 min_threads=DEFAULTS['MIN_THREADS'], + 652 max_threads=DEFAULTS['MAX_THREADS'], + 653 ): +
    654 + 655 if __debug__: + 656 log.debug("Initializing ThreadPool.") + 657 + 658 self.check_for_dead_threads = 0 + 659 self.active_queue = active_queue + 660 + 661 self.worker_class = method + 662 self.min_threads = min_threads + 663 self.max_threads = max_threads + 664 self.monitor_queue = monitor_queue + 665 self.stop_server = False + 666 + 667 # TODO - Optimize this based on some real-world usage data + 668 self.grow_threshold = int(max_threads/10) + 2 + 669 + 670 if not isinstance(app_info, dict): + 671 app_info = dict() + 672 + 673 app_info.update(max_threads=max_threads, + 674 min_threads=min_threads) + 675 + 676 self.app_info = app_info + 677 + 678 self.threads = set() + 679 for x in range(min_threads): + 680 worker = self.worker_class(app_info, + 681 self.active_queue, + 682 self.monitor_queue) + 683 self.threads.add(worker) +
    684 +
    685 - def start(self): +
    686 self.stop_server = False + 687 if __debug__: + 688 log.debug("Starting threads.") + 689 + 690 for thread in self.threads: + 691 thread.setDaemon(True) + 692 thread.start() +
    693 +
    694 - def stop(self): +
    695 if __debug__: + 696 log.debug("Stopping threads.") + 697 + 698 self.stop_server = True + 699 + 700 # Prompt the threads to die + 701 for t in self.threads: + 702 self.active_queue.put(None) + 703 + 704 # Give them the gun + 705 for t in self.threads: + 706 t.kill() + 707 + 708 # Wait until they pull the trigger + 709 for t in self.threads: + 710 t.join() + 711 + 712 # Clean up the mess + 713 self.bring_out_your_dead() +
    714 +
    715 - def bring_out_your_dead(self): +
    716 # Remove dead threads from the pool + 717 + 718 dead_threads = [t for t in self.threads if not t.isAlive()] + 719 for t in dead_threads: + 720 if __debug__: + 721 log.debug("Removing dead thread: %s." % t.getName()) + 722 try: + 723 # Py2.4 complains here so we put it in a try block + 724 self.threads.remove(t) + 725 except: + 726 pass + 727 self.check_for_dead_threads -= len(dead_threads) +
    728 +
    729 - def grow(self, amount=None): +
    730 if self.stop_server: + 731 return + 732 + 733 if not amount: + 734 amount = self.max_threads + 735 + 736 amount = min([amount, self.max_threads - len(self.threads)]) + 737 + 738 if __debug__: + 739 log.debug("Growing by %i." % amount) + 740 + 741 for x in range(amount): + 742 worker = self.worker_class(self.app_info, + 743 self.active_queue, + 744 self.monitor_queue) + 745 + 746 worker.setDaemon(True) + 747 self.threads.add(worker) + 748 worker.start() +
    749 +
    750 - def shrink(self, amount=1): +
    751 if __debug__: + 752 log.debug("Shrinking by %i." % amount) + 753 + 754 self.check_for_dead_threads += amount + 755 + 756 for x in range(amount): + 757 self.active_queue.put(None) +
    758 +
    759 - def dynamic_resize(self): +
    760 if (self.max_threads > self.min_threads or self.max_threads == 0): + 761 if self.check_for_dead_threads > 0: + 762 self.bring_out_your_dead() + 763 + 764 queueSize = self.active_queue.qsize() + 765 threadCount = len(self.threads) + 766 + 767 if __debug__: + 768 log.debug("Examining ThreadPool. %i threads and %i Q'd conxions" + 769 % (threadCount, queueSize)) + 770 + 771 if queueSize == 0 and threadCount > self.min_threads: + 772 self.shrink() + 773 + 774 elif queueSize > self.grow_threshold: + 775 + 776 self.grow(queueSize) +
    777 + 778 # Monolithic build...end of module: rocket\threadpool.py + 779 # Monolithic build...start of module: rocket\worker.py + 780 + 781 # Import System Modules + 782 import re + 783 import sys + 784 import socket + 785 import logging + 786 import traceback + 787 #from wsgiref.headers import Headers + 788 from threading import Thread + 789 from datetime import datetime + 790 + 791 try: + 792 from urllib import unquote + 793 except ImportError: + 794 from urllib.parse import unquote + 795 + 796 try: + 797 from io import StringIO + 798 except ImportError: + 799 try: + 800 from cStringIO import StringIO + 801 except ImportError: + 802 from StringIO import StringIO + 803 + 804 try: + 805 from ssl import SSLError + 806 except ImportError: +
    807 - class SSLError(socket.error): +
    808 pass +
    809 # Import Package Modules + 810 # package imports removed in monolithic build + 811 + 812 + 813 # Define Constants + 814 re_SLASH = re.compile('%2F', re.IGNORECASE) + 815 re_REQUEST_LINE = re.compile(r"""^ + 816 (?P<method>OPTIONS|GET|HEAD|POST|PUT|DELETE|TRACE|CONNECT) # Request Method + 817 \ # (single space) + 818 ( + 819 (?P<scheme>[^:/]+) # Scheme + 820 (://) # + 821 (?P<host>[^/]+) # Host + 822 )? # + 823 (?P<path>(\*|/[^ \?]*)) # Path + 824 (\? (?P<query_string>[^ ]+))? # Query String + 825 \ # (single space) + 826 (?P<protocol>HTTPS?/1\.[01]) # Protocol + 827 $ + 828 """, re.X) + 829 LOG_LINE = '%(client_ip)s - "%(request_line)s" - %(status)s %(size)s' + 830 RESPONSE = '''\ + 831 HTTP/1.1 %s + 832 Content-Length: %i + 833 Content-Type: %s + 834 + 835 %s + 836 ''' + 837 if IS_JYTHON: + 838 HTTP_METHODS = set(['OPTIONS', 'GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'TRACE', 'CONNECT']) + 839 + 840 ### + 841 # The Headers and FileWrapper classes are ripped straight from the Python + 842 # Standard Library. I've removed some docstrings and integrated my BUF_SIZE. + 843 # See the Python License here: http://docs.python.org/license.html + 844 ### + 845 + 846 # Regular expression that matches `special' characters in parameters, the + 847 # existance of which force quoting of the parameter value. + 848 import re + 849 _tspecials = re.compile(r'[ \(\)<>@,;:\\"/\[\]\?=]') + 850 +
    851 -def _formatparam(param, value=None, quote=1): +
    852 """Convenience function to format and return a key=value pair. + 853 + 854 This will quote the value if needed or if quote is true. + 855 """ + 856 if value is not None and len(value) > 0: + 857 if quote or _tspecials.search(value): + 858 value = value.replace('\\', '\\\\').replace('"', r'\"') + 859 return '%s="%s"' % (param, value) + 860 else: + 861 return '%s=%s' % (param, value) + 862 else: + 863 return param +
    864 +
    865 -class Headers: +
    866 - def __init__(self,headers): +
    867 if type(headers) is not type([]): + 868 raise TypeError("Headers must be a list of name/value tuples") + 869 self._headers = headers +
    870 +
    871 - def __len__(self): +
    872 return len(self._headers) +
    873 +
    874 - def __setitem__(self, name, val): +
    875 del self[name] + 876 self._headers.append((name, val)) +
    877 +
    878 - def __delitem__(self,name): +
    879 name = name.lower() + 880 self._headers[:] = [kv for kv in self._headers if kv[0].lower() != name] +
    881 +
    882 - def __getitem__(self,name): +
    883 return self.get(name) +
    884 +
    885 - def has_key(self, name): +
    886 return self.get(name) is not None +
    887 + 888 __contains__ = has_key + 889 +
    890 - def get_all(self, name): +
    891 name = name.lower() + 892 return [kv[1] for kv in self._headers if kv[0].lower()==name] +
    893 +
    894 - def get(self,name,default=None): +
    895 name = name.lower() + 896 for k,v in self._headers: + 897 if k.lower()==name: + 898 return v + 899 return default +
    900 +
    901 - def keys(self): +
    902 return [k for k, v in self._headers] +
    903 +
    904 - def values(self): +
    905 return [v for k, v in self._headers] +
    906 +
    907 - def items(self): +
    908 return self._headers[:] +
    909 +
    910 - def __repr__(self): +
    911 return "Headers(%r)" % self._headers +
    912 +
    913 - def __str__(self): +
    914 return '\r\n'.join(["%s: %s" % kv for kv in self._headers]+['','']) +
    915 +
    916 - def setdefault(self,name,value): +
    917 result = self.get(name) + 918 if result is None: + 919 self._headers.append((name,value)) + 920 return value + 921 else: + 922 return result +
    923 + 924 +
    925 - def add_header(self, _name, _value, **_params): +
    926 parts = [] + 927 if _value is not None: + 928 parts.append(_value) + 929 for k, v in _params.items(): + 930 if v is None: + 931 parts.append(k.replace('_', '-')) + 932 else: + 933 parts.append(_formatparam(k.replace('_', '-'), v)) + 934 self._headers.append((_name, "; ".join(parts))) +
    935 +
    936 -class FileWrapper: +
    937 """Wrapper to convert file-like objects to iterables""" + 938 +
    939 - def __init__(self, filelike, blksize=BUF_SIZE): +
    940 self.filelike = filelike + 941 self.blksize = blksize + 942 if hasattr(filelike,'close'): + 943 self.close = filelike.close +
    944 +
    945 - def __getitem__(self,key): +
    946 data = self.filelike.read(self.blksize) + 947 if data: + 948 return data + 949 raise IndexError +
    950 +
    951 - def __iter__(self): +
    952 return self +
    953 +
    954 - def next(self): +
    955 data = self.filelike.read(self.blksize) + 956 if data: + 957 return data + 958 raise StopIteration +
    959 +
    960 -class Worker(Thread): +
    961 """The Worker class is a base class responsible for receiving connections + 962 and (a subclass) will run an application to process the the connection """ + 963 +
    964 - def __init__(self, + 965 app_info, + 966 active_queue, + 967 monitor_queue, + 968 *args, + 969 **kwargs): +
    970 + 971 Thread.__init__(self, *args, **kwargs) + 972 + 973 # Instance Variables + 974 self.app_info = app_info + 975 self.active_queue = active_queue + 976 self.monitor_queue = monitor_queue + 977 + 978 self.size = 0 + 979 self.status = "200 OK" + 980 self.closeConnection = True + 981 + 982 # Request Log + 983 self.req_log = logging.getLogger('Rocket.Requests') + 984 self.req_log.addHandler(NullHandler()) + 985 + 986 # Error Log + 987 self.err_log = logging.getLogger('Rocket.Errors.'+self.getName()) + 988 self.err_log.addHandler(NullHandler()) +
    989 +
    990 - def _handleError(self, typ, val, tb): +
    991 if typ == SSLError: + 992 if 'timed out' in val.args[0]: + 993 typ = SocketTimeout + 994 if typ == SocketTimeout: + 995 if __debug__: + 996 self.err_log.debug('Socket timed out') + 997 self.monitor_queue.put(self.conn) + 998 return True + 999 if typ == SocketClosed: +1000 self.closeConnection = True +1001 if __debug__: +1002 self.err_log.debug('Client closed socket') +1003 return False +1004 if typ == BadRequest: +1005 self.closeConnection = True +1006 if __debug__: +1007 self.err_log.debug('Client sent a bad request') +1008 return True +1009 if typ == socket.error: +1010 self.closeConnection = True +1011 if val.args[0] in IGNORE_ERRORS_ON_CLOSE: +1012 if __debug__: +1013 self.err_log.debug('Ignorable socket Error received...' +1014 'closing connection.') +1015 return False +1016 else: +1017 self.status = "999 Utter Server Failure" +1018 tb_fmt = traceback.format_exception(typ, val, tb) +1019 self.err_log.error('Unhandled Error when serving ' +1020 'connection:\n' + '\n'.join(tb_fmt)) +1021 return False +1022 +1023 self.closeConnection = True +1024 tb_fmt = traceback.format_exception(typ, val, tb) +1025 self.err_log.error('\n'.join(tb_fmt)) +1026 self.send_response('500 Server Error') +1027 return False +
    1028 +
    1029 - def run(self): +
    1030 if __debug__: +1031 self.err_log.debug('Entering main loop.') +1032 +1033 # Enter thread main loop +1034 while True: +1035 conn = self.active_queue.get() +1036 +1037 if not conn: +1038 # A non-client is a signal to die +1039 if __debug__: +1040 self.err_log.debug('Received a death threat.') +1041 return conn +1042 +1043 if isinstance(conn, tuple): +1044 conn = Connection(*conn) +1045 +1046 self.conn = conn +1047 +1048 if conn.ssl != conn.secure: +1049 self.err_log.info('Received HTTP connection on HTTPS port.') +1050 self.send_response('400 Bad Request') +1051 self.closeConnection = True +1052 conn.close() +1053 continue +1054 else: +1055 if __debug__: +1056 self.err_log.debug('Received a connection.') +1057 self.closeConnection = False +1058 +1059 # Enter connection serve loop +1060 while True: +1061 if __debug__: +1062 self.err_log.debug('Serving a request') +1063 try: +1064 self.run_app(conn) +1065 log_info = dict(client_ip = conn.client_addr, +1066 time = datetime.now().strftime('%c'), +1067 status = self.status.split(' ')[0], +1068 size = self.size, +1069 request_line = self.request_line) +1070 self.req_log.info(LOG_LINE % log_info) +1071 except: +1072 exc = sys.exc_info() +1073 handled = self._handleError(*exc) +1074 if handled: +1075 break +1076 else: +1077 if self.request_line: +1078 log_info = dict(client_ip = conn.client_addr, +1079 time = datetime.now().strftime('%c'), +1080 status = self.status.split(' ')[0], +1081 size = self.size, +1082 request_line = self.request_line + ' - not stopping') +1083 self.req_log.info(LOG_LINE % log_info) +1084 +1085 if self.closeConnection: +1086 try: +1087 conn.close() +1088 except: +1089 self.err_log.error(str(traceback.format_exc())) +1090 +1091 break +
    1092 +
    1093 - def run_app(self, conn): +
    1094 # Must be overridden with a method reads the request from the socket +1095 # and sends a response. +1096 self.closeConnection = True +1097 raise NotImplementedError('Overload this method!') +
    1098 +
    1099 - def send_response(self, status): +
    1100 stat_msg = status.split(' ', 1)[1] +1101 msg = RESPONSE % (status, +1102 len(stat_msg), +1103 'text/plain', +1104 stat_msg) +1105 try: +1106 self.conn.sendall(b(msg)) +1107 except socket.error: +1108 self.closeConnection = True +1109 self.err_log.error('Tried to send "%s" to client but received socket' +1110 ' error' % status) +
    1111 +
    1112 - def kill(self): +
    1113 if self.isAlive() and hasattr(self, 'conn'): +1114 try: +1115 self.conn.shutdown(socket.SHUT_RDWR) +1116 except socket.error: +1117 info = sys.exc_info() +1118 if info[1].args[0] != socket.EBADF: +1119 self.err_log.debug('Error on shutdown: '+str(info)) +
    1120 +
    1121 - def read_request_line(self, sock_file): +
    1122 self.request_line = '' +1123 try: +1124 # Grab the request line +1125 d = sock_file.readline() +1126 if PY3K: +1127 d = d.decode('ISO-8859-1') +1128 +1129 if d == '\r\n': +1130 # Allow an extra NEWLINE at the beginning per HTTP 1.1 spec +1131 if __debug__: +1132 self.err_log.debug('Client sent newline') +1133 +1134 d = sock_file.readline() +1135 if PY3K: +1136 d = d.decode('ISO-8859-1') +1137 except socket.timeout: +1138 raise SocketTimeout("Socket timed out before request.") +1139 +1140 d = d.strip() +1141 +1142 if not d: +1143 if __debug__: +1144 self.err_log.debug('Client did not send a recognizable request.') +1145 raise SocketClosed('Client closed socket.') +1146 +1147 self.request_line = d +1148 +1149 # NOTE: I've replaced the traditional method of procedurally breaking +1150 # apart the request line with a (rather unsightly) regular expression. +1151 # However, Java's regexp support sucks so bad that it actually takes +1152 # longer in Jython to process the regexp than procedurally. So I've +1153 # left the old code here for Jython's sake...for now. +1154 if IS_JYTHON: +1155 return self._read_request_line_jython(d) +1156 +1157 match = re_REQUEST_LINE.match(d) +1158 +1159 if not match: +1160 self.send_response('400 Bad Request') +1161 raise BadRequest +1162 +1163 req = match.groupdict() +1164 for k,v in req.items(): +1165 if not v: +1166 req[k] = "" +1167 if k == 'path': +1168 req['path'] = r'%2F'.join([unquote(x) for x in re_SLASH.split(v)]) +1169 +1170 return req +
    1171 +
    1172 - def _read_request_line_jython(self, d): +
    1173 d = d.strip() +1174 try: +1175 method, uri, proto = d.split(' ') +1176 if not proto.startswith('HTTP') or \ +1177 proto[-3:] not in ('1.0', '1.1') or \ +1178 method not in HTTP_METHODS: +1179 self.send_response('400 Bad Request') +1180 raise BadRequest +1181 except ValueError: +1182 self.send_response('400 Bad Request') +1183 raise BadRequest +1184 +1185 req = dict(method=method, protocol = proto) +1186 scheme = '' +1187 host = '' +1188 if uri == '*' or uri.startswith('/'): +1189 path = uri +1190 elif '://' in uri: +1191 scheme, rest = uri.split('://') +1192 host, path = rest.split('/', 1) +1193 path = '/' + path +1194 else: +1195 self.send_response('400 Bad Request') +1196 raise BadRequest +1197 +1198 query_string = '' +1199 if '?' in path: +1200 path, query_string = path.split('?', 1) +1201 +1202 path = r'%2F'.join([unquote(x) for x in re_SLASH.split(path)]) +1203 +1204 req.update(path=path, +1205 query_string=query_string, +1206 scheme=scheme.lower(), +1207 host=host) +1208 return req +
    1209 +1210 +
    1211 - def read_headers(self, sock_file): +
    1212 headers = dict() +1213 l = sock_file.readline() +1214 +1215 lname = None +1216 lval = None +1217 while True: +1218 if PY3K: +1219 try: +1220 l = str(l, 'ISO-8859-1') +1221 except UnicodeDecodeError: +1222 self.err_log.warning('Client sent invalid header: ' + repr(l)) +1223 +1224 if l == '\r\n': +1225 break +1226 +1227 if l[0] in ' \t' and lname: +1228 # Some headers take more than one line +1229 lval += ',' + l.strip() +1230 else: +1231 # HTTP header values are latin-1 encoded +1232 l = l.split(':', 1) +1233 # HTTP header names are us-ascii encoded +1234 +1235 lname = l[0].strip().upper().replace('-', '_') +1236 lval = l[-1].strip() +1237 headers[str(lname)] = str(lval) +1238 +1239 l = sock_file.readline() +1240 return headers +
    1241 +
    1242 -class SocketTimeout(Exception): +
    1243 "Exception for when a socket times out between requests." +1244 pass +
    1245 +
    1246 -class BadRequest(Exception): +
    1247 "Exception for when a client sends an incomprehensible request." +1248 pass +
    1249 +
    1250 -class SocketClosed(Exception): +
    1251 "Exception for when a socket is closed by the client." +1252 pass +
    1253 +
    1254 -class ChunkedReader(object): +
    1255 - def __init__(self, sock_file): +
    1256 self.stream = sock_file +1257 self.chunk_size = 0 +
    1258 +
    1259 - def _read_header(self): +
    1260 chunk_len = "" +1261 try: +1262 while "" == chunk_len: +1263 chunk_len = self.stream.readline().strip() +1264 return int(chunk_len, 16) +1265 except ValueError: +1266 return 0 +
    1267 +
    1268 - def read(self, size): +
    1269 data = b('') +1270 chunk_size = self.chunk_size +1271 while size: +1272 if not chunk_size: +1273 chunk_size = self._read_header() +1274 +1275 if size < chunk_size: +1276 data += self.stream.read(size) +1277 chunk_size -= size +1278 break +1279 else: +1280 if not chunk_size: +1281 break +1282 data += self.stream.read(chunk_size) +1283 size -= chunk_size +1284 chunk_size = 0 +1285 +1286 self.chunk_size = chunk_size +1287 return data +
    1288 +
    1289 - def readline(self): +
    1290 data = b('') +1291 c = self.read(1) +1292 while c and c != b('\n'): +1293 data += c +1294 c = self.read(1) +1295 data += c +1296 return data +
    1297 +
    1298 - def readlines(self): +
    1299 yield self.readline() +
    1300 +
    1301 -def get_method(method): +
    1302 +1303 methods = dict(wsgi=WSGIWorker) +1304 return methods[method.lower()] +
    1305 +1306 # Monolithic build...end of module: rocket\worker.py +1307 # Monolithic build...start of module: rocket\methods\__init__.py +1308 +1309 # Monolithic build...end of module: rocket\methods\__init__.py +1310 # Monolithic build...start of module: rocket\methods\wsgi.py +1311 +1312 # Import System Modules +1313 import sys +1314 import socket +1315 #from wsgiref.headers import Headers +1316 #from wsgiref.util import FileWrapper +1317 # Import Package Modules +1318 # package imports removed in monolithic build +1319 +1320 +1321 if PY3K: +1322 from email.utils import formatdate +1323 else: +1324 # Caps Utils for Py2.4 compatibility +1325 from email.Utils import formatdate +1326 +1327 # Define Constants +1328 NEWLINE = b('\r\n') +1329 HEADER_RESPONSE = '''HTTP/1.1 %s\r\n%s''' +1330 BASE_ENV = {'SERVER_NAME': SERVER_NAME, +1331 'SCRIPT_NAME': '', # Direct call WSGI does not need a name +1332 'wsgi.errors': sys.stderr, +1333 'wsgi.version': (1, 0), +1334 'wsgi.multiprocess': False, +1335 'wsgi.run_once': False, +1336 'wsgi.file_wrapper': FileWrapper +1337 } +1338 +
    1339 -class WSGIWorker(Worker): +
    1340 - def __init__(self, *args, **kwargs): +
    1341 """Builds some instance variables that will last the life of the +1342 thread.""" +1343 Worker.__init__(self, *args, **kwargs) +1344 +1345 if isinstance(self.app_info, dict): +1346 multithreaded = self.app_info.get('max_threads') != 1 +1347 else: +1348 multithreaded = False +1349 self.base_environ = dict({'SERVER_SOFTWARE': self.app_info['server_software'], +1350 'wsgi.multithread': multithreaded, +1351 }) +1352 self.base_environ.update(BASE_ENV) +1353 # Grab our application +1354 self.app = self.app_info.get('wsgi_app') +1355 +1356 if not hasattr(self.app, "__call__"): +1357 raise TypeError("The wsgi_app specified (%s) is not a valid WSGI application." % repr(self.app)) +
    1358 +1359 +
    1360 - def build_environ(self, sock_file, conn): +
    1361 """ Build the execution environment. """ +1362 # Grab the request line +1363 request = self.read_request_line(sock_file) +1364 +1365 # Copy the Base Environment +1366 environ = self.base_environ.copy() +1367 +1368 # Grab the headers +1369 for k, v in self.read_headers(sock_file).items(): +1370 environ[str('HTTP_'+k)] = v +1371 +1372 # Add CGI Variables +1373 environ['REQUEST_METHOD'] = request['method'] +1374 environ['PATH_INFO'] = request['path'] +1375 environ['SERVER_PROTOCOL'] = request['protocol'] +1376 environ['SERVER_PORT'] = str(conn.server_port) +1377 environ['REMOTE_PORT'] = str(conn.client_port) +1378 environ['REMOTE_ADDR'] = str(conn.client_addr) +1379 environ['QUERY_STRING'] = request['query_string'] +1380 if 'HTTP_CONTENT_LENGTH' in environ: +1381 environ['CONTENT_LENGTH'] = environ['HTTP_CONTENT_LENGTH'] +1382 if 'HTTP_CONTENT_TYPE' in environ: +1383 environ['CONTENT_TYPE'] = environ['HTTP_CONTENT_TYPE'] +1384 +1385 # Save the request method for later +1386 self.request_method = environ['REQUEST_METHOD'] +1387 +1388 # Add Dynamic WSGI Variables +1389 if conn.ssl: +1390 environ['wsgi.url_scheme'] = 'https' +1391 environ['HTTPS'] = 'on' +1392 else: +1393 environ['wsgi.url_scheme'] = 'http' +1394 +1395 if environ.get('HTTP_TRANSFER_ENCODING', '') == 'chunked': +1396 environ['wsgi.input'] = ChunkedReader(sock_file) +1397 else: +1398 environ['wsgi.input'] = sock_file +1399 +1400 return environ +
    1401 +
    1402 - def send_headers(self, data, sections): +
    1403 h_set = self.header_set +1404 +1405 # Does the app want us to send output chunked? +1406 self.chunked = h_set.get('transfer-encoding', '').lower() == 'chunked' +1407 +1408 # Add a Date header if it's not there already +1409 if not 'date' in h_set: +1410 h_set['Date'] = formatdate(usegmt=True) +1411 +1412 # Add a Server header if it's not there already +1413 if not 'server' in h_set: +1414 h_set['Server'] = HTTP_SERVER_SOFTWARE +1415 +1416 if 'content-length' in h_set: +1417 self.size = int(h_set['content-length']) +1418 else: +1419 s = int(self.status.split(' ')[0]) +1420 if s < 200 or s not in (204, 205, 304): +1421 if not self.chunked: +1422 if sections == 1: +1423 # Add a Content-Length header if it's not there already +1424 h_set['Content-Length'] = str(len(data)) +1425 self.size = len(data) +1426 else: +1427 # If they sent us more than one section, we blow chunks +1428 h_set['Transfer-Encoding'] = 'Chunked' +1429 self.chunked = True +1430 if __debug__: +1431 self.err_log.debug('Adding header...' +1432 'Transfer-Encoding: Chunked') +1433 +1434 if 'connection' not in h_set: +1435 # If the application did not provide a connection header, fill it in +1436 client_conn = self.environ.get('HTTP_CONNECTION', '').lower() +1437 if self.environ['SERVER_PROTOCOL'] == 'HTTP/1.1': +1438 # HTTP = 1.1 defaults to keep-alive connections +1439 if client_conn: +1440 h_set['Connection'] = client_conn +1441 else: +1442 h_set['Connection'] = 'keep-alive' +1443 else: +1444 # HTTP < 1.1 supports keep-alive but it's quirky so we don't support it +1445 h_set['Connection'] = 'close' +1446 +1447 # Close our connection if we need to. +1448 self.closeConnection = h_set.get('connection', '').lower() == 'close' +1449 +1450 # Build our output headers +1451 header_data = HEADER_RESPONSE % (self.status, str(h_set)) +1452 +1453 # Send the headers +1454 if __debug__: +1455 self.err_log.debug('Sending Headers: %s' % repr(header_data)) +1456 self.conn.sendall(b(header_data)) +1457 self.headers_sent = True +
    1458 +
    1459 - def write_warning(self, data, sections=None): +
    1460 self.err_log.warning('WSGI app called write method directly. This is ' +1461 'deprecated behavior. Please update your app.') +1462 return self.write(data, sections) +
    1463 +
    1464 - def write(self, data, sections=None): +
    1465 """ Write the data to the output socket. """ +1466 +1467 if self.error[0]: +1468 self.status = self.error[0] +1469 data = b(self.error[1]) +1470 +1471 if not self.headers_sent: +1472 self.send_headers(data, sections) +1473 +1474 if self.request_method != 'HEAD': +1475 try: +1476 if self.chunked: +1477 self.conn.sendall(b('%x\r\n%s\r\n' % (len(data), data))) +1478 else: +1479 self.conn.sendall(data) +1480 except socket.error: +1481 # But some clients will close the connection before that +1482 # resulting in a socket error. +1483 self.closeConnection = True +
    1484 +
    1485 - def start_response(self, status, response_headers, exc_info=None): +
    1486 """ Store the HTTP status and headers to be sent when self.write is +1487 called. """ +1488 if exc_info: +1489 try: +1490 if self.headers_sent: +1491 # Re-raise original exception if headers sent +1492 # because this violates WSGI specification. +1493 raise +1494 finally: +1495 exc_info = None +1496 elif self.header_set: +1497 raise AssertionError("Headers already set!") +1498 +1499 if PY3K and not isinstance(status, str): +1500 self.status = str(status, 'ISO-8859-1') +1501 else: +1502 self.status = status +1503 # Make sure headers are bytes objects +1504 try: +1505 self.header_set = Headers(response_headers) +1506 except UnicodeDecodeError: +1507 self.error = ('500 Internal Server Error', +1508 'HTTP Headers should be bytes') +1509 self.err_log.error('Received HTTP Headers from client that contain' +1510 ' invalid characters for Latin-1 encoding.') +1511 +1512 return self.write_warning +
    1513 +
    1514 - def run_app(self, conn): +
    1515 self.size = 0 +1516 self.header_set = Headers([]) +1517 self.headers_sent = False +1518 self.error = (None, None) +1519 self.chunked = False +1520 sections = None +1521 output = None +1522 +1523 if __debug__: +1524 self.err_log.debug('Getting sock_file') +1525 +1526 # Build our file-like object +1527 sock_file = conn.makefile('rb',BUF_SIZE) +1528 +1529 try: +1530 # Read the headers and build our WSGI environment +1531 self.environ = environ = self.build_environ(sock_file, conn) +1532 +1533 # Handle 100 Continue +1534 if environ.get('HTTP_EXPECT', '') == '100-continue': +1535 res = environ['SERVER_PROTOCOL'] + ' 100 Continue\r\n\r\n' +1536 conn.sendall(b(res)) +1537 +1538 # Send it to our WSGI application +1539 output = self.app(environ, self.start_response) +1540 +1541 if not hasattr(output, '__len__') and not hasattr(output, '__iter__'): +1542 self.error = ('500 Internal Server Error', +1543 'WSGI applications must return a list or ' +1544 'generator type.') +1545 +1546 if hasattr(output, '__len__'): +1547 sections = len(output) +1548 +1549 for data in output: +1550 # Don't send headers until body appears +1551 if data: +1552 self.write(data, sections) +1553 +1554 if self.chunked: +1555 # If chunked, send our final chunk length +1556 self.conn.sendall(b('0\r\n\r\n')) +1557 elif not self.headers_sent: +1558 # Send headers if the body was empty +1559 self.send_headers('', sections) +1560 +1561 # Don't capture exceptions here. The Worker class handles +1562 # them appropriately. +1563 finally: +1564 if __debug__: +1565 self.err_log.debug('Finally closing output and sock_file') +1566 +1567 if hasattr(output,'close'): +1568 output.close() +1569 +1570 sock_file.close() +
    1571 +1572 # Monolithic build...end of module: rocket\methods\wsgi.py +1573 +1574 # +1575 # the following code is not part of Rocket but was added in web2py for testing purposes +1576 # +1577 +
    1578 -def demo_app(environ, start_response): +
    1579 global static_folder +1580 import os +1581 types = {'htm': 'text/html','html': 'text/html','gif': 'image/gif', +1582 'jpg': 'image/jpeg','png': 'image/png','pdf': 'applications/pdf'} +1583 if static_folder: +1584 if not static_folder.startswith('/'): +1585 static_folder = os.path.join(os.getcwd(),static_folder) +1586 path = os.path.join(static_folder, environ['PATH_INFO'][1:] or 'index.html') +1587 type = types.get(path.split('.')[-1],'text') +1588 if os.path.exists(path): +1589 try: +1590 pathfile = open(path,'rb') +1591 try: +1592 data = pathfile.read() +1593 finally: +1594 pathfile.close() +1595 start_response('200 OK', [('Content-Type', type)]) +1596 except IOError: +1597 start_response('404 NOT FOUND', []) +1598 data = '404 NOT FOUND' +1599 else: +1600 start_response('500 INTERNAL SERVER ERROR', []) +1601 data = '500 INTERNAL SERVER ERROR' +1602 else: +1603 start_response('200 OK', [('Content-Type', 'text/html')]) +1604 data = '<html><body><h1>Hello from Rocket Web Server</h1></body></html>' +1605 return [data] +
    1606 +
    1607 -def demo(): +
    1608 from optparse import OptionParser +1609 parser = OptionParser() +1610 parser.add_option("-i", "--ip", dest="ip",default="127.0.0.1", +1611 help="ip address of the network interface") +1612 parser.add_option("-p", "--port", dest="port",default="8000", +1613 help="post where to run web server") +1614 parser.add_option("-s", "--static", dest="static",default=None, +1615 help="folder containing static files") +1616 (options, args) = parser.parse_args() +1617 global static_folder +1618 static_folder = options.static +1619 print 'Rocket running on %s:%s' % (options.ip, options.port) +1620 r=Rocket((options.ip,int(options.port)),'wsgi', {'wsgi_app':demo_app}) +1621 r.start() +
    1622 +1623 if __name__=='__main__': +1624 demo() +1625 +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.rocket.BadRequest-class.html Index: applications/examples/static/epydoc/web2py.gluon.rocket.BadRequest-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.rocket.BadRequest-class.html +++ applications/examples/static/epydoc/web2py.gluon.rocket.BadRequest-class.html @@ -0,0 +1,199 @@ + + + + + web2py.gluon.rocket.BadRequest + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module rocket :: + Class BadRequest + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class BadRequest

    source code

    +
    +              object --+        
    +                       |        
    +exceptions.BaseException --+    
    +                           |    
    +        exceptions.Exception --+
    +                               |
    +                              BadRequest
    +
    + +
    +Exception for when a client sends an incomprehensible request.

    + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from exceptions.Exception: + __init__, + __new__ +

    +

    Inherited from exceptions.BaseException: + __delattr__, + __getattribute__, + __getitem__, + __getslice__, + __reduce__, + __repr__, + __setattr__, + __setstate__, + __str__ +

    +

    Inherited from object: + __hash__, + __reduce_ex__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from exceptions.BaseException: + args, + message +

    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.rocket.ChunkedReader-class.html Index: applications/examples/static/epydoc/web2py.gluon.rocket.ChunkedReader-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.rocket.ChunkedReader-class.html +++ applications/examples/static/epydoc/web2py.gluon.rocket.ChunkedReader-class.html @@ -0,0 +1,310 @@ + + + + + web2py.gluon.rocket.ChunkedReader + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module rocket :: + Class ChunkedReader + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class ChunkedReader

    source code

    +
    +object --+
    +         |
    +        ChunkedReader
    +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + sock_file)
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    _read_header(self) + source code + +
    + +
    +   + + + + + + +
    read(self, + size) + source code + +
    + +
    +   + + + + + + +
    readline(self) + source code + +
    + +
    +   + + + + + + +
    readlines(self) + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + sock_file) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.rocket.Connection-class.html Index: applications/examples/static/epydoc/web2py.gluon.rocket.Connection-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.rocket.Connection-class.html +++ applications/examples/static/epydoc/web2py.gluon.rocket.Connection-class.html @@ -0,0 +1,349 @@ + + + + + web2py.gluon.rocket.Connection + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module rocket :: + Class Connection + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class Connection

    source code

    +
    +object --+
    +         |
    +        Connection
    +
    + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + sock_tuple, + port, + secure=True)
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    close(self) + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +   + + client_addr +
    +   + + client_port +
    +   + + fileno +
    +   + + makefile +
    +   + + secure +
    +   + + sendall +
    +   + + server_port +
    +   + + setblocking +
    +   + + shutdown +
    +   + + socket +
    +   + + ssl +
    +   + + start_time +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + sock_tuple, + port, + secure=True) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.rocket.FileWrapper-class.html Index: applications/examples/static/epydoc/web2py.gluon.rocket.FileWrapper-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.rocket.FileWrapper-class.html +++ applications/examples/static/epydoc/web2py.gluon.rocket.FileWrapper-class.html @@ -0,0 +1,203 @@ + + + + + web2py.gluon.rocket.FileWrapper + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module rocket :: + Class FileWrapper + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class FileWrapper

    source code

    +Wrapper to convert file-like objects to iterables

    + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + filelike, + blksize=16384) + source code + +
    + +
    +   + + + + + + +
    __getitem__(self, + key) + source code + +
    + +
    +   + + + + + + +
    __iter__(self) + source code + +
    + +
    +   + + + + + + +
    next(self) + source code + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.rocket.Headers-class.html Index: applications/examples/static/epydoc/web2py.gluon.rocket.Headers-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.rocket.Headers-class.html +++ applications/examples/static/epydoc/web2py.gluon.rocket.Headers-class.html @@ -0,0 +1,405 @@ + + + + + web2py.gluon.rocket.Headers + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module rocket :: + Class Headers + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class Headers

    source code

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + headers) + source code + +
    + +
    +   + + + + + + +
    __len__(self) + source code + +
    + +
    +   + + + + + + +
    __setitem__(self, + name, + val) + source code + +
    + +
    +   + + + + + + +
    __delitem__(self, + name) + source code + +
    + +
    +   + + + + + + +
    __getitem__(self, + name) + source code + +
    + +
    +   + + + + + + +
    has_key(self, + name) + source code + +
    + +
    +   + + + + + + +
    __contains__(self, + name) + source code + +
    + +
    +   + + + + + + +
    get_all(self, + name) + source code + +
    + +
    +   + + + + + + +
    get(self, + name, + default=1) + source code + +
    + +
    +   + + + + + + +
    keys(self) + source code + +
    + +
    +   + + + + + + +
    values(self) + source code + +
    + +
    +   + + + + + + +
    items(self) + source code + +
    + +
    +   + + + + + + +
    __repr__(self) + source code + +
    + +
    +   + + + + + + +
    __str__(self) + source code + +
    + +
    +   + + + + + + +
    setdefault(self, + name, + value) + source code + +
    + +
    +   + + + + + + +
    add_header(self, + _name, + _value, + **_params) + source code + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.rocket.Listener-class.html Index: applications/examples/static/epydoc/web2py.gluon.rocket.Listener-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.rocket.Listener-class.html +++ applications/examples/static/epydoc/web2py.gluon.rocket.Listener-class.html @@ -0,0 +1,326 @@ + + + + + web2py.gluon.rocket.Listener + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module rocket :: + Class Listener + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class Listener

    source code

    +
    +        object --+        
    +                 |        
    +threading._Verbose --+    
    +                     |    
    +      threading.Thread --+
    +                         |
    +                        Listener
    +
    + +
    +The Listener class is a class responsible for accepting connections + and queuing them to be processed by a worker thread.

    + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + interface, + queue_size, + active_queue, + *args, + **kwargs) + source code + +
    + +
    +   + + + + + + +
    wrap_socket(self, + sock) + source code + +
    + +
    +   + + + + + + +
    run(self) + source code + +
    + +
    +

    Inherited from threading.Thread: + __repr__, + getName, + isAlive, + isDaemon, + join, + setDaemon, + setName, + start +

    +

    Inherited from threading.Thread (private): + _set_daemon +

    +

    Inherited from threading._Verbose (private): + _note +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + interface, + queue_size, + active_queue, + *args, + **kwargs) +
    (Constructor) +

    +
    source code  +
    + + +
    +
    Overrides: + threading.Thread.__init__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    run(self) +

    +
    source code  +
    + + +
    +
    Overrides: + threading.Thread.run +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.rocket.Monitor-class.html Index: applications/examples/static/epydoc/web2py.gluon.rocket.Monitor-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.rocket.Monitor-class.html +++ applications/examples/static/epydoc/web2py.gluon.rocket.Monitor-class.html @@ -0,0 +1,322 @@ + + + + + web2py.gluon.rocket.Monitor + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module rocket :: + Class Monitor + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class Monitor

    source code

    +
    +        object --+        
    +                 |        
    +threading._Verbose --+    
    +                     |    
    +      threading.Thread --+
    +                         |
    +                        Monitor
    +
    + +
    + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + monitor_queue, + active_queue, + timeout, + *args, + **kwargs) + source code + +
    + +
    +   + + + + + + +
    run(self) + source code + +
    + +
    +   + + + + + + +
    stop(self) + source code + +
    + +
    +

    Inherited from threading.Thread: + __repr__, + getName, + isAlive, + isDaemon, + join, + setDaemon, + setName, + start +

    +

    Inherited from threading.Thread (private): + _set_daemon +

    +

    Inherited from threading._Verbose (private): + _note +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + monitor_queue, + active_queue, + timeout, + *args, + **kwargs) +
    (Constructor) +

    +
    source code  +
    + + +
    +
    Overrides: + threading.Thread.__init__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    run(self) +

    +
    source code  +
    + + +
    +
    Overrides: + threading.Thread.run +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.rocket.NullHandler-class.html Index: applications/examples/static/epydoc/web2py.gluon.rocket.NullHandler-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.rocket.NullHandler-class.html +++ applications/examples/static/epydoc/web2py.gluon.rocket.NullHandler-class.html @@ -0,0 +1,231 @@ + + + + + web2py.gluon.rocket.NullHandler + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module rocket :: + Class NullHandler + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class NullHandler

    source code

    +
    +logging.Filterer --+    
    +                   |    
    +     logging.Handler --+
    +                       |
    +                      NullHandler
    +
    + +
    +A Logging handler to prevent library errors.

    + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    emit(self, + record)
    + Do whatever it takes to actually log the specified logging + record.
    + source code + +
    + +
    +

    Inherited from logging.Handler: + __init__, + acquire, + close, + createLock, + flush, + format, + handle, + handleError, + release, + setFormatter, + setLevel +

    +

    Inherited from logging.Filterer: + addFilter, + filter, + removeFilter +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    emit(self, + record) +

    +
    source code  +
    + +

    Do whatever it takes to actually log the specified logging record.

    + This version is intended to be implemented by subclasses and so raises + a NotImplementedError. +
    +
    Overrides: + logging.Handler.emit +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.rocket.Rocket-class.html Index: applications/examples/static/epydoc/web2py.gluon.rocket.Rocket-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.rocket.Rocket-class.html +++ applications/examples/static/epydoc/web2py.gluon.rocket.Rocket-class.html @@ -0,0 +1,347 @@ + + + + + web2py.gluon.rocket.Rocket + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module rocket :: + Class Rocket + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class Rocket

    source code

    +
    +object --+
    +         |
    +        Rocket
    +
    + +
    +The Rocket class is responsible for handling threads and accepting and + dispatching connections.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + interfaces=('127.0.0.1', 8000), + method='wsgi', + app_info=1, + min_threads=1, + max_threads=1, + queue_size=1, + timeout=600, + handle_signals=True)
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    _sigterm(self, + signum, + frame) + source code + +
    + +
    +   + + + + + + +
    _sighup(self, + signum, + frame) + source code + +
    + +
    +   + + + + + + +
    start(self) + source code + +
    + +
    +   + + + + + + +
    stop(self, + stoplogging=True) + source code + +
    + +
    +   + + + + + + +
    restart(self) + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + interfaces=('127.0.0.1', 8000), + method='wsgi', + app_info=1, + min_threads=1, + max_threads=1, + queue_size=1, + timeout=600, + handle_signals=True) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.rocket.SSLError-class.html Index: applications/examples/static/epydoc/web2py.gluon.rocket.SSLError-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.rocket.SSLError-class.html +++ applications/examples/static/epydoc/web2py.gluon.rocket.SSLError-class.html @@ -0,0 +1,199 @@ + + + + + web2py.gluon.rocket.SSLError + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module rocket :: + Class SSLError + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class SSLError

    source code

    +
    +              object --+            
    +                       |            
    +exceptions.BaseException --+        
    +                           |        
    +        exceptions.Exception --+    
    +                               |    
    +                    socket.error --+
    +                                   |
    +                                  SSLError
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from exceptions.Exception: + __init__, + __new__ +

    +

    Inherited from exceptions.BaseException: + __delattr__, + __getattribute__, + __getitem__, + __getslice__, + __reduce__, + __repr__, + __setattr__, + __setstate__, + __str__ +

    +

    Inherited from object: + __hash__, + __reduce_ex__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from exceptions.BaseException: + args, + message +

    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.rocket.SocketClosed-class.html Index: applications/examples/static/epydoc/web2py.gluon.rocket.SocketClosed-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.rocket.SocketClosed-class.html +++ applications/examples/static/epydoc/web2py.gluon.rocket.SocketClosed-class.html @@ -0,0 +1,199 @@ + + + + + web2py.gluon.rocket.SocketClosed + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module rocket :: + Class SocketClosed + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class SocketClosed

    source code

    +
    +              object --+        
    +                       |        
    +exceptions.BaseException --+    
    +                           |    
    +        exceptions.Exception --+
    +                               |
    +                              SocketClosed
    +
    + +
    +Exception for when a socket is closed by the client.

    + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from exceptions.Exception: + __init__, + __new__ +

    +

    Inherited from exceptions.BaseException: + __delattr__, + __getattribute__, + __getitem__, + __getslice__, + __reduce__, + __repr__, + __setattr__, + __setstate__, + __str__ +

    +

    Inherited from object: + __hash__, + __reduce_ex__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from exceptions.BaseException: + args, + message +

    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.rocket.SocketTimeout-class.html Index: applications/examples/static/epydoc/web2py.gluon.rocket.SocketTimeout-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.rocket.SocketTimeout-class.html +++ applications/examples/static/epydoc/web2py.gluon.rocket.SocketTimeout-class.html @@ -0,0 +1,199 @@ + + + + + web2py.gluon.rocket.SocketTimeout + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module rocket :: + Class SocketTimeout + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class SocketTimeout

    source code

    +
    +              object --+        
    +                       |        
    +exceptions.BaseException --+    
    +                           |    
    +        exceptions.Exception --+
    +                               |
    +                              SocketTimeout
    +
    + +
    +Exception for when a socket times out between requests.

    + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from exceptions.Exception: + __init__, + __new__ +

    +

    Inherited from exceptions.BaseException: + __delattr__, + __getattribute__, + __getitem__, + __getslice__, + __reduce__, + __repr__, + __setattr__, + __setstate__, + __str__ +

    +

    Inherited from object: + __hash__, + __reduce_ex__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from exceptions.BaseException: + args, + message +

    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.rocket.ThreadPool-class.html Index: applications/examples/static/epydoc/web2py.gluon.rocket.ThreadPool-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.rocket.ThreadPool-class.html +++ applications/examples/static/epydoc/web2py.gluon.rocket.ThreadPool-class.html @@ -0,0 +1,257 @@ + + + + + web2py.gluon.rocket.ThreadPool + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module rocket :: + Class ThreadPool + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class ThreadPool

    source code

    +The ThreadPool class is a container class for all the worker threads. + It manages the number of actively running threads.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + method, + app_info, + active_queue, + monitor_queue, + min_threads=10, + max_threads=0) + source code + +
    + +
    +   + + + + + + +
    start(self) + source code + +
    + +
    +   + + + + + + +
    stop(self) + source code + +
    + +
    +   + + + + + + +
    bring_out_your_dead(self) + source code + +
    + +
    +   + + + + + + +
    grow(self, + amount=1) + source code + +
    + +
    +   + + + + + + +
    shrink(self, + amount=1) + source code + +
    + +
    +   + + + + + + +
    dynamic_resize(self) + source code + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.rocket.WSGIWorker-class.html Index: applications/examples/static/epydoc/web2py.gluon.rocket.WSGIWorker-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.rocket.WSGIWorker-class.html +++ applications/examples/static/epydoc/web2py.gluon.rocket.WSGIWorker-class.html @@ -0,0 +1,413 @@ + + + + + web2py.gluon.rocket.WSGIWorker + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module rocket :: + Class WSGIWorker + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class WSGIWorker

    source code

    +
    +        object --+            
    +                 |            
    +threading._Verbose --+        
    +                     |        
    +      threading.Thread --+    
    +                         |    
    +                    Worker --+
    +                             |
    +                            WSGIWorker
    +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + *args, + **kwargs)
    + Builds some instance variables that will last the life of the + thread.
    + source code + +
    + +
    +   + + + + + + +
    build_environ(self, + sock_file, + conn)
    + Build the execution environment.
    + source code + +
    + +
    +   + + + + + + +
    send_headers(self, + data, + sections) + source code + +
    + +
    +   + + + + + + +
    write_warning(self, + data, + sections=1) + source code + +
    + +
    +   + + + + + + +
    write(self, + data, + sections=1)
    + Write the data to the output socket.
    + source code + +
    + +
    +   + + + + + + +
    start_response(self, + status, + response_headers, + exc_info=1)
    + Store the HTTP status and headers to be sent when self.write is + called.
    + source code + +
    + +
    +   + + + + + + +
    run_app(self, + conn) + source code + +
    + +
    +

    Inherited from Worker: + kill, + read_headers, + read_request_line, + run, + send_response +

    +

    Inherited from Worker (private): + _handleError, + _read_request_line_jython +

    +

    Inherited from threading.Thread: + __repr__, + getName, + isAlive, + isDaemon, + join, + setDaemon, + setName, + start +

    +

    Inherited from threading.Thread (private): + _set_daemon +

    +

    Inherited from threading._Verbose (private): + _note +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + *args, + **kwargs) +
    (Constructor) +

    +
    source code  +
    + + Builds some instance variables that will last the life of the + thread. +
    +
    Overrides: + Worker.__init__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    run_app(self, + conn) +

    +
    source code  +
    + + +
    +
    Overrides: + Worker.run_app +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.rocket.Worker-class.html Index: applications/examples/static/epydoc/web2py.gluon.rocket.Worker-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.rocket.Worker-class.html +++ applications/examples/static/epydoc/web2py.gluon.rocket.Worker-class.html @@ -0,0 +1,435 @@ + + + + + web2py.gluon.rocket.Worker + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module rocket :: + Class Worker + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class Worker

    source code

    +
    +        object --+        
    +                 |        
    +threading._Verbose --+    
    +                     |    
    +      threading.Thread --+
    +                         |
    +                        Worker
    +
    + +
    Known Subclasses:
    +
    + WSGIWorker +
    + +
    +The Worker class is a base class responsible for receiving connections + and (a subclass) will run an application to process the the + connection

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + app_info, + active_queue, + monitor_queue, + *args, + **kwargs) + source code + +
    + +
    +   + + + + + + +
    _handleError(self, + typ, + val, + tb) + source code + +
    + +
    +   + + + + + + +
    run(self) + source code + +
    + +
    +   + + + + + + +
    run_app(self, + conn) + source code + +
    + +
    +   + + + + + + +
    send_response(self, + status) + source code + +
    + +
    +   + + + + + + +
    kill(self) + source code + +
    + +
    +   + + + + + + +
    read_request_line(self, + sock_file) + source code + +
    + +
    +   + + + + + + +
    _read_request_line_jython(self, + d) + source code + +
    + +
    +   + + + + + + +
    read_headers(self, + sock_file) + source code + +
    + +
    +

    Inherited from threading.Thread: + __repr__, + getName, + isAlive, + isDaemon, + join, + setDaemon, + setName, + start +

    +

    Inherited from threading.Thread (private): + _set_daemon +

    +

    Inherited from threading._Verbose (private): + _note +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + app_info, + active_queue, + monitor_queue, + *args, + **kwargs) +
    (Constructor) +

    +
    source code  +
    + + +
    +
    Overrides: + threading.Thread.__init__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    run(self) +

    +
    source code  +
    + + +
    +
    Overrides: + threading.Thread.run +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.sanitizer-module.html Index: applications/examples/static/epydoc/web2py.gluon.sanitizer-module.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.sanitizer-module.html +++ applications/examples/static/epydoc/web2py.gluon.sanitizer-module.html @@ -0,0 +1,203 @@ + + + + + web2py.gluon.sanitizer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module sanitizer + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Module sanitizer

    source code

    +: +
    +   # from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/496942
    +   # Title: Cross-site scripting (XSS) defense
    +   # Submitter: Josh Goldfoot (other recipes)
    +   # Last Updated: 2006/08/05
    +   # Version no: 1.0
    +


    + + + + + + + + + + +
    + + + + + +
    Classes[hide private]
    +
    +   + + XssCleaner +
    + + + + + + + + + + + + +
    + + + + + +
    Functions[hide private]
    +
    +   + + + + + + +
    xssescape(text)
    + Gets rid of < and > and & and, for good measure, :
    + source code + +
    + +
    +   + + + + + + +
    sanitize(text, + permitted_tags=['a', 'b', 'blockquote', 'br/', 'i', 'li', 'ol', 'ul', 'p', 'c..., + allowed_attributes={'a': ['href', 'title'], 'blockquote': ['type'], 'img': ['src'..., + escape=True) + source code + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.sanitizer-pysrc.html Index: applications/examples/static/epydoc/web2py.gluon.sanitizer-pysrc.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.sanitizer-pysrc.html +++ applications/examples/static/epydoc/web2py.gluon.sanitizer-pysrc.html @@ -0,0 +1,1384 @@ + + + + + web2py.gluon.sanitizer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module sanitizer + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Module web2py.gluon.sanitizer

    +
    +  1  #!/usr/bin/env python 
    +  2  # -*- coding: utf-8 -*- 
    +  3   
    +  4  """ 
    +  5  :: 
    +  6   
    +  7      # from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/496942 
    +  8      # Title: Cross-site scripting (XSS) defense 
    +  9      # Submitter: Josh Goldfoot (other recipes) 
    + 10      # Last Updated: 2006/08/05 
    + 11      # Version no: 1.0 
    + 12   
    + 13  """ 
    + 14   
    + 15   
    + 16  from htmllib import HTMLParser 
    + 17  from cgi import escape 
    + 18  from urlparse import urlparse 
    + 19  from formatter import AbstractFormatter 
    + 20  from htmlentitydefs import entitydefs 
    + 21  from xml.sax.saxutils import quoteattr 
    + 22   
    + 23  __all__ = ['sanitize'] 
    + 24   
    + 25   
    +
    26 -def xssescape(text): +
    27 """Gets rid of < and > and & and, for good measure, :""" + 28 + 29 return escape(text, quote=True).replace(':', '&#58;') +
    30 + 31 +
    32 -class XssCleaner(HTMLParser): +
    33 +
    34 - def __init__( + 35 self, + 36 permitted_tags=[ + 37 'a', + 38 'b', + 39 'blockquote', + 40 'br/', + 41 'i', + 42 'li', + 43 'ol', + 44 'ul', + 45 'p', + 46 'cite', + 47 'code', + 48 'pre', + 49 'img/', + 50 ], + 51 allowed_attributes={'a': ['href', 'title'], 'img': ['src', 'alt' + 52 ], 'blockquote': ['type']}, + 53 fmt=AbstractFormatter, + 54 strip_disallowed = False + 55 ): +
    56 + 57 HTMLParser.__init__(self, fmt) + 58 self.result = '' + 59 self.open_tags = [] + 60 self.permitted_tags = [i for i in permitted_tags if i[-1] != '/'] + 61 self.requires_no_close = [i[:-1] for i in permitted_tags + 62 if i[-1] == '/'] + 63 self.permitted_tags += self.requires_no_close + 64 self.allowed_attributes = allowed_attributes + 65 + 66 # The only schemes allowed in URLs (for href and src attributes). + 67 # Adding "javascript" or "vbscript" to this list would not be smart. + 68 + 69 self.allowed_schemes = ['http', 'https', 'ftp'] + 70 + 71 #to strip or escape disallowed tags? + 72 self.strip_disallowed = strip_disallowed + 73 self.in_disallowed = False +
    74 +
    75 - def handle_data(self, data): +
    76 if data and not self.in_disallowed: + 77 self.result += xssescape(data) +
    78 +
    79 - def handle_charref(self, ref): +
    80 if self.in_disallowed: + 81 return + 82 elif len(ref) < 7 and ref.isdigit(): + 83 self.result += '&#%s;' % ref + 84 else: + 85 self.result += xssescape('&#%s' % ref) +
    86 +
    87 - def handle_entityref(self, ref): +
    88 if self.in_disallowed: + 89 return + 90 elif ref in entitydefs: + 91 self.result += '&%s;' % ref + 92 else: + 93 self.result += xssescape('&%s' % ref) +
    94 +
    95 - def handle_comment(self, comment): +
    96 if self.in_disallowed: + 97 return + 98 elif comment: + 99 self.result += xssescape('<!--%s-->' % comment) +
    100 +
    101 - def handle_starttag( +102 self, +103 tag, +104 method, +105 attrs, +106 ): +
    107 if tag not in self.permitted_tags: +108 if self.strip_disallowed: +109 self.in_disallowed = True +110 else: +111 self.result += xssescape('<%s>' % tag) +112 else: +113 bt = '<' + tag +114 if tag in self.allowed_attributes: +115 attrs = dict(attrs) +116 self.allowed_attributes_here = [x for x in +117 self.allowed_attributes[tag] if x in attrs +118 and len(attrs[x]) > 0] +119 for attribute in self.allowed_attributes_here: +120 if attribute in ['href', 'src', 'background']: +121 if self.url_is_acceptable(attrs[attribute]): +122 bt += ' %s="%s"' % (attribute, +123 attrs[attribute]) +124 else: +125 bt += ' %s=%s' % (xssescape(attribute), +126 quoteattr(attrs[attribute])) +127 if bt == '<a' or bt == '<img': +128 return +129 if tag in self.requires_no_close: +130 bt += ' /' +131 bt += '>' +132 self.result += bt +133 self.open_tags.insert(0, tag) +
    134 +
    135 - def handle_endtag(self, tag, attrs): +
    136 bracketed = '</%s>' % tag +137 if tag not in self.permitted_tags: +138 if self.strip_disallowed: +139 self.in_disallowed = False +140 else: +141 self.result += xssescape(bracketed) +142 elif tag in self.open_tags: +143 self.result += bracketed +144 self.open_tags.remove(tag) +
    145 +
    146 - def unknown_starttag(self, tag, attributes): +
    147 self.handle_starttag(tag, None, attributes) +
    148 +
    149 - def unknown_endtag(self, tag): +
    150 self.handle_endtag(tag, None) +
    151 +
    152 - def url_is_acceptable(self, url): +
    153 """ +154 Accepts relative and absolute urls +155 """ +156 +157 parsed = urlparse(url) +158 return (parsed[0] in self.allowed_schemes and '.' in parsed[1]) \ +159 or (parsed[0] == '' and parsed[2].startswith('/')) +
    160 +
    161 - def strip(self, rawstring, escape=True): +
    162 """ +163 Returns the argument stripped of potentially harmful +164 HTML or Javascript code +165 +166 @type escape: boolean +167 @param escape: If True (default) it escapes the potentially harmful +168 content, otherwise remove it +169 """ +170 +171 if not isinstance(rawstring, str): return str(rawstring) +172 for tag in self.requires_no_close: +173 rawstring = rawstring.replace("<%s/>" % tag, "<%s />" % tag) +174 if not escape: +175 self.strip_disallowed = True +176 self.result = '' +177 self.feed(rawstring) +178 for endtag in self.open_tags: +179 if endtag not in self.requires_no_close: +180 self.result += '</%s>' % endtag +181 return self.result +
    182 +
    183 - def xtags(self): +
    184 """ +185 Returns a printable string informing the user which tags are allowed +186 """ +187 +188 tg = '' +189 for x in sorted(self.permitted_tags): +190 tg += '<' + x +191 if x in self.allowed_attributes: +192 for y in self.allowed_attributes[x]: +193 tg += ' %s=""' % y +194 tg += '> ' +195 return xssescape(tg.strip()) +
    196 +197 +
    198 -def sanitize(text, permitted_tags=[ +199 'a', +200 'b', +201 'blockquote', +202 'br/', +203 'i', +204 'li', +205 'ol', +206 'ul', +207 'p', +208 'cite', +209 'code', +210 'pre', +211 'img/', +212 'h1','h2','h3','h4','h5','h6', +213 'table','tr','td','div', +214 ], +215 allowed_attributes = { +216 'a': ['href', 'title'], +217 'img': ['src', 'alt'], +218 'blockquote': ['type'], +219 'td': ['colspan'], +220 }, +221 escape=True): +
    222 if not isinstance(text, str): return str(text) +223 return XssCleaner(permitted_tags=permitted_tags, +224 allowed_attributes=allowed_attributes).strip(text, escape) +
    225 +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.sanitizer.XssCleaner-class.html Index: applications/examples/static/epydoc/web2py.gluon.sanitizer.XssCleaner-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.sanitizer.XssCleaner-class.html +++ applications/examples/static/epydoc/web2py.gluon.sanitizer.XssCleaner-class.html @@ -0,0 +1,788 @@ + + + + + web2py.gluon.sanitizer.XssCleaner + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module sanitizer :: + Class XssCleaner + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class XssCleaner

    source code

    +
    +markupbase.ParserBase --+        
    +                        |        
    +       sgmllib.SGMLParser --+    
    +                            |    
    +           htmllib.HTMLParser --+
    +                                |
    +                               XssCleaner
    +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + permitted_tags=['a', 'b', 'blockquote', 'br/', 'i', 'li', 'ol', 'ul', 'p', 'c..., + allowed_attributes={'a': ['href', 'title'], 'blockquote': ['type'], 'img': ['src'..., + fmt=<class formatter.AbstractFormatter at 0xa77cb0>, + strip_disallowed=True)
    + Creates an instance of the HTMLParser class.
    + source code + +
    + +
    +   + + + + + + +
    handle_data(self, + data) + source code + +
    + +
    +   + + + + + + +
    handle_charref(self, + ref)
    + Handle character reference, no need to override.
    + source code + +
    + +
    +   + + + + + + +
    handle_entityref(self, + ref)
    + Handle entity references, no need to override.
    + source code + +
    + +
    +   + + + + + + +
    handle_comment(self, + comment) + source code + +
    + +
    +   + + + + + + +
    handle_starttag(self, + tag, + method, + attrs) + source code + +
    + +
    +   + + + + + + +
    handle_endtag(self, + tag, + attrs) + source code + +
    + +
    +   + + + + + + +
    unknown_starttag(self, + tag, + attributes) + source code + +
    + +
    +   + + + + + + +
    unknown_endtag(self, + tag) + source code + +
    + +
    +   + + + + + + +
    url_is_acceptable(self, + url)
    + Accepts relative and absolute urls
    + source code + +
    + +
    +   + + + + + + +
    strip(self, + rawstring, + escape=True)
    + Returns the argument stripped of potentially harmful HTML or + Javascript code
    + source code + +
    + +
    +   + + + + + + +
    xtags(self)
    + Returns a printable string informing the user which tags are + allowed
    + source code + +
    + +
    +

    Inherited from htmllib.HTMLParser: + anchor_bgn, + anchor_end, + ddpop, + do_base, + do_br, + do_dd, + do_dt, + do_hr, + do_img, + do_isindex, + do_li, + do_link, + do_meta, + do_nextid, + do_p, + do_plaintext, + end_a, + end_address, + end_b, + end_blockquote, + end_body, + end_cite, + end_code, + end_dir, + end_dl, + end_em, + end_h1, + end_h2, + end_h3, + end_h4, + end_h5, + end_h6, + end_head, + end_html, + end_i, + end_kbd, + end_listing, + end_menu, + end_ol, + end_pre, + end_samp, + end_strong, + end_title, + end_tt, + end_ul, + end_var, + end_xmp, + error, + handle_image, + reset, + save_bgn, + save_end, + start_a, + start_address, + start_b, + start_blockquote, + start_body, + start_cite, + start_code, + start_dir, + start_dl, + start_em, + start_h1, + start_h2, + start_h3, + start_h4, + start_h5, + start_h6, + start_head, + start_html, + start_i, + start_kbd, + start_listing, + start_menu, + start_ol, + start_pre, + start_samp, + start_strong, + start_title, + start_tt, + start_ul, + start_var, + start_xmp +

    +

    Inherited from sgmllib.SGMLParser: + close, + convert_charref, + convert_codepoint, + convert_entityref, + feed, + finish_endtag, + finish_shorttag, + finish_starttag, + get_starttag_text, + goahead, + handle_decl, + handle_pi, + parse_endtag, + parse_pi, + parse_starttag, + report_unbalanced, + setliteral, + setnomoretags, + unknown_charref, + unknown_entityref +

    +

    Inherited from sgmllib.SGMLParser (private): + _convert_ref +

    +

    Inherited from markupbase.ParserBase: + getpos, + parse_comment, + parse_declaration, + parse_marked_section, + unknown_decl, + updatepos +

    +

    Inherited from markupbase.ParserBase (private): + _parse_doctype_attlist, + _parse_doctype_element, + _parse_doctype_entity, + _parse_doctype_notation, + _parse_doctype_subset, + _scan_name +

    +
    + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +

    Inherited from sgmllib.SGMLParser: + entity_or_charref +

    +

    Inherited from sgmllib.SGMLParser (private): + _decl_otherchars +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + permitted_tags=['a', 'b', 'blockquote', 'br/', 'i', 'li', 'ol', 'ul', 'p', 'c..., + allowed_attributes={'a': ['href', 'title'], 'blockquote': ['type'], 'img': ['src'..., + fmt=<class formatter.AbstractFormatter at 0xa77cb0>, + strip_disallowed=True) +
    (Constructor) +

    +
    source code  +
    + +

    Creates an instance of the HTMLParser class.

    + The formatter parameter is the formatter instance associated with the + parser. +
    +
    Overrides: + htmllib.HTMLParser.__init__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    handle_data(self, + data) +

    +
    source code  +
    + + +
    +
    Overrides: + htmllib.HTMLParser.handle_data +
    +
    +
    +
    + +
    + +
    + + +
    +

    handle_charref(self, + ref) +

    +
    source code  +
    + + Handle character reference, no need to override. +
    +
    Overrides: + sgmllib.SGMLParser.handle_charref +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    handle_entityref(self, + ref) +

    +
    source code  +
    + + Handle entity references, no need to override. +
    +
    Overrides: + sgmllib.SGMLParser.handle_entityref +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    handle_comment(self, + comment) +

    +
    source code  +
    + + +
    +
    Overrides: + sgmllib.SGMLParser.handle_comment +
    +
    +
    +
    + +
    + +
    + + +
    +

    handle_starttag(self, + tag, + method, + attrs) +

    +
    source code  +
    + + +
    +
    Overrides: + sgmllib.SGMLParser.handle_starttag +
    +
    +
    +
    + +
    + +
    + + +
    +

    handle_endtag(self, + tag, + attrs) +

    +
    source code  +
    + + +
    +
    Overrides: + sgmllib.SGMLParser.handle_endtag +
    +
    +
    +
    + +
    + +
    + + +
    +

    unknown_starttag(self, + tag, + attributes) +

    +
    source code  +
    + + +
    +
    Overrides: + htmllib.HTMLParser.unknown_starttag +
    +
    +
    +
    + +
    + +
    + + +
    +

    unknown_endtag(self, + tag) +

    +
    source code  +
    + + +
    +
    Overrides: + htmllib.HTMLParser.unknown_endtag +
    +
    +
    +
    + +
    + +
    + + +
    +

    strip(self, + rawstring, + escape=True) +

    +
    source code  +
    + + Returns the argument stripped of potentially harmful HTML or + Javascript code +
    +
    Parameters:
    +
      +
    • escape (boolean) - If True (default) it escapes the potentially harmful content, + otherwise remove it
    • +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.serializers-module.html Index: applications/examples/static/epydoc/web2py.gluon.serializers-module.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.serializers-module.html +++ applications/examples/static/epydoc/web2py.gluon.serializers-module.html @@ -0,0 +1,237 @@ + + + + + web2py.gluon.serializers + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module serializers + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Module serializers

    source code

    +This file is part of the web2py Web Framework Copyrighted by Massimo + Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Functions[hide private]
    +
    +   + + + + + + +
    custom_json(o) + source code + +
    + +
    +   + + + + + + +
    xml_rec(value, + key) + source code + +
    + +
    +   + + + + + + +
    xml(value, + encoding='UTF-8', + key='document') + source code + +
    + +
    +   + + + + + + +
    json(value, + default=<function custom_json at 0xc0db18>) + source code + +
    + +
    +   + + + + + + +
    csv(value) + source code + +
    + +
    +   + + + + + + +
    rss(feed) + source code + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.serializers-pysrc.html Index: applications/examples/static/epydoc/web2py.gluon.serializers-pysrc.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.serializers-pysrc.html +++ applications/examples/static/epydoc/web2py.gluon.serializers-pysrc.html @@ -0,0 +1,215 @@ + + + + + web2py.gluon.serializers + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module serializers + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Module web2py.gluon.serializers

    +
    + 1  """ 
    + 2  This file is part of the web2py Web Framework 
    + 3  Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> 
    + 4  License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) 
    + 5  """ 
    + 6  import datetime 
    + 7  from storage import Storage 
    + 8  from html import TAG 
    + 9  from html import xmlescape 
    +10  from languages import lazyT 
    +11  import contrib.simplejson as simplejson 
    +12  import contrib.rss2 as rss2 
    +13   
    +
    14 -def custom_json(o): +
    15 if hasattr(o,'custom_json') and callable(o.custom_json): +16 return o.custom_json() +17 if isinstance(o, (datetime.date, +18 datetime.datetime, +19 datetime.time)): +20 return o.isoformat()[:19].replace('T',' ') +21 elif isinstance(o, (int, long)): +22 return int(o) +23 elif isinstance(o, lazyT): +24 return str(o) +25 elif hasattr(o,'as_list') and callable(o.as_list): +26 return o.as_list() +27 elif hasattr(o,'as_dict') and callable(o.as_dict): +28 return o.as_dict() +29 else: +30 raise TypeError(repr(o) + " is not JSON serializable") +
    31 +32 +
    33 -def xml_rec(value, key): +
    34 if hasattr(value,'custom_xml') and callable(value.custom_xml): +35 return value.custom_xml() +36 elif isinstance(value, (dict, Storage)): +37 return TAG[key](*[TAG[k](xml_rec(v, '')) for k, v in value.items()]) +38 elif isinstance(value, list): +39 return TAG[key](*[TAG.item(xml_rec(item, '')) for item in value]) +40 elif hasattr(value,'as_list') and callable(value.as_list): +41 return str(xml_rec(value.as_list(),'')) +42 elif hasattr(value,'as_dict') and callable(value.as_dict): +43 return str(xml_rec(value.as_dict(),'')) +44 else: +45 return xmlescape(value) +
    46 +47 +
    48 -def xml(value, encoding='UTF-8', key='document'): +
    49 return ('<?xml version="1.0" encoding="%s"?>' % encoding) + str(xml_rec(value,key)) +
    50 +51 +
    52 -def json(value,default=custom_json): +
    53 return simplejson.dumps(value,default=default) +
    54 +55 +
    56 -def csv(value): +
    57 return '' +
    58 +59 +
    60 -def rss(feed): +
    61 if not 'entries' in feed and 'items' in feed: +62 feed['entries'] = feed['items'] +63 now=datetime.datetime.now() +64 rss = rss2.RSS2(title = feed['title'], +65 link = str(feed['link']), +66 description = feed['description'], +67 lastBuildDate = feed.get('created_on', now), +68 items = [rss2.RSSItem(\ +69 title=entry['title'], +70 link=str(entry['link']), +71 description=entry['description'], +72 pubDate=entry.get('created_on', now) +73 )\ +74 for entry in feed['entries'] +75 ] +76 ) +77 return rss2.dumps(rss) +
    78 +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.settings-module.html Index: applications/examples/static/epydoc/web2py.gluon.settings-module.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.settings-module.html +++ applications/examples/static/epydoc/web2py.gluon.settings-module.html @@ -0,0 +1,214 @@ + + + + + web2py.gluon.settings + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module settings + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Module settings

    source code

    +This file is part of the web2py Web Framework Copyrighted by Massimo + Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)

    + + + + + + + + + + + + + +
    + + + + + +
    Variables[hide private]
    +
    +   + + global_settings = <Storage {'debugging': False, 'gluon_parent'... +
    +   + + settings = <Storage {'debugging': False, 'gluon_parent': '/hom... +
    + + + + + + +
    + + + + + +
    Variables Details[hide private]
    +
    + +
    + +
    +

    global_settings

    + +
    +
    +
    +
    Value:
    +
    +<Storage {'debugging': False, 'gluon_parent': '/home/mdipierro/web2py'\
    +, 'is_jython': False, 'applications_parent': '/home/mdipierro/web2py',\
    + 'app_folders': set([]), 'db_sessions': set([])}>
    +
    +
    +
    +
    +
    + +
    + +
    +

    settings

    + +
    +
    +
    +
    Value:
    +
    +<Storage {'debugging': False, 'gluon_parent': '/home/mdipierro/web2py'\
    +, 'is_jython': False, 'applications_parent': '/home/mdipierro/web2py',\
    + 'app_folders': set([]), 'db_sessions': set([])}>
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.settings-pysrc.html Index: applications/examples/static/epydoc/web2py.gluon.settings-pysrc.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.settings-pysrc.html +++ applications/examples/static/epydoc/web2py.gluon.settings-pysrc.html @@ -0,0 +1,133 @@ + + + + + web2py.gluon.settings + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module settings + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Module web2py.gluon.settings

    +
    + 1  """ 
    + 2  This file is part of the web2py Web Framework 
    + 3  Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> 
    + 4  License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) 
    + 5  """ 
    + 6   
    + 7  from storage import Storage 
    + 8   
    + 9  global_settings = Storage() 
    +10  settings = global_settings  # legacy compatibility 
    +11   
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.shell-module.html Index: applications/examples/static/epydoc/web2py.gluon.shell-module.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.shell-module.html +++ applications/examples/static/epydoc/web2py.gluon.shell-module.html @@ -0,0 +1,487 @@ + + + + + web2py.gluon.shell + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module shell + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Module shell

    source code

    +This file is part of the web2py Web Framework Developed by Massimo Di + Pierro <mdipierro@cs.depaul.edu>, limodou <limodou@gmail.com> + and srackham <srackham@gmail.com>. License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Functions[hide private]
    +
    +   + + + + + + +
    exec_environment(pyfile='', + request=1, + response=1, + session=1)
    + ..
    + source code + +
    + +
    +   + + + + + + +
    env(a, + import_models=True, + c=1, + f=1, + dir='', + extra_request={})
    + Return web2py execution environment for application (a), + controller (c), function (f).
    + source code + +
    + +
    +   + + + + + + +
    exec_pythonrc() + source code + +
    + +
    +   + + + + + + +
    run(appname, + plain=True, + import_models=True, + startfile=1, + bpython=True)
    + Start interactive shell or run Python script (startfile) in web2py + controller environment.
    + source code + +
    + +
    +   + + + + + + +
    parse_path_info(path_info)
    + Parse path info formatted like a/c/f where c and f are optional + and a leading / accepted.
    + source code + +
    + +
    +   + + + + + + +
    die(msg) + source code + +
    + +
    +   + + + + + + +
    test(testpath, + import_models=True, + verbose=True)
    + Run doctests in web2py environment.
    + source code + +
    + +
    +   + + + + + + +
    get_usage() + source code + +
    + +
    +   + + + + + + +
    execute_from_command_line(argv=1) + source code + +
    + +
    + + + + + + + + + +
    + + + + + +
    Variables[hide private]
    +
    +   + + logger = logging.getLogger("web2py") +
    + + + + + + +
    + + + + + +
    Function Details[hide private]
    +
    + +
    + +
    + + +
    +

    exec_environment(pyfile='', + request=1, + response=1, + session=1) +

    +
    source code  +
    + +
    +
    +.. function:: gluon.shell.exec_environment([pyfile=''[, request=Request()
    +    [, response=Response[, session=Session()]]]])
    +
    +    Environment builder and module loader.
    +
    +
    +    Builds a web2py environment and optionally executes a Python
    +    file into the environment.
    +    A Storage dictionary containing the resulting environment is returned.
    +    The working directory must be web2py root -- this is the web2py default.
    +
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    env(a, + import_models=True, + c=1, + f=1, + dir='', + extra_request={}) +

    +
    source code  +
    + +

    Return web2py execution environment for application (a), controller + (c), function (f). If import_models is True the exec all application + models into the environment.

    + extra_request allows you to pass along any extra variables to the + request object before your models get executed. This was mainly done to + support web2py_utils.test_runner, however you can use it with any wrapper + scripts that need access to the web2py environment. +
    +
    +
    +
    + +
    + +
    + + +
    +

    run(appname, + plain=True, + import_models=True, + startfile=1, + bpython=True) +

    +
    source code  +
    + +

    Start interactive shell or run Python script (startfile) in web2py + controller environment. appname is formatted like:

    + a web2py application name a/c exec the controller c into the + application environment +
    +
    +
    +
    + +
    + +
    + + +
    +

    parse_path_info(path_info) +

    +
    source code  +
    + + Parse path info formatted like a/c/f where c and f are optional and a + leading / accepted. Return tuple (a, c, f). If invalid path_info a is set + to None. If c or f are omitted they are set to None. +
    +
    +
    +
    + +
    + +
    + + +
    +

    test(testpath, + import_models=True, + verbose=True) +

    +
    source code  +
    + +

    Run doctests in web2py environment. testpath is formatted like:

    +

    a tests all controllers in application a a/c tests controller + c in application a a/c/f test function f in controller c, application + a

    + Where a, c and f are application, controller and function names + respectively. If the testpath is a file name the file is tested. If a + controller is specified models are executed by default. +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.shell-pysrc.html Index: applications/examples/static/epydoc/web2py.gluon.shell-pysrc.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.shell-pysrc.html +++ applications/examples/static/epydoc/web2py.gluon.shell-pysrc.html @@ -0,0 +1,703 @@ + + + + + web2py.gluon.shell + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module shell + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Module web2py.gluon.shell

    +
    +  1  #!/usr/bin/env python 
    +  2  # -*- coding: utf-8 -*- 
    +  3   
    +  4  """ 
    +  5  This file is part of the web2py Web Framework 
    +  6  Developed by Massimo Di Pierro <mdipierro@cs.depaul.edu>, 
    +  7  limodou <limodou@gmail.com> and srackham <srackham@gmail.com>. 
    +  8  License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) 
    +  9   
    + 10  """ 
    + 11   
    + 12  import os 
    + 13  import sys 
    + 14  import code 
    + 15  import logging 
    + 16  import types 
    + 17  import re 
    + 18  import optparse 
    + 19  import glob 
    + 20   
    + 21  import fileutils 
    + 22  import settings 
    + 23  from utils import web2py_uuid 
    + 24  from compileapp import build_environment, read_pyc, run_models_in 
    + 25  from restricted import RestrictedError 
    + 26  from globals import Request, Response, Session 
    + 27  from storage import Storage 
    + 28  from admin import w2p_unpack 
    + 29   
    + 30   
    + 31  logger = logging.getLogger("web2py") 
    + 32   
    +
    33 -def exec_environment( + 34 pyfile='', + 35 request=None, + 36 response=None, + 37 session=None, + 38 ): +
    39 """ + 40 .. function:: gluon.shell.exec_environment([pyfile=''[, request=Request() + 41 [, response=Response[, session=Session()]]]]) + 42 + 43 Environment builder and module loader. + 44 + 45 + 46 Builds a web2py environment and optionally executes a Python + 47 file into the environment. + 48 A Storage dictionary containing the resulting environment is returned. + 49 The working directory must be web2py root -- this is the web2py default. + 50 + 51 """ + 52 + 53 if request==None: request = Request() + 54 if response==None: response = Response() + 55 if session==None: session = Session() + 56 + 57 if request.folder is None: + 58 mo = re.match(r'(|.*/)applications/(?P<appname>[^/]+)', pyfile) + 59 if mo: + 60 appname = mo.group('appname') + 61 request.folder = os.path.join('applications', appname) + 62 else: + 63 request.folder = '' + 64 env = build_environment(request, response, session, store_current=False) + 65 if pyfile: + 66 pycfile = pyfile + 'c' + 67 if os.path.isfile(pycfile): + 68 exec read_pyc(pycfile) in env + 69 else: + 70 execfile(pyfile, env) + 71 return Storage(env) +
    72 + 73 +
    74 -def env( + 75 a, + 76 import_models=False, + 77 c=None, + 78 f=None, + 79 dir='', + 80 extra_request={}, + 81 ): +
    82 """ + 83 Return web2py execution environment for application (a), controller (c), + 84 function (f). + 85 If import_models is True the exec all application models into the + 86 environment. + 87 + 88 extra_request allows you to pass along any extra + 89 variables to the request object before your models + 90 get executed. This was mainly done to support + 91 web2py_utils.test_runner, however you can use it + 92 with any wrapper scripts that need access to the + 93 web2py environment. + 94 """ + 95 + 96 request = Request() + 97 response = Response() + 98 session = Session() + 99 request.application = a +100 +101 # Populate the dummy environment with sensible defaults. +102 +103 if not dir: +104 request.folder = os.path.join('applications', a) +105 else: +106 request.folder = dir +107 request.controller = c or 'default' +108 request.function = f or 'index' +109 response.view = '%s/%s.html' % (request.controller, +110 request.function) +111 request.env.path_info = '/%s/%s/%s' % (a, c, f) +112 request.env.http_host = '127.0.0.1:8000' +113 request.env.remote_addr = '127.0.0.1' +114 request.env.web2py_runtime_gae = settings.global_settings.web2py_runtime_gae +115 +116 for k,v in extra_request.items(): +117 request[k] = v +118 +119 # Monkey patch so credentials checks pass. +120 +121 def check_credentials(request, other_application='admin'): +122 return True +
    123 +124 fileutils.check_credentials = check_credentials +125 +126 environment = build_environment(request, response, session) +127 +128 if import_models: +129 try: +130 run_models_in(environment) +131 except RestrictedError, e: +132 sys.stderr.write(e.traceback+'\n') +133 sys.exit(1) +134 +135 environment['__name__'] = '__main__' +136 return environment +137 +138 +
    139 -def exec_pythonrc(): +
    140 pythonrc = os.environ.get('PYTHONSTARTUP') +141 if pythonrc and os.path.isfile(pythonrc): +142 try: +143 execfile(pythonrc) +144 except NameError: +145 pass +
    146 +147 +
    148 -def run( +149 appname, +150 plain=False, +151 import_models=False, +152 startfile=None, +153 bpython=False +154 ): +
    155 """ +156 Start interactive shell or run Python script (startfile) in web2py +157 controller environment. appname is formatted like: +158 +159 a web2py application name +160 a/c exec the controller c into the application environment +161 """ +162 +163 (a, c, f) = parse_path_info(appname) +164 errmsg = 'invalid application name: %s' % appname +165 if not a: +166 die(errmsg) +167 adir = os.path.join('applications', a) +168 if not os.path.exists(adir): +169 if raw_input('application %s does not exist, create (y/n)?' +170 % a).lower() in ['y', 'yes']: +171 os.mkdir(adir) +172 w2p_unpack('welcome.w2p', adir) +173 for subfolder in ['models','views','controllers', 'databases', +174 'modules','cron','errors','sessions', +175 'languages','static','private','uploads']: +176 subpath = os.path.join(adir,subfolder) +177 if not os.path.exists(subpath): +178 os.mkdir(subpath) +179 db = os.path.join(adir,'models/db.py') +180 if os.path.exists(db): +181 data = fileutils.read_file(db) +182 data = data.replace('<your secret key>','sha512:'+web2py_uuid()) +183 fileutils.write_file(db, data) +184 +185 if c: +186 import_models = True +187 _env = env(a, c=c, import_models=import_models) +188 if c: +189 cfile = os.path.join('applications', a, 'controllers', c + '.py') +190 if not os.path.isfile(cfile): +191 cfile = os.path.join('applications', a, 'compiled', "controllers_%s_%s.pyc" % (c,f)) +192 if not os.path.isfile(cfile): +193 die(errmsg) +194 else: +195 exec read_pyc(cfile) in _env +196 else: +197 execfile(cfile, _env) +198 +199 if f: +200 exec ('print %s()' % f, _env) +201 elif startfile: +202 exec_pythonrc() +203 try: +204 execfile(startfile, _env) +205 except RestrictedError, e: +206 print e.traceback +207 else: +208 if not plain: +209 if bpython: +210 try: +211 import bpython +212 bpython.embed(locals_=_env) +213 return +214 except: +215 logger.warning( +216 'import bpython error; trying ipython...') +217 else: +218 try: +219 import IPython +220 # following 2 lines fix a problem with IPython; thanks Michael Toomim +221 if '__builtins__' in _env: +222 del _env['__builtins__'] +223 shell = IPython.Shell.IPShell(argv=[], user_ns=_env) +224 shell.mainloop() +225 return +226 except: +227 logger.warning( +228 'import IPython error; use default python shell') +229 try: +230 import readline +231 import rlcompleter +232 except ImportError: +233 pass +234 else: +235 readline.set_completer(rlcompleter.Completer(_env).complete) +236 readline.parse_and_bind('tab:complete') +237 exec_pythonrc() +238 code.interact(local=_env) +
    239 +240 +
    241 -def parse_path_info(path_info): +
    242 """ +243 Parse path info formatted like a/c/f where c and f are optional +244 and a leading / accepted. +245 Return tuple (a, c, f). If invalid path_info a is set to None. +246 If c or f are omitted they are set to None. +247 """ +248 +249 mo = re.match(r'^/?(?P<a>\w+)(/(?P<c>\w+)(/(?P<f>\w+))?)?$', +250 path_info) +251 if mo: +252 return (mo.group('a'), mo.group('c'), mo.group('f')) +253 else: +254 return (None, None, None) +
    255 +256 +
    257 -def die(msg): +
    258 print >> sys.stderr, msg +259 sys.exit(1) +
    260 +261 +
    262 -def test(testpath, import_models=True, verbose=False): +
    263 """ +264 Run doctests in web2py environment. testpath is formatted like: +265 +266 a tests all controllers in application a +267 a/c tests controller c in application a +268 a/c/f test function f in controller c, application a +269 +270 Where a, c and f are application, controller and function names +271 respectively. If the testpath is a file name the file is tested. +272 If a controller is specified models are executed by default. +273 """ +274 +275 import doctest +276 if os.path.isfile(testpath): +277 mo = re.match(r'(|.*/)applications/(?P<a>[^/]+)', testpath) +278 if not mo: +279 die('test file is not in application directory: %s' +280 % testpath) +281 a = mo.group('a') +282 c = f = None +283 files = [testpath] +284 else: +285 (a, c, f) = parse_path_info(testpath) +286 errmsg = 'invalid test path: %s' % testpath +287 if not a: +288 die(errmsg) +289 cdir = os.path.join('applications', a, 'controllers') +290 if not os.path.isdir(cdir): +291 die(errmsg) +292 if c: +293 cfile = os.path.join(cdir, c + '.py') +294 if not os.path.isfile(cfile): +295 die(errmsg) +296 files = [cfile] +297 else: +298 files = glob.glob(os.path.join(cdir, '*.py')) +299 for testfile in files: +300 globs = env(a, import_models) +301 ignores = globs.keys() +302 execfile(testfile, globs) +303 +304 def doctest_object(name, obj): +305 """doctest obj and enclosed methods and classes.""" +306 +307 if type(obj) in (types.FunctionType, types.TypeType, +308 types.ClassType, types.MethodType, +309 types.UnboundMethodType): +310 +311 # Reload environment before each test. +312 +313 globs = env(a, c=c, f=f, import_models=import_models) +314 execfile(testfile, globs) +315 doctest.run_docstring_examples(obj, globs=globs, +316 name='%s: %s' % (os.path.basename(testfile), +317 name), verbose=verbose) +318 if type(obj) in (types.TypeType, types.ClassType): +319 for attr_name in dir(obj): +320 +321 # Execute . operator so decorators are executed. +322 +323 o = eval('%s.%s' % (name, attr_name), globs) +324 doctest_object(attr_name, o) +
    325 +326 for (name, obj) in globs.items(): +327 if name not in ignores and (f is None or f == name): +328 doctest_object(name, obj) +329 +330 +
    331 -def get_usage(): +
    332 usage = """ +333 %prog [options] pythonfile +334 """ +335 return usage +
    336 +337 +
    338 -def execute_from_command_line(argv=None): +
    339 if argv is None: +340 argv = sys.argv +341 +342 parser = optparse.OptionParser(usage=get_usage()) +343 +344 parser.add_option('-S', '--shell', dest='shell', metavar='APPNAME', +345 help='run web2py in interactive shell or IPython(if installed) ' + \ +346 'with specified appname') +347 msg = 'run web2py in interactive shell or bpython (if installed) with' +348 msg += ' specified appname (if app does not exist it will be created).' +349 msg += '\n Use combined with --shell' +350 parser.add_option( +351 '-B', +352 '--bpython', +353 action='store_true', +354 default=False, +355 dest='bpython', +356 help=msg, +357 ) +358 parser.add_option( +359 '-P', +360 '--plain', +361 action='store_true', +362 default=False, +363 dest='plain', +364 help='only use plain python shell, should be used with --shell option', +365 ) +366 parser.add_option( +367 '-M', +368 '--import_models', +369 action='store_true', +370 default=False, +371 dest='import_models', +372 help='auto import model files, default is False, ' + \ +373 ' should be used with --shell option', +374 ) +375 parser.add_option( +376 '-R', +377 '--run', +378 dest='run', +379 metavar='PYTHON_FILE', +380 default='', +381 help='run PYTHON_FILE in web2py environment, ' + \ +382 'should be used with --shell option', +383 ) +384 +385 (options, args) = parser.parse_args(argv[1:]) +386 +387 if len(sys.argv) == 1: +388 parser.print_help() +389 sys.exit(0) +390 +391 if len(args) > 0: +392 startfile = args[0] +393 else: +394 startfile = '' +395 run(options.shell, options.plain, startfile=startfile, bpython=options.bpython) +
    396 +397 +398 if __name__ == '__main__': +399 execute_from_command_line() +400 +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.sql-module.html Index: applications/examples/static/epydoc/web2py.gluon.sql-module.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.sql-module.html +++ applications/examples/static/epydoc/web2py.gluon.sql-module.html @@ -0,0 +1,174 @@ + + + + + web2py.gluon.sql + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module sql + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Module sql

    source code

    + + + + + + + + + + + + +
    + + + + + +
    Classes[hide private]
    +
    +   + + DAL
    + an instance of this class represents a database connection +
    +   + + Field
    + an instance of this class represents a database field +
    + + + + + + + + + +
    + + + + + +
    Variables[hide private]
    +
    +   + + drivers = ['SQLite3', 'pymysql', 'PostgreSQL'] +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.sql-pysrc.html Index: applications/examples/static/epydoc/web2py.gluon.sql-pysrc.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.sql-pysrc.html +++ applications/examples/static/epydoc/web2py.gluon.sql-pysrc.html @@ -0,0 +1,128 @@ + + + + + web2py.gluon.sql + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module sql + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Module web2py.gluon.sql

    +
    +1  # this file exists for backward compatibility 
    +2   
    +3  __all__ = ['DAL','Field','drivers'] 
    +4   
    +5  from dal import DAL, Field, Table, Query, Set, Expression, Row, Rows, drivers, BaseAdapter, SQLField, SQLTable, SQLXorable, SQLQuery, SQLSet, SQLRows, SQLStorage, SQLDB, GQLDB, SQLALL, SQLCustomType 
    +6   
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.sqlhtml-module.html Index: applications/examples/static/epydoc/web2py.gluon.sqlhtml-module.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.sqlhtml-module.html +++ applications/examples/static/epydoc/web2py.gluon.sqlhtml-module.html @@ -0,0 +1,476 @@ + + + + + web2py.gluon.sqlhtml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module sqlhtml + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Module sqlhtml

    source code

    +
    +
    +This file is part of the web2py Web Framework
    +Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
    +License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
    +
    +Holds:
    +
    +- SQLFORM: provide a form for a table (with/without record)
    +- SQLTABLE: provides a table for a set of records
    +- form_factory: provides a SQLFORM for an non-db backed table
    +
    +


    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Classes[hide private]
    +
    +   + + FormWidget
    + helper for SQLFORM to generate form input fields (widget), + related to the fieldtype +
    +   + + StringWidget +
    +   + + IntegerWidget +
    +   + + DoubleWidget +
    +   + + DecimalWidget +
    +   + + TimeWidget +
    +   + + DateWidget +
    +   + + DatetimeWidget +
    +   + + TextWidget +
    +   + + BooleanWidget +
    +   + + OptionsWidget +
    +   + + ListWidget +
    +   + + MultipleOptionsWidget +
    +   + + RadioWidget +
    +   + + CheckboxesWidget +
    +   + + PasswordWidget +
    +   + + UploadWidget +
    +   + + AutocompleteWidget +
    +   + + SQLFORM
    + SQLFORM is used to map a table (and a current record) into an HTML form + +given a SQLTable stored in db.table + +generates an insert form:: + + SQLFORM(db.table) + +generates an update form:: + + record=db.table[some_id] + SQLFORM(db.table, record) + +generates an update with a delete button:: + + SQLFORM(db.table, record, deletable=True) + +if record is an int:: + + record=db.table[record] + +optional arguments: + +:param fields: a list of fields that should be placed in the form, + default is all. +
    +   + + SQLTABLE
    + given a Rows object, as returned by a db().select(), generates +an html table with the rows. +
    + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Functions[hide private]
    +
    +   + + + + + + +
    represent(field, + value, + record) + source code + +
    + +
    +   + + + + + + +
    safe_int(x) + source code + +
    + +
    +   + + + + + + +
    safe_float(x) + source code + +
    + +
    +   + + + + + + +
    form_factory(*fields, + **attributes)
    + generates a SQLFORM for the given fields.
    + source code + +
    + +
    + + + + + + + + + + + + +
    + + + + + +
    Variables[hide private]
    +
    +   + + table_field = re.compile(r'[\w_]+\.[\w_]+') +
    +   + + widget_class = re.compile(r'^\w*') +
    + + + + + + +
    + + + + + +
    Function Details[hide private]
    +
    + +
    + +
    + + +
    +

    form_factory(*fields, + **attributes) +
    Static Method +

    +
    source code  +
    + +

    generates a SQLFORM for the given fields.

    + Internally will build a non-database based data model to hold the + fields. +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.sqlhtml-pysrc.html Index: applications/examples/static/epydoc/web2py.gluon.sqlhtml-pysrc.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.sqlhtml-pysrc.html +++ applications/examples/static/epydoc/web2py.gluon.sqlhtml-pysrc.html @@ -0,0 +1,2831 @@ + + + + + web2py.gluon.sqlhtml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module sqlhtml + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Module web2py.gluon.sqlhtml

    +
    +   1  #!/usr/bin/env python 
    +   2  # -*- coding: utf-8 -*- 
    +   3   
    +   4  """ 
    +   5  This file is part of the web2py Web Framework 
    +   6  Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> 
    +   7  License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) 
    +   8   
    +   9  Holds: 
    +  10   
    +  11  - SQLFORM: provide a form for a table (with/without record) 
    +  12  - SQLTABLE: provides a table for a set of records 
    +  13  - form_factory: provides a SQLFORM for an non-db backed table 
    +  14   
    +  15  """ 
    +  16   
    +  17  from http import HTTP 
    +  18  from html import XML, SPAN, TAG, A, DIV, UL, LI, TEXTAREA, BR, IMG, SCRIPT 
    +  19  from html import FORM, INPUT, LABEL, OPTION, SELECT 
    +  20  from html import TABLE, THEAD, TBODY, TR, TD, TH 
    +  21  from html import URL as Url 
    +  22  from dal import DAL, Table, Row, CALLABLETYPES 
    +  23  from storage import Storage 
    +  24  from utils import md5_hash 
    +  25  from validators import IS_EMPTY_OR 
    +  26   
    +  27  import urllib 
    +  28  import re 
    +  29  import cStringIO 
    +  30   
    +  31   
    +  32  table_field = re.compile('[\w_]+\.[\w_]+') 
    +  33  widget_class = re.compile('^\w*') 
    +  34   
    +
    35 -def represent(field,value,record): +
    36 f = field.represent + 37 if not callable(f): + 38 return str(value) + 39 n = f.func_code.co_argcount-len(f.func_defaults or []) + 40 if n==1: + 41 return f(value) + 42 elif n==2: + 43 return f(value,record) + 44 else: + 45 raise RuntimeError, "field representation must take 1 or 2 args" +
    46 +
    47 -def safe_int(x): +
    48 try: + 49 return int(x) + 50 except ValueError: + 51 return 0 +
    52 +
    53 -def safe_float(x): +
    54 try: + 55 return float(x) + 56 except ValueError: + 57 return 0 +
    58 +
    59 -class FormWidget(object): +
    60 """ + 61 helper for SQLFORM to generate form input fields (widget), + 62 related to the fieldtype + 63 """ + 64 + 65 @staticmethod +
    66 - def _attributes(field, widget_attributes, **attributes): +
    67 """ + 68 helper to build a common set of attributes + 69 + 70 :param field: the field involved, some attributes are derived from this + 71 :param widget_attributes: widget related attributes + 72 :param attributes: any other supplied attributes + 73 """ + 74 attr = dict( + 75 _id = '%s_%s' % (field._tablename, field.name), + 76 _class = widget_class.match(str(field.type)).group(), + 77 _name = field.name, + 78 requires = field.requires, + 79 ) + 80 attr.update(widget_attributes) + 81 attr.update(attributes) + 82 return attr +
    83 + 84 @staticmethod +
    85 - def widget(field, value, **attributes): +
    86 """ + 87 generates the widget for the field. + 88 + 89 When serialized, will provide an INPUT tag: + 90 + 91 - id = tablename_fieldname + 92 - class = field.type + 93 - name = fieldname + 94 + 95 :param field: the field needing the widget + 96 :param value: value + 97 :param attributes: any other attributes to be applied + 98 """ + 99 + 100 raise NotImplementedError +
    101 +
    102 -class StringWidget(FormWidget): +
    103 + 104 @staticmethod +
    105 - def widget(field, value, **attributes): +
    106 """ + 107 generates an INPUT text tag. + 108 + 109 see also: :meth:`FormWidget.widget` + 110 """ + 111 + 112 default = dict( + 113 _type = 'text', + 114 value = (value!=None and str(value)) or '', + 115 ) + 116 attr = StringWidget._attributes(field, default, **attributes) + 117 + 118 return INPUT(**attr) +
    119 + 120 +
    121 -class IntegerWidget(StringWidget): +
    122 + 123 pass +
    124 + 125 +
    126 -class DoubleWidget(StringWidget): +
    127 + 128 pass +
    129 + 130 +
    131 -class DecimalWidget(StringWidget): +
    132 + 133 pass +
    134 + 135 +
    136 -class TimeWidget(StringWidget): +
    137 + 138 pass +
    139 + 140 +
    141 -class DateWidget(StringWidget): +
    142 + 143 pass +
    144 + 145 +
    146 -class DatetimeWidget(StringWidget): +
    147 + 148 pass +
    149 + 150 +
    151 -class TextWidget(FormWidget): +
    152 + 153 @staticmethod +
    154 - def widget(field, value, **attributes): +
    155 """ + 156 generates a TEXTAREA tag. + 157 + 158 see also: :meth:`FormWidget.widget` + 159 """ + 160 + 161 default = dict( + 162 value = value, + 163 ) + 164 attr = TextWidget._attributes(field, default, **attributes) + 165 + 166 return TEXTAREA(**attr) +
    167 + 168 +
    169 -class BooleanWidget(FormWidget): +
    170 + 171 @staticmethod +
    172 - def widget(field, value, **attributes): +
    173 """ + 174 generates an INPUT checkbox tag. + 175 + 176 see also: :meth:`FormWidget.widget` + 177 """ + 178 + 179 default=dict( + 180 _type='checkbox', + 181 value=value, + 182 ) + 183 attr = BooleanWidget._attributes(field, default, **attributes) + 184 + 185 return INPUT(**attr) +
    186 + 187 +
    188 -class OptionsWidget(FormWidget): +
    189 + 190 @staticmethod +
    191 - def has_options(field): +
    192 """ + 193 checks if the field has selectable options + 194 + 195 :param field: the field needing checking + 196 :returns: True if the field has options + 197 """ + 198 + 199 return hasattr(field.requires, 'options') +
    200 + 201 @staticmethod +
    202 - def widget(field, value, **attributes): +
    203 """ + 204 generates a SELECT tag, including OPTIONs (only 1 option allowed) + 205 + 206 see also: :meth:`FormWidget.widget` + 207 """ + 208 default = dict( + 209 value=value, + 210 ) + 211 attr = OptionsWidget._attributes(field, default, **attributes) + 212 + 213 requires = field.requires + 214 if not isinstance(requires, (list, tuple)): + 215 requires = [requires] + 216 if requires: + 217 if hasattr(requires[0], 'options'): + 218 options = requires[0].options() + 219 else: + 220 raise SyntaxError, 'widget cannot determine options of %s' \ + 221 % field + 222 opts = [OPTION(v, _value=k) for (k, v) in options] + 223 + 224 return SELECT(*opts, **attr) +
    225 +
    226 -class ListWidget(StringWidget): +
    227 @staticmethod +
    228 - def widget(field,value,**attributes): +
    229 _id = '%s_%s' % (field._tablename, field.name) + 230 _name = field.name + 231 if field.type=='list:integer': _class = 'integer' + 232 else: _class = 'string' + 233 items=[LI(INPUT(_id=_id,_class=_class,_name=_name,value=v,hideerror=True)) \ + 234 for v in value or ['']] + 235 script=SCRIPT(""" + 236 // from http://refactormycode.com/codes/694-expanding-input-list-using-jquery + 237 (function(){ + 238 jQuery.fn.grow_input = function() { + 239 return this.each(function() { + 240 var ul = this; + 241 jQuery(ul).find(":text").after('<a href="javascript:void(0)>+</a>').keypress(function (e) { return (e.which == 13) ? pe(ul) : true; }).next().click(function(){ pe(ul) }); + 242 }); + 243 }; + 244 function pe(ul) { + 245 var new_line = ml(ul); + 246 rel(ul); + 247 new_line.appendTo(ul); + 248 new_line.find(":text").focus(); + 249 return false; + 250 } + 251 function ml(ul) { + 252 var line = jQuery(ul).find("li:first").clone(true); + 253 line.find(':text').val(''); + 254 return line; + 255 } + 256 function rel(ul) { + 257 jQuery(ul).find("li").each(function() { + 258 var trimmed = jQuery.trim(jQuery(this.firstChild).val()); + 259 if (trimmed=='') jQuery(this).remove(); else jQuery(this.firstChild).val(trimmed); + 260 }); + 261 } + 262 })(); + 263 jQuery(document).ready(function(){jQuery('#%s_grow_input').grow_input();}); + 264 """ % _id) + 265 attributes['_id']=_id+'_grow_input' + 266 return TAG[''](UL(*items,**attributes),script) +
    267 + 268 +
    269 -class MultipleOptionsWidget(OptionsWidget): +
    270 + 271 @staticmethod +
    272 - def widget(field, value, size=5, **attributes): +
    273 """ + 274 generates a SELECT tag, including OPTIONs (multiple options allowed) + 275 + 276 see also: :meth:`FormWidget.widget` + 277 + 278 :param size: optional param (default=5) to indicate how many rows must + 279 be shown + 280 """ + 281 + 282 attributes.update(dict(_size=size, _multiple=True)) + 283 + 284 return OptionsWidget.widget(field, value, **attributes) +
    285 + 286 +
    287 -class RadioWidget(OptionsWidget): +
    288 + 289 @staticmethod +
    290 - def widget(field, value, **attributes): +
    291 """ + 292 generates a TABLE tag, including INPUT radios (only 1 option allowed) + 293 + 294 see also: :meth:`FormWidget.widget` + 295 """ + 296 + 297 attr = OptionsWidget._attributes(field, {}, **attributes) + 298 + 299 requires = field.requires + 300 if not isinstance(requires, (list, tuple)): + 301 requires = [requires] + 302 if requires: + 303 if hasattr(requires[0], 'options'): + 304 options = requires[0].options() + 305 else: + 306 raise SyntaxError, 'widget cannot determine options of %s' \ + 307 % field + 308 + 309 options = [(k, v) for k, v in options if str(v)] + 310 opts = [] + 311 cols = attributes.get('cols',1) + 312 totals = len(options) + 313 mods = totals%cols + 314 rows = totals/cols + 315 if mods: + 316 rows += 1 + 317 + 318 for r_index in range(rows): + 319 tds = [] + 320 for k, v in options[r_index*cols:(r_index+1)*cols]: + 321 tds.append(TD(INPUT(_type='radio', _name=field.name, + 322 requires=attr.get('requires',None), + 323 hideerror=True, _value=k, + 324 value=value), v)) + 325 opts.append(TR(tds)) + 326 + 327 if opts: + 328 opts[-1][0][0]['hideerror'] = False + 329 return TABLE(*opts, **attr) +
    330 + 331 +
    332 -class CheckboxesWidget(OptionsWidget): +
    333 + 334 @staticmethod +
    335 - def widget(field, value, **attributes): +
    336 """ + 337 generates a TABLE tag, including INPUT checkboxes (multiple allowed) + 338 + 339 see also: :meth:`FormWidget.widget` + 340 """ + 341 + 342 # was values = re.compile('[\w\-:]+').findall(str(value)) + 343 if isinstance(value, (list, tuple)): + 344 values = [str(v) for v in value] + 345 else: + 346 values = [str(value)] + 347 + 348 attr = OptionsWidget._attributes(field, {}, **attributes) + 349 + 350 requires = field.requires + 351 if not isinstance(requires, (list, tuple)): + 352 requires = [requires] + 353 if requires: + 354 if hasattr(requires[0], 'options'): + 355 options = requires[0].options() + 356 else: + 357 raise SyntaxError, 'widget cannot determine options of %s' \ + 358 % field + 359 + 360 options = [(k, v) for k, v in options if k != ''] + 361 opts = [] + 362 cols = attributes.get('cols', 1) + 363 totals = len(options) + 364 mods = totals % cols + 365 rows = totals / cols + 366 if mods: + 367 rows += 1 + 368 + 369 for r_index in range(rows): + 370 tds = [] + 371 for k, v in options[r_index*cols:(r_index+1)*cols]: + 372 if k in values: + 373 r_value = k + 374 else: + 375 r_value = [] + 376 tds.append(TD(INPUT(_type='checkbox', _name=field.name, + 377 requires=attr.get('requires', None), + 378 hideerror=True, _value=k, + 379 value=r_value), v)) + 380 opts.append(TR(tds)) + 381 + 382 if opts: + 383 opts[-1][0][0]['hideerror'] = False + 384 return TABLE(*opts, **attr) +
    385 + 386 +
    387 -class PasswordWidget(FormWidget): +
    388 + 389 DEFAULT_PASSWORD_DISPLAY = 8*('*') + 390 + 391 @staticmethod +
    392 - def widget(field, value, **attributes): +
    393 """ + 394 generates a INPUT password tag. + 395 If a value is present it will be shown as a number of '*', not related + 396 to the length of the actual value. + 397 + 398 see also: :meth:`FormWidget.widget` + 399 """ + 400 + 401 default=dict( + 402 _type='password', + 403 _value=(value and PasswordWidget.DEFAULT_PASSWORD_DISPLAY) or '', + 404 ) + 405 attr = PasswordWidget._attributes(field, default, **attributes) + 406 + 407 return INPUT(**attr) +
    408 + 409 +
    410 -class UploadWidget(FormWidget): +
    411 + 412 DEFAULT_WIDTH = '150px' + 413 ID_DELETE_SUFFIX = '__delete' + 414 GENERIC_DESCRIPTION = 'file' + 415 DELETE_FILE = 'delete' + 416 + 417 @staticmethod +
    418 - def widget(field, value, download_url=None, **attributes): +
    419 """ + 420 generates a INPUT file tag. + 421 + 422 Optionally provides an A link to the file, including a checkbox so + 423 the file can be deleted. + 424 All is wrapped in a DIV. + 425 + 426 see also: :meth:`FormWidget.widget` + 427 + 428 :param download_url: Optional URL to link to the file (default = None) + 429 """ + 430 + 431 default=dict( + 432 _type='file', + 433 ) + 434 attr = UploadWidget._attributes(field, default, **attributes) + 435 + 436 inp = INPUT(**attr) + 437 + 438 if download_url and value: + 439 url = download_url + '/' + value + 440 (br, image) = ('', '') + 441 if UploadWidget.is_image(value): + 442 br = BR() + 443 image = IMG(_src = url, _width = UploadWidget.DEFAULT_WIDTH) + 444 + 445 requires = attr["requires"] + 446 if requires == [] or isinstance(requires, IS_EMPTY_OR): + 447 inp = DIV(inp, '[', + 448 A(UploadWidget.GENERIC_DESCRIPTION, _href = url), + 449 '|', + 450 INPUT(_type='checkbox', + 451 _name=field.name + UploadWidget.ID_DELETE_SUFFIX), + 452 UploadWidget.DELETE_FILE, + 453 ']', br, image) + 454 else: + 455 inp = DIV(inp, '[', + 456 A(UploadWidget.GENERIC_DESCRIPTION, _href = url), + 457 ']', br, image) + 458 return inp +
    459 + 460 @staticmethod +
    461 - def represent(field, value, download_url=None): +
    462 """ + 463 how to represent the file: + 464 + 465 - with download url and if it is an image: <A href=...><IMG ...></A> + 466 - otherwise with download url: <A href=...>file</A> + 467 - otherwise: file + 468 + 469 :param field: the field + 470 :param value: the field value + 471 :param download_url: url for the file download (default = None) + 472 """ + 473 + 474 inp = UploadWidget.GENERIC_DESCRIPTION + 475 + 476 if download_url and value: + 477 url = download_url + '/' + value + 478 if UploadWidget.is_image(value): + 479 inp = IMG(_src = url, _width = UploadWidget.DEFAULT_WIDTH) + 480 inp = A(inp, _href = url) + 481 + 482 return inp +
    483 + 484 @staticmethod +
    485 - def is_image(value): +
    486 """ + 487 Tries to check if the filename provided references to an image + 488 + 489 Checking is based on filename extension. Currently recognized: + 490 gif, png, jp(e)g, bmp + 491 + 492 :param value: filename + 493 """ + 494 + 495 extension = value.split('.')[-1].lower() + 496 if extension in ['gif', 'png', 'jpg', 'jpeg', 'bmp']: + 497 return True + 498 return False +
    499 + 500 +
    501 -class AutocompleteWidget(object): +
    502 +
    503 - def __init__(self, request, field, id_field=None, db=None, + 504 orderby=None, limitby=(0,10), + 505 keyword='_autocomplete_%(fieldname)s', + 506 min_length=2): +
    507 self.request = request + 508 self.keyword = keyword % dict(fieldname=field.name) + 509 self.db = db or field._db + 510 self.orderby = orderby + 511 self.limitby = limitby + 512 self.min_length = min_length + 513 self.fields=[field] + 514 if id_field: + 515 self.is_reference = True + 516 self.fields.append(id_field) + 517 else: + 518 self.is_reference = False + 519 if hasattr(request,'application'): + 520 self.url = Url(r=request, args=request.args) + 521 self.callback() + 522 else: + 523 self.url = request +
    524 - def callback(self): +
    525 if self.keyword in self.request.vars: + 526 field = self.fields[0] + 527 rows = self.db(field.like(self.request.vars[self.keyword]+'%'))\ + 528 .select(orderby=self.orderby,limitby=self.limitby,*self.fields) + 529 if rows: + 530 if self.is_reference: + 531 id_field = self.fields[1] + 532 raise HTTP(200,SELECT(_id=self.keyword,_class='autocomplete', + 533 _size=len(rows),_multiple=(len(rows)==1), + 534 *[OPTION(s[field.name],_value=s[id_field.name], + 535 _selected=(k==0)) \ + 536 for k,s in enumerate(rows)]).xml()) + 537 else: + 538 raise HTTP(200,SELECT(_id=self.keyword,_class='autocomplete', + 539 _size=len(rows),_multiple=(len(rows)==1), + 540 *[OPTION(s[field.name], + 541 _selected=(k==0)) \ + 542 for k,s in enumerate(rows)]).xml()) + 543 else: + 544 + 545 raise HTTP(200,'') +
    546 - def __call__(self,field,value,**attributes): +
    547 default = dict( + 548 _type = 'text', + 549 value = (value!=None and str(value)) or '', + 550 ) + 551 attr = StringWidget._attributes(field, default, **attributes) + 552 div_id = self.keyword+'_div' + 553 attr['_autocomplete']='off' + 554 if self.is_reference: + 555 key2 = self.keyword+'_aux' + 556 key3 = self.keyword+'_auto' + 557 attr['_class']='string' + 558 name = attr['_name'] + 559 if 'requires' in attr: del attr['requires'] + 560 attr['_name'] = key2 + 561 value = attr['value'] + 562 record = self.db(self.fields[1]==value).select(self.fields[0]).first() + 563 attr['value'] = record and record[self.fields[0].name] + 564 attr['_onblur']="jQuery('#%(div_id)s').delay(3000).fadeOut('slow');" % \ + 565 dict(div_id=div_id,u='F'+self.keyword) + 566 attr['_onkeyup'] = "jQuery('#%(key3)s').val('');var e=event.which?event.which:event.keyCode; function %(u)s(){jQuery('#%(id)s').val(jQuery('#%(key)s :selected').text());jQuery('#%(key3)s').val(jQuery('#%(key)s').val())}; if(e==39) %(u)s(); else if(e==40) {if(jQuery('#%(key)s option:selected').next().length)jQuery('#%(key)s option:selected').attr('selected',null).next().attr('selected','selected'); %(u)s();} else if(e==38) {if(jQuery('#%(key)s option:selected').prev().length)jQuery('#%(key)s option:selected').attr('selected',null).prev().attr('selected','selected'); %(u)s();} else if(jQuery('#%(id)s').val().length>=%(min_length)s) jQuery.get('%(url)s?%(key)s='+escape(jQuery('#%(id)s').val()),function(data){if(data=='')jQuery('#%(key3)s').val('');else{jQuery('#%(id)s').next('.error').hide();jQuery('#%(div_id)s').html(data).show().focus();jQuery('#%(div_id)s select').css('width',jQuery('#%(id)s').css('width'));jQuery('#%(key3)s').val(jQuery('#%(key)s').val());jQuery('#%(key)s').change(%(u)s);jQuery('#%(key)s').click(%(u)s);};}); else jQuery('#%(div_id)s').fadeOut('slow');" % \ + 567 dict(url=self.url,min_length=self.min_length, + 568 key=self.keyword,id=attr['_id'],key2=key2,key3=key3, + 569 name=name,div_id=div_id,u='F'+self.keyword) + 570 if self.min_length==0: + 571 attr['_onfocus'] = attr['_onkeyup'] + 572 return TAG[''](INPUT(**attr),INPUT(_type='hidden',_id=key3,_value=value, + 573 _name=name,requires=field.requires), + 574 DIV(_id=div_id,_style='position:absolute;')) + 575 else: + 576 attr['_name']=field.name + 577 attr['_onblur']="jQuery('#%(div_id)s').delay(3000).fadeOut('slow');" % \ + 578 dict(div_id=div_id,u='F'+self.keyword) + 579 attr['_onkeyup'] = "var e=event.which?event.which:event.keyCode; function %(u)s(){jQuery('#%(id)s').val(jQuery('#%(key)s').val())}; if(e==39) %(u)s(); else if(e==40) {if(jQuery('#%(key)s option:selected').next().length)jQuery('#%(key)s option:selected').attr('selected',null).next().attr('selected','selected'); %(u)s();} else if(e==38) {if(jQuery('#%(key)s option:selected').prev().length)jQuery('#%(key)s option:selected').attr('selected',null).prev().attr('selected','selected'); %(u)s();} else if(jQuery('#%(id)s').val().length>=%(min_length)s) jQuery.get('%(url)s?%(key)s='+escape(jQuery('#%(id)s').val()),function(data){jQuery('#%(id)s').next('.error').hide();jQuery('#%(div_id)s').html(data).show().focus();jQuery('#%(div_id)s select').css('width',jQuery('#%(id)s').css('width'));jQuery('#%(key)s').change(%(u)s);jQuery('#%(key)s').click(%(u)s);}); else jQuery('#%(div_id)s').fadeOut('slow');" % \ + 580 dict(url=self.url,min_length=self.min_length, + 581 key=self.keyword,id=attr['_id'],div_id=div_id,u='F'+self.keyword) + 582 if self.min_length==0: + 583 attr['_onfocus'] = attr['_onkeyup'] + 584 return TAG[''](INPUT(**attr),DIV(_id=div_id,_style='position:absolute;')) +
    585 + 586 +
    587 -class SQLFORM(FORM): +
    588 + 589 """ + 590 SQLFORM is used to map a table (and a current record) into an HTML form + 591 + 592 given a SQLTable stored in db.table + 593 + 594 generates an insert form:: + 595 + 596 SQLFORM(db.table) + 597 + 598 generates an update form:: + 599 + 600 record=db.table[some_id] + 601 SQLFORM(db.table, record) + 602 + 603 generates an update with a delete button:: + 604 + 605 SQLFORM(db.table, record, deletable=True) + 606 + 607 if record is an int:: + 608 + 609 record=db.table[record] + 610 + 611 optional arguments: + 612 + 613 :param fields: a list of fields that should be placed in the form, + 614 default is all. + 615 :param labels: a dictionary with labels for each field, keys are the field + 616 names. + 617 :param col3: a dictionary with content for an optional third column + 618 (right of each field). keys are field names. + 619 :param linkto: the URL of a controller/function to access referencedby + 620 records + 621 see controller appadmin.py for examples + 622 :param upload: the URL of a controller/function to download an uploaded file + 623 see controller appadmin.py for examples + 624 + 625 any named optional attribute is passed to the <form> tag + 626 for example _class, _id, _style, _action, _method, etc. + 627 + 628 """ + 629 + 630 # usability improvements proposal by fpp - 4 May 2008 : + 631 # - correct labels (for points to field id, not field name) + 632 # - add label for delete checkbox + 633 # - add translatable label for record ID + 634 # - add third column to right of fields, populated from the col3 dict + 635 + 636 widgets = Storage(dict( + 637 string = StringWidget, + 638 text = TextWidget, + 639 password = PasswordWidget, + 640 integer = IntegerWidget, + 641 double = DoubleWidget, + 642 decimal = DecimalWidget, + 643 time = TimeWidget, + 644 date = DateWidget, + 645 datetime = DatetimeWidget, + 646 upload = UploadWidget, + 647 boolean = BooleanWidget, + 648 blob = None, + 649 options = OptionsWidget, + 650 multiple = MultipleOptionsWidget, + 651 radio = RadioWidget, + 652 checkboxes = CheckboxesWidget, + 653 autocomplete = AutocompleteWidget, + 654 list = ListWidget, + 655 )) + 656 + 657 FIELDNAME_REQUEST_DELETE = 'delete_this_record' + 658 FIELDKEY_DELETE_RECORD = 'delete_record' + 659 ID_LABEL_SUFFIX = '__label' + 660 ID_ROW_SUFFIX = '__row' + 661 +
    662 - def __init__( + 663 self, + 664 table, + 665 record = None, + 666 deletable = False, + 667 linkto = None, + 668 upload = None, + 669 fields = None, + 670 labels = None, + 671 col3 = {}, + 672 submit_button = 'Submit', + 673 delete_label = 'Check to delete:', + 674 showid = True, + 675 readonly = False, + 676 comments = True, + 677 keepopts = [], + 678 ignore_rw = False, + 679 record_id = None, + 680 formstyle = 'table3cols', + 681 buttons = ['submit'], + 682 separator = ': ', + 683 **attributes + 684 ): +
    685 """ + 686 SQLFORM(db.table, + 687 record=None, + 688 fields=['name'], + 689 labels={'name': 'Your name'}, + 690 linkto=URL(r=request, f='table/db/') + 691 """ + 692 + 693 self.ignore_rw = ignore_rw + 694 self.formstyle = formstyle + 695 nbsp = XML('&nbsp;') # Firefox2 does not display fields with blanks + 696 FORM.__init__(self, *[], **attributes) + 697 ofields = fields + 698 keyed = hasattr(table,'_primarykey') + 699 + 700 # if no fields are provided, build it from the provided table + 701 # will only use writable or readable fields, unless forced to ignore + 702 if fields == None: + 703 fields = [f.name for f in table if (ignore_rw or f.writable or f.readable) and not f.compute] + 704 self.fields = fields + 705 + 706 # make sure we have an id + 707 if self.fields[0] != table.fields[0] and \ + 708 isinstance(table,Table) and not keyed: + 709 self.fields.insert(0, table.fields[0]) + 710 + 711 self.table = table + 712 + 713 # try to retrieve the indicated record using its id + 714 # otherwise ignore it + 715 if record and isinstance(record, (int, long, str, unicode)): + 716 if not str(record).isdigit(): + 717 raise HTTP(404, "Object not found") + 718 record = table._db(table._id == record).select().first() + 719 if not record: + 720 raise HTTP(404, "Object not found") + 721 self.record = record + 722 + 723 self.record_id = record_id + 724 if keyed: + 725 if record: + 726 self.record_id = dict([(k,record[k]) for k in table._primarykey]) + 727 else: + 728 self.record_id = dict([(k,None) for k in table._primarykey]) + 729 self.field_parent = {} + 730 xfields = [] + 731 self.fields = fields + 732 self.custom = Storage() + 733 self.custom.dspval = Storage() + 734 self.custom.inpval = Storage() + 735 self.custom.label = Storage() + 736 self.custom.comment = Storage() + 737 self.custom.widget = Storage() + 738 self.custom.linkto = Storage() + 739 + 740 sep = separator or '' + 741 + 742 for fieldname in self.fields: + 743 if fieldname.find('.') >= 0: + 744 continue + 745 + 746 field = self.table[fieldname] + 747 comment = None + 748 + 749 if comments: + 750 comment = col3.get(fieldname, field.comment) + 751 if comment == None: + 752 comment = '' + 753 self.custom.comment[fieldname] = comment + 754 + 755 if labels != None and fieldname in labels: + 756 label = labels[fieldname] + 757 else: + 758 label = field.label + 759 self.custom.label[fieldname] = label + 760 + 761 field_id = '%s_%s' % (table._tablename, fieldname) + 762 + 763 label = LABEL(label, sep, _for=field_id, + 764 _id=field_id+SQLFORM.ID_LABEL_SUFFIX) + 765 + 766 row_id = field_id+SQLFORM.ID_ROW_SUFFIX + 767 if field.type == 'id': + 768 self.custom.dspval.id = nbsp + 769 self.custom.inpval.id = '' + 770 widget = '' + 771 if record: + 772 if showid and 'id' in fields and field.readable: + 773 v = record['id'] + 774 widget = SPAN(v, _id=field_id) + 775 self.custom.dspval.id = str(v) + 776 xfields.append((row_id,label, widget,comment)) + 777 self.record_id = str(record['id']) + 778 self.custom.widget.id = widget + 779 continue + 780 + 781 if readonly and not ignore_rw and not field.readable: + 782 continue + 783 + 784 if record: + 785 default = record[fieldname] + 786 else: + 787 default = field.default + 788 if isinstance(default,CALLABLETYPES): + 789 default=default() + 790 + 791 cond = readonly or \ + 792 (not ignore_rw and not field.writable and field.readable) + 793 + 794 if default and not cond: + 795 default = field.formatter(default) + 796 dspval = default + 797 inpval = default + 798 + 799 if cond: + 800 + 801 # ## if field.represent is available else + 802 # ## ignore blob and preview uploaded images + 803 # ## format everything else + 804 + 805 if field.represent: + 806 inp = represent(field,default,record) + 807 elif field.type in ['blob']: + 808 continue + 809 elif field.type == 'upload': + 810 inp = UploadWidget.represent(field, default, upload) + 811 elif field.type == 'boolean': + 812 inp = self.widgets.boolean.widget(field, default, _disabled=True) + 813 else: + 814 inp = field.formatter(default) + 815 elif field.type == 'upload': + 816 if hasattr(field, 'widget') and field.widget: + 817 inp = field.widget(field, default, upload) + 818 else: + 819 inp = self.widgets.upload.widget(field, default, upload) + 820 elif hasattr(field, 'widget') and field.widget: + 821 inp = field.widget(field, default) + 822 elif field.type == 'boolean': + 823 inp = self.widgets.boolean.widget(field, default) + 824 if default: + 825 inpval = 'checked' + 826 else: + 827 inpval = '' + 828 elif OptionsWidget.has_options(field): + 829 if not field.requires.multiple: + 830 inp = self.widgets.options.widget(field, default) + 831 else: + 832 inp = self.widgets.multiple.widget(field, default) + 833 if fieldname in keepopts: + 834 inpval = TAG[''](*inp.components) + 835 elif field.type.startswith('list:'): + 836 inp = self.widgets.list.widget(field,default) + 837 elif field.type == 'text': + 838 inp = self.widgets.text.widget(field, default) + 839 elif field.type == 'password': + 840 inp = self.widgets.password.widget(field, default) + 841 if self.record: + 842 dspval = PasswordWidget.DEFAULT_PASSWORD_DISPLAY + 843 else: + 844 dspval = '' + 845 elif field.type == 'blob': + 846 continue + 847 else: + 848 inp = self.widgets.string.widget(field, default) + 849 + 850 xfields.append((row_id,label,inp,comment)) + 851 self.custom.dspval[fieldname] = dspval or nbsp + 852 self.custom.inpval[fieldname] = inpval or '' + 853 self.custom.widget[fieldname] = inp + 854 + 855 # if a record is provided and found, as is linkto + 856 # build a link + 857 if record and linkto: + 858 db = linkto.split('/')[-1] + 859 for (rtable, rfield) in table._referenced_by: + 860 if keyed: + 861 rfld = table._db[rtable][rfield] + 862 query = urllib.quote('%s.%s==%s' % (db,rfld,record[rfld.type[10:].split('.')[1]])) + 863 else: + 864 query = urllib.quote('%s.%s==%s' % (db,table._db[rtable][rfield],record.id)) + 865 lname = olname = '%s.%s' % (rtable, rfield) + 866 if ofields and not olname in ofields: + 867 continue + 868 if labels and lname in labels: + 869 lname = labels[lname] + 870 widget = A(lname, + 871 _class='reference', + 872 _href='%s/%s?query=%s' % (linkto, rtable, query)) + 873 xfields.append((olname.replace('.', '__')+SQLFORM.ID_ROW_SUFFIX, + 874 '',widget,col3.get(olname,''))) + 875 self.custom.linkto[olname.replace('.', '__')] = widget + 876 # </block> + 877 + 878 # when deletable, add delete? checkbox + 879 self.custom.deletable = '' + 880 if record and deletable: + 881 widget = INPUT(_type='checkbox', + 882 _class='delete', + 883 _id=self.FIELDKEY_DELETE_RECORD, + 884 _name=self.FIELDNAME_REQUEST_DELETE, + 885 ) + 886 xfields.append((self.FIELDKEY_DELETE_RECORD+SQLFORM.ID_ROW_SUFFIX, + 887 LABEL( + 888 delete_label, + 889 _for=self.FIELDKEY_DELETE_RECORD, + 890 _id=self.FIELDKEY_DELETE_RECORD+SQLFORM.ID_LABEL_SUFFIX), + 891 widget, + 892 col3.get(self.FIELDKEY_DELETE_RECORD, ''))) + 893 self.custom.deletable = widget + 894 # when writable, add submit button + 895 self.custom.submit = '' + 896 if (not readonly) and ('submit' in buttons): + 897 widget = INPUT(_type='submit', + 898 _value=submit_button) + 899 xfields.append(('submit_record'+SQLFORM.ID_ROW_SUFFIX, + 900 '', widget,col3.get('submit_button', ''))) + 901 self.custom.submit = widget + 902 # if a record is provided and found + 903 # make sure it's id is stored in the form + 904 if record: + 905 if not self['hidden']: + 906 self['hidden'] = {} + 907 if not keyed: + 908 self['hidden']['id'] = record['id'] + 909 + 910 (begin, end) = self._xml() + 911 self.custom.begin = XML("<%s %s>" % (self.tag, begin)) + 912 self.custom.end = XML("%s</%s>" % (end, self.tag)) + 913 table = self.createform(xfields) + 914 self.components = [table] +
    915 +
    916 - def createform(self, xfields): +
    917 if self.formstyle == 'table3cols': + 918 table = TABLE() + 919 for id,a,b,c in xfields: + 920 td_b = self.field_parent[id] = TD(b,_class='w2p_fw') + 921 table.append(TR(TD(a,_class='w2p_fl'), + 922 td_b, + 923 TD(c,_class='w2p_fc'),_id=id)) + 924 elif self.formstyle == 'table2cols': + 925 table = TABLE() + 926 for id,a,b,c in xfields: + 927 td_b = self.field_parent[id] = TD(b,_class='w2p_fw',_colspan="2") + 928 table.append(TR(TD(a,_class='w2p_fl'), + 929 TD(c,_class='w2p_fc'),_id=id + 930 +'1',_class='even')) + 931 table.append(TR(td_b,_id=id+'2',_class='odd')) + 932 elif self.formstyle == 'divs': + 933 table = TAG['']() + 934 for id,a,b,c in xfields: + 935 div_b = self.field_parent[id] = DIV(b,_class='w2p_fw') + 936 table.append(DIV(DIV(a,_class='w2p_fl'), + 937 div_b, + 938 DIV(c,_class='w2p_fc'),_id=id)) + 939 elif self.formstyle == 'ul': + 940 table = UL() + 941 for id,a,b,c in xfields: + 942 div_b = self.field_parent[id] = DIV(b,_class='w2p_fw') + 943 table.append(LI(DIV(a,_class='w2p_fl'), + 944 div_b, + 945 DIV(c,_class='w2p_fc'),_id=id)) + 946 elif type(self.formstyle) == type(lambda:None): + 947 table = TABLE() + 948 for id,a,b,c in xfields: + 949 td_b = self.field_parent[id] = TD(b,_class='w2p_fw') + 950 newrows = self.formstyle(id,a,td_b,c) + 951 if type(newrows).__name__ != "tuple": + 952 newrows = [newrows] + 953 for newrow in newrows: + 954 table.append(newrow) + 955 else: + 956 raise RuntimeError, 'formstyle not supported' + 957 return table +
    958 + 959 +
    960 - def accepts( + 961 self, + 962 request_vars, + 963 session=None, + 964 formname='%(tablename)s/%(record_id)s', + 965 keepvalues=False, + 966 onvalidation=None, + 967 dbio=True, + 968 hideerror=False, + 969 detect_record_change=False, + 970 ): +
    971 + 972 """ + 973 similar FORM.accepts but also does insert, update or delete in DAL. + 974 but if detect_record_change == True than: + 975 form.record_changed = False (record is properly validated/submitted) + 976 form.record_changed = True (record cannot be submitted because changed) + 977 elseif detect_record_change == False than: + 978 form.record_changed = None + 979 """ + 980 + 981 if request_vars.__class__.__name__ == 'Request': + 982 request_vars = request_vars.post_vars + 983 + 984 keyed = hasattr(self.table, '_primarykey') + 985 + 986 # implement logic to detect whether record exist but has been modified + 987 # server side + 988 self.record_changed = None + 989 if detect_record_change: + 990 if self.record: + 991 self.record_changed = False + 992 serialized = '|'.join(str(self.record[k]) for k in self.table.fields()) + 993 self.record_hash = md5_hash(serialized) + 994 + 995 # logic to deal with record_id for keyed tables + 996 if self.record: + 997 if keyed: + 998 formname_id = '.'.join(str(self.record[k]) + 999 for k in self.table._primarykey +1000 if hasattr(self.record,k)) +1001 record_id = dict((k, request_vars[k]) for k in self.table._primarykey) +1002 else: +1003 (formname_id, record_id) = (self.record.id, +1004 request_vars.get('id', None)) +1005 keepvalues = True +1006 else: +1007 if keyed: +1008 formname_id = 'create' +1009 record_id = dict([(k, None) for k in self.table._primarykey]) +1010 else: +1011 (formname_id, record_id) = ('create', None) +1012 +1013 if not keyed and isinstance(record_id, (list, tuple)): +1014 record_id = record_id[0] +1015 +1016 if formname: +1017 formname = formname % dict(tablename = self.table._tablename, +1018 record_id = formname_id) +1019 +1020 # ## THIS IS FOR UNIQUE RECORDS, read IS_NOT_IN_DB +1021 +1022 for fieldname in self.fields: +1023 field = self.table[fieldname] +1024 requires = field.requires or [] +1025 if not isinstance(requires, (list, tuple)): +1026 requires = [requires] +1027 [item.set_self_id(self.record_id) for item in requires +1028 if hasattr(item, 'set_self_id') and self.record_id] +1029 +1030 # ## END +1031 +1032 fields = {} +1033 for key in self.vars: +1034 fields[key] = self.vars[key] +1035 +1036 ret = FORM.accepts( +1037 self, +1038 request_vars, +1039 session, +1040 formname, +1041 keepvalues, +1042 onvalidation, +1043 hideerror=hideerror, +1044 ) +1045 +1046 if not ret and self.record and self.errors: +1047 ### if there are errors in update mode +1048 # and some errors refers to an already uploaded file +1049 # delete error if +1050 # - user not trying to upload a new file +1051 # - there is existing file and user is not trying to delete it +1052 # this is because removing the file may not pass validation +1053 for key in self.errors.keys(): +1054 if key in self.table \ +1055 and self.table[key].type == 'upload' \ +1056 and request_vars.get(key, None) in (None, '') \ +1057 and self.record[key] \ +1058 and not key + UploadWidget.ID_DELETE_SUFFIX in request_vars: +1059 del self.errors[key] +1060 if not self.errors: +1061 ret = True +1062 +1063 requested_delete = \ +1064 request_vars.get(self.FIELDNAME_REQUEST_DELETE, False) +1065 +1066 self.custom.end = TAG[''](self.hidden_fields(), self.custom.end) +1067 +1068 auch = record_id and self.errors and requested_delete +1069 +1070 # auch is true when user tries to delete a record +1071 # that does not pass validation, yet it should be deleted +1072 +1073 if not ret and not auch: +1074 for fieldname in self.fields: +1075 field = self.table[fieldname] +1076 ### this is a workaround! widgets should always have default not None! +1077 if not field.widget and field.type.startswith('list:') and \ +1078 not OptionsWidget.has_options(field): +1079 field.widget = self.widgets.list.widget +1080 if hasattr(field, 'widget') and field.widget and fieldname in request_vars: +1081 if fieldname in self.vars: +1082 value = self.vars[fieldname] +1083 elif self.record: +1084 value = self.record[fieldname] +1085 else: +1086 value = self.table[fieldname].default +1087 row_id = '%s_%s%s' % (self.table, fieldname, SQLFORM.ID_ROW_SUFFIX) +1088 widget = field.widget(field, value) +1089 self.field_parent[row_id].components = [ widget ] +1090 if not field.type.startswith('list:'): +1091 self.field_parent[row_id]._traverse(False, hideerror) +1092 self.custom.widget[ fieldname ] = widget +1093 return ret +1094 +1095 if record_id and str(record_id) != str(self.record_id): +1096 raise SyntaxError, 'user is tampering with form\'s record_id: ' \ +1097 '%s != %s' % (record_id, self.record_id) +1098 +1099 if record_id and dbio: +1100 if keyed: +1101 self.vars.update(record_id) +1102 else: +1103 self.vars.id = self.record.id +1104 +1105 if requested_delete and self.custom.deletable: +1106 if dbio: +1107 if keyed: +1108 qry = reduce(lambda x, y: x & y, +1109 [self.table[k] == record_id[k] for k in self.table._primarykey]) +1110 else: +1111 qry = self.table._id == self.record.id +1112 self.table._db(qry).delete() +1113 self.errors.clear() +1114 for component in self.elements('input, select, textarea'): +1115 component['_disabled'] = True +1116 return True +1117 +1118 for fieldname in self.fields: +1119 if not fieldname in self.table.fields: +1120 continue +1121 +1122 if not self.ignore_rw and not self.table[fieldname].writable: +1123 ### this happens because FORM has no knowledge of writable +1124 ### and thinks that a missing boolean field is a None +1125 if self.table[fieldname].type == 'boolean' and \ +1126 self.vars.get(fieldname, True) == None: +1127 del self.vars[fieldname] +1128 continue +1129 +1130 field = self.table[fieldname] +1131 if field.type == 'id': +1132 continue +1133 if field.type == 'boolean': +1134 if self.vars.get(fieldname, False): +1135 self.vars[fieldname] = fields[fieldname] = True +1136 else: +1137 self.vars[fieldname] = fields[fieldname] = False +1138 elif field.type == 'password' and self.record\ +1139 and request_vars.get(fieldname, None) == \ +1140 PasswordWidget.DEFAULT_PASSWORD_DISPLAY: +1141 continue # do not update if password was not changed +1142 elif field.type == 'upload': +1143 f = self.vars[fieldname] +1144 fd = '%s__delete' % fieldname +1145 if f == '' or f == None: +1146 if self.vars.get(fd, False) or not self.record: +1147 fields[fieldname] = '' +1148 else: +1149 fields[fieldname] = self.record[fieldname] +1150 self.vars[fieldname] = fields[fieldname] +1151 continue +1152 elif hasattr(f, 'file'): +1153 (source_file, original_filename) = (f.file, f.filename) +1154 elif isinstance(f, (str, unicode)): +1155 ### do not know why this happens, it should not +1156 (source_file, original_filename) = \ +1157 (cStringIO.StringIO(f), 'file.txt') +1158 newfilename = field.store(source_file, original_filename) +1159 # this line is for backward compatibility only +1160 self.vars['%s_newfilename' % fieldname] = newfilename +1161 fields[fieldname] = newfilename +1162 if isinstance(field.uploadfield, str): +1163 fields[field.uploadfield] = source_file.read() +1164 # proposed by Hamdy (accept?) do we need fields at this point? +1165 self.vars[fieldname] = fields[fieldname] +1166 continue +1167 elif fieldname in self.vars: +1168 fields[fieldname] = self.vars[fieldname] +1169 elif field.default == None and field.type != 'blob': +1170 self.errors[fieldname] = 'no data' +1171 return False +1172 value = fields.get(fieldname,None) +1173 if field.type == 'list:string': +1174 if not isinstance(value, (tuple, list)): +1175 fields[fieldname] = value and [value] or [] +1176 elif isinstance(field.type,str) and field.type.startswith('list:'): +1177 if not isinstance(value, list): +1178 fields[fieldname] = [safe_int(x) for x in (value and [value] or [])] +1179 elif field.type == 'integer': +1180 if value != None: +1181 fields[fieldname] = safe_int(value) +1182 elif field.type.startswith('reference'): +1183 if value != None and isinstance(self.table, Table) and not keyed: +1184 fields[fieldname] = safe_int(value) +1185 elif field.type == 'double': +1186 if value != None: +1187 fields[fieldname] = safe_float(value) +1188 +1189 for fieldname in self.vars: +1190 if fieldname != 'id' and fieldname in self.table.fields\ +1191 and not fieldname in fields and not fieldname\ +1192 in request_vars: +1193 fields[fieldname] = self.vars[fieldname] +1194 +1195 if dbio: +1196 if 'delete_this_record' in fields: +1197 # this should never happen but seems to happen to some +1198 del fields['delete_this_record'] +1199 for field in self.table: +1200 if not field.name in fields and field.writable==False: +1201 if record_id: +1202 fields[field.name] = self.record[field.name] +1203 elif self.table[field.name].default!=None: +1204 fields[field.name] = self.table[field.name].default +1205 if keyed: +1206 if reduce(lambda x, y: x and y, record_id.values()): # if record_id +1207 if fields: +1208 qry = reduce(lambda x, y: x & y, +1209 [self.table[k] == self.record[k] for k in self.table._primarykey]) +1210 self.table._db(qry).update(**fields) +1211 else: +1212 pk = self.table.insert(**fields) +1213 if pk: +1214 self.vars.update(pk) +1215 else: +1216 ret = False +1217 else: +1218 if record_id: +1219 self.vars.id = self.record.id +1220 if fields: +1221 self.table._db(self.table._id == self.record.id).update(**fields) +1222 else: +1223 self.vars.id = self.table.insert(**fields) +1224 return ret +
    1225 +1226 @staticmethod +
    1227 - def factory(*fields, **attributes): +
    1228 """ +1229 generates a SQLFORM for the given fields. +1230 +1231 Internally will build a non-database based data model +1232 to hold the fields. +1233 """ +1234 # Define a table name, this way it can be logical to our CSS. +1235 # And if you switch from using SQLFORM to SQLFORM.factory +1236 # your same css definitions will still apply. +1237 +1238 table_name = attributes.get('table_name', 'no_table') +1239 +1240 # So it won't interfear with SQLDB.define_table +1241 if 'table_name' in attributes: +1242 del attributes['table_name'] +1243 +1244 return SQLFORM(DAL(None).define_table(table_name, *fields), **attributes) +
    1245 +1246 +
    1247 -class SQLTABLE(TABLE): +
    1248 +1249 """ +1250 given a Rows object, as returned by a db().select(), generates +1251 an html table with the rows. +1252 +1253 optional arguments: +1254 +1255 :param linkto: URL (or lambda to generate a URL) to edit individual records +1256 :param upload: URL to download uploaded files +1257 :param orderby: Add an orderby link to column headers. +1258 :param headers: dictionary of headers to headers redefinions +1259 headers can also be a string to gerenare the headers from data +1260 for now only headers="fieldname:capitalize", +1261 headers="labels" and headers=None are supported +1262 :param truncate: length at which to truncate text in table cells. +1263 Defaults to 16 characters. +1264 :param columns: a list or dict contaning the names of the columns to be shown +1265 Defaults to all +1266 +1267 Optional names attributes for passed to the <table> tag +1268 +1269 The keys of headers and columns must be of the form "tablename.fieldname" +1270 +1271 Simple linkto example:: +1272 +1273 rows = db.select(db.sometable.ALL) +1274 table = SQLTABLE(rows, linkto='someurl') +1275 +1276 This will link rows[id] to .../sometable/value_of_id +1277 +1278 +1279 More advanced linkto example:: +1280 +1281 def mylink(field, type, ref): +1282 return URL(r=request, args=[field]) +1283 +1284 rows = db.select(db.sometable.ALL) +1285 table = SQLTABLE(rows, linkto=mylink) +1286 +1287 This will link rows[id] to +1288 current_app/current_controlle/current_function/value_of_id +1289 +1290 New Implements: 24 June 2011: +1291 ----------------------------- +1292 +1293 :param selectid: The id you want to select +1294 :param renderstyle: Boolean render the style with the table +1295 +1296 :param extracolums = [{'label':A('Extra',_href='#'), +1297 'class': '', #class name of the header +1298 'width':'', #width in pixels or % +1299 'content':lambda row, rc: A('Edit',_href='edit/%s'%row.id), +1300 'selected': False #agregate class selected to this column +1301 }] +1302 +1303 +1304 :param headers = {'table.id':{'label':'Id', +1305 'class':'', #class name of the header +1306 'width':'', #width in pixels or % +1307 'truncate': 16, #truncate the content to... +1308 'selected': False #agregate class selected to this column +1309 }, +1310 'table.myfield':{'label':'My field', +1311 'class':'', #class name of the header +1312 'width':'', #width in pixels or % +1313 'truncate': 16, #truncate the content to... +1314 'selected': False #agregate class selected to this column +1315 }, +1316 } +1317 +1318 table = SQLTABLE(rows, headers=headers, extracolums=extracolums) +1319 +1320 +1321 """ +1322 +
    1323 - def __init__( +1324 self, +1325 sqlrows, +1326 linkto=None, +1327 upload=None, +1328 orderby=None, +1329 headers={}, +1330 truncate=16, +1331 columns=None, +1332 th_link='', +1333 extracolumns=None, +1334 selectid=None, +1335 renderstyle=False, +1336 **attributes +1337 ): +
    1338 +1339 TABLE.__init__(self, **attributes) +1340 self.components = [] +1341 self.attributes = attributes +1342 self.sqlrows = sqlrows +1343 (components, row) = (self.components, []) +1344 if not sqlrows: +1345 return +1346 if not columns: +1347 columns = sqlrows.colnames +1348 if headers=='fieldname:capitalize': +1349 headers = {} +1350 for c in columns: +1351 headers[c] = ' '.join([w.capitalize() for w in c.split('.')[-1].split('_')]) +1352 elif headers=='labels': +1353 headers = {} +1354 for c in columns: +1355 (t,f) = c.split('.') +1356 field = sqlrows.db[t][f] +1357 headers[c] = field.label +1358 if headers!=None: +1359 for c in columns:#new implement dict +1360 if isinstance(headers.get(c, c), dict): +1361 coldict = headers.get(c, c) +1362 attrcol = dict() +1363 if coldict['width']!="": +1364 attrcol.update(_width=coldict['width']) +1365 if coldict['class']!="": +1366 attrcol.update(_class=coldict['class']) +1367 row.append(TH(coldict['label'],**attrcol)) +1368 elif orderby: +1369 row.append(TH(A(headers.get(c, c), +1370 _href=th_link+'?orderby=' + c))) +1371 else: +1372 row.append(TH(headers.get(c, c))) +1373 +1374 if extracolumns:#new implement dict +1375 for c in extracolumns: +1376 attrcol = dict() +1377 if c['width']!="": +1378 attrcol.update(_width=c['width']) +1379 if c['class']!="": +1380 attrcol.update(_class=c['class']) +1381 row.append(TH(c['label'],**attrcol)) +1382 +1383 components.append(THEAD(TR(*row))) +1384 +1385 +1386 tbody = [] +1387 for (rc, record) in enumerate(sqlrows): +1388 row = [] +1389 if rc % 2 == 0: +1390 _class = 'even' +1391 else: +1392 _class = 'odd' +1393 +1394 if selectid!=None:#new implement +1395 if record.id==selectid: +1396 _class += ' rowselected' +1397 +1398 for colname in columns: +1399 if not table_field.match(colname): +1400 if "_extra" in record and colname in record._extra: +1401 r = record._extra[colname] +1402 row.append(TD(r)) +1403 continue +1404 else: +1405 raise KeyError("Column %s not found (SQLTABLE)" % colname) +1406 (tablename, fieldname) = colname.split('.') +1407 try: +1408 field = sqlrows.db[tablename][fieldname] +1409 except KeyError: +1410 field = None +1411 if tablename in record \ +1412 and isinstance(record,Row) \ +1413 and isinstance(record[tablename],Row): +1414 r = record[tablename][fieldname] +1415 elif fieldname in record: +1416 r = record[fieldname] +1417 else: +1418 raise SyntaxError, 'something wrong in Rows object' +1419 r_old = r +1420 if not field: +1421 pass +1422 elif linkto and field.type == 'id': +1423 try: +1424 href = linkto(r, 'table', tablename) +1425 except TypeError: +1426 href = '%s/%s/%s' % (linkto, tablename, r_old) +1427 r = A(r, _href=href) +1428 elif field.type.startswith('reference'): +1429 if linkto: +1430 ref = field.type[10:] +1431 try: +1432 href = linkto(r, 'reference', ref) +1433 except TypeError: +1434 href = '%s/%s/%s' % (linkto, ref, r_old) +1435 if ref.find('.') >= 0: +1436 tref,fref = ref.split('.') +1437 if hasattr(sqlrows.db[tref],'_primarykey'): +1438 href = '%s/%s?%s' % (linkto, tref, urllib.urlencode({fref:r})) +1439 r = A(represent(field,r,record), _href=str(href)) +1440 elif field.represent: +1441 r = represent(field,r,record) +1442 elif linkto and hasattr(field._table,'_primarykey') and fieldname in field._table._primarykey: +1443 # have to test this with multi-key tables +1444 key = urllib.urlencode(dict( [ \ +1445 ((tablename in record \ +1446 and isinstance(record, Row) \ +1447 and isinstance(record[tablename], Row)) and +1448 (k, record[tablename][k])) or (k, record[k]) \ +1449 for k in field._table._primarykey ] )) +1450 r = A(r, _href='%s/%s?%s' % (linkto, tablename, key)) +1451 elif field.type.startswith('list:'): +1452 r = represent(field,r or [],record) +1453 elif field.represent: +1454 r = represent(field,r,record) +1455 elif field.type == 'blob' and r: +1456 r = 'DATA' +1457 elif field.type == 'upload': +1458 if upload and r: +1459 r = A('file', _href='%s/%s' % (upload, r)) +1460 elif r: +1461 r = 'file' +1462 else: +1463 r = '' +1464 elif field.type in ['string','text']: +1465 r = str(field.formatter(r)) +1466 ur = unicode(r, 'utf8') +1467 if headers!={}: #new implement dict +1468 if isinstance(headers[colname],dict): +1469 if isinstance(headers[colname]['truncate'], int) \ +1470 and len(ur)>headers[colname]['truncate']: +1471 r = ur[:headers[colname]['truncate'] - 3] +1472 r = r.encode('utf8') + '...' +1473 elif truncate!=None and len(ur) > truncate: +1474 r = ur[:truncate - 3].encode('utf8') + '...' +1475 +1476 attrcol = dict()#new implement dict +1477 if headers!={}: +1478 if isinstance(headers[colname],dict): +1479 colclass=headers[colname]['class'] +1480 if headers[colname]['selected']: +1481 colclass= str(headers[colname]['class'] + " colselected").strip() +1482 if colclass!="": +1483 attrcol.update(_class=colclass) +1484 +1485 row.append(TD(r,**attrcol)) +1486 +1487 if extracolumns:#new implement dict +1488 for c in extracolumns: +1489 attrcol = dict() +1490 colclass=c['class'] +1491 if c['selected']: +1492 colclass= str(c['class'] + " colselected").strip() +1493 if colclass!="": +1494 attrcol.update(_class=colclass) +1495 contentfunc = c['content'] +1496 row.append(TD(contentfunc(record, rc),**attrcol)) +1497 +1498 tbody.append(TR(_class=_class, *row)) +1499 +1500 if renderstyle: +1501 components.append(STYLE(self.style())) +1502 +1503 components.append(TBODY(*tbody)) +
    1504 +1505 +
    1506 - def style(self): +
    1507 +1508 css = ''' +1509 table tbody tr.odd { +1510 background-color: #DFD; +1511 } +1512 table tbody tr.even { +1513 background-color: #EFE; +1514 } +1515 table tbody tr.rowselected { +1516 background-color: #FDD; +1517 } +1518 table tbody tr td.colselected { +1519 background-color: #FDD; +1520 } +1521 table tbody tr:hover { +1522 background: #DDF; +1523 } +1524 ''' +1525 +1526 return css +
    1527 +1528 form_factory = SQLFORM.factory # for backward compatibility, deprecated +1529 +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.sqlhtml.AutocompleteWidget-class.html Index: applications/examples/static/epydoc/web2py.gluon.sqlhtml.AutocompleteWidget-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.sqlhtml.AutocompleteWidget-class.html +++ applications/examples/static/epydoc/web2py.gluon.sqlhtml.AutocompleteWidget-class.html @@ -0,0 +1,294 @@ + + + + + web2py.gluon.sqlhtml.AutocompleteWidget + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module sqlhtml :: + Class AutocompleteWidget + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class AutocompleteWidget

    source code

    +
    +object --+
    +         |
    +        AutocompleteWidget
    +
    + +
    + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + request, + field, + id_field=1, + db=1, + orderby=1, + limitby=(0, 10), + keyword='_autocomplete_%(fieldname)s', + min_length=1)
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    callback(self) + source code + +
    + +
    +   + + + + + + +
    __call__(self, + field, + value, + **attributes) + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + request, + field, + id_field=1, + db=1, + orderby=1, + limitby=(0, 10), + keyword='_autocomplete_%(fieldname)s', + min_length=1) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.sqlhtml.BooleanWidget-class.html Index: applications/examples/static/epydoc/web2py.gluon.sqlhtml.BooleanWidget-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.sqlhtml.BooleanWidget-class.html +++ applications/examples/static/epydoc/web2py.gluon.sqlhtml.BooleanWidget-class.html @@ -0,0 +1,273 @@ + + + + + web2py.gluon.sqlhtml.BooleanWidget + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module sqlhtml :: + Class BooleanWidget + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class BooleanWidget

    source code

    +
    +object --+    
    +         |    
    +FormWidget --+
    +             |
    +            BooleanWidget
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __init__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +   + + + + + + +
    widget(field, + value, + **attributes)
    + generates an INPUT checkbox tag.
    + source code + +
    + +
    +

    Inherited from FormWidget (private): + _attributes +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    widget(field, + value, + **attributes) +
    Static Method +

    +
    source code  +
    + +

    generates an INPUT checkbox tag.

    + see also: :meth:`FormWidget.widget` +
    +
    Overrides: + FormWidget.widget +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.sqlhtml.CheckboxesWidget-class.html Index: applications/examples/static/epydoc/web2py.gluon.sqlhtml.CheckboxesWidget-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.sqlhtml.CheckboxesWidget-class.html +++ applications/examples/static/epydoc/web2py.gluon.sqlhtml.CheckboxesWidget-class.html @@ -0,0 +1,280 @@ + + + + + web2py.gluon.sqlhtml.CheckboxesWidget + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module sqlhtml :: + Class CheckboxesWidget + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class CheckboxesWidget

    source code

    +
    +object --+        
    +         |        
    +FormWidget --+    
    +             |    
    + OptionsWidget --+
    +                 |
    +                CheckboxesWidget
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __init__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +   + + + + + + +
    widget(field, + value, + **attributes)
    + generates a TABLE tag, including INPUT checkboxes (multiple + allowed)
    + source code + +
    + +
    +

    Inherited from OptionsWidget: + has_options +

    +

    Inherited from FormWidget (private): + _attributes +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    widget(field, + value, + **attributes) +
    Static Method +

    +
    source code  +
    + +

    generates a TABLE tag, including INPUT checkboxes (multiple + allowed)

    + see also: :meth:`FormWidget.widget` +
    +
    Overrides: + OptionsWidget.widget +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.sqlhtml.DateWidget-class.html Index: applications/examples/static/epydoc/web2py.gluon.sqlhtml.DateWidget-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.sqlhtml.DateWidget-class.html +++ applications/examples/static/epydoc/web2py.gluon.sqlhtml.DateWidget-class.html @@ -0,0 +1,214 @@ + + + + + web2py.gluon.sqlhtml.DateWidget + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module sqlhtml :: + Class DateWidget + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class DateWidget

    source code

    +
    +object --+        
    +         |        
    +FormWidget --+    
    +             |    
    +  StringWidget --+
    +                 |
    +                DateWidget
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __init__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +

    Inherited from StringWidget: + widget +

    +

    Inherited from FormWidget (private): + _attributes +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.sqlhtml.DatetimeWidget-class.html Index: applications/examples/static/epydoc/web2py.gluon.sqlhtml.DatetimeWidget-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.sqlhtml.DatetimeWidget-class.html +++ applications/examples/static/epydoc/web2py.gluon.sqlhtml.DatetimeWidget-class.html @@ -0,0 +1,214 @@ + + + + + web2py.gluon.sqlhtml.DatetimeWidget + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module sqlhtml :: + Class DatetimeWidget + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class DatetimeWidget

    source code

    +
    +object --+        
    +         |        
    +FormWidget --+    
    +             |    
    +  StringWidget --+
    +                 |
    +                DatetimeWidget
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __init__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +

    Inherited from StringWidget: + widget +

    +

    Inherited from FormWidget (private): + _attributes +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.sqlhtml.DecimalWidget-class.html Index: applications/examples/static/epydoc/web2py.gluon.sqlhtml.DecimalWidget-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.sqlhtml.DecimalWidget-class.html +++ applications/examples/static/epydoc/web2py.gluon.sqlhtml.DecimalWidget-class.html @@ -0,0 +1,214 @@ + + + + + web2py.gluon.sqlhtml.DecimalWidget + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module sqlhtml :: + Class DecimalWidget + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class DecimalWidget

    source code

    +
    +object --+        
    +         |        
    +FormWidget --+    
    +             |    
    +  StringWidget --+
    +                 |
    +                DecimalWidget
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __init__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +

    Inherited from StringWidget: + widget +

    +

    Inherited from FormWidget (private): + _attributes +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.sqlhtml.DoubleWidget-class.html Index: applications/examples/static/epydoc/web2py.gluon.sqlhtml.DoubleWidget-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.sqlhtml.DoubleWidget-class.html +++ applications/examples/static/epydoc/web2py.gluon.sqlhtml.DoubleWidget-class.html @@ -0,0 +1,214 @@ + + + + + web2py.gluon.sqlhtml.DoubleWidget + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module sqlhtml :: + Class DoubleWidget + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class DoubleWidget

    source code

    +
    +object --+        
    +         |        
    +FormWidget --+    
    +             |    
    +  StringWidget --+
    +                 |
    +                DoubleWidget
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __init__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +

    Inherited from StringWidget: + widget +

    +

    Inherited from FormWidget (private): + _attributes +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.sqlhtml.FormWidget-class.html Index: applications/examples/static/epydoc/web2py.gluon.sqlhtml.FormWidget-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.sqlhtml.FormWidget-class.html +++ applications/examples/static/epydoc/web2py.gluon.sqlhtml.FormWidget-class.html @@ -0,0 +1,331 @@ + + + + + web2py.gluon.sqlhtml.FormWidget + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module sqlhtml :: + Class FormWidget + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class FormWidget

    source code

    +
    +object --+
    +         |
    +        FormWidget
    +
    + +
    Known Subclasses:
    +
    + BooleanWidget, + OptionsWidget, + StringWidget, + PasswordWidget, + TextWidget, + UploadWidget +
    + +
    +helper for SQLFORM to generate form input fields (widget), related to + the fieldtype

    + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __init__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +   + + + + + + +
    _attributes(field, + widget_attributes, + **attributes)
    + helper to build a common set of attributes
    + source code + +
    + +
    +   + + + + + + +
    widget(field, + value, + **attributes)
    + generates the widget for the field.
    + source code + +
    + +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    _attributes(field, + widget_attributes, + **attributes) +
    Static Method +

    +
    source code  +
    + +

    helper to build a common set of attributes

    + :param field: the field involved, some attributes are derived from + this :param widget_attributes: widget related attributes :param + attributes: any other supplied attributes +
    +
    +
    +
    + +
    + +
    + + +
    +

    widget(field, + value, + **attributes) +
    Static Method +

    +
    source code  +
    + +
    +
    +generates the widget for the field.
    +
    +When serialized, will provide an INPUT tag:
    +
    +- id = tablename_fieldname
    +- class = field.type
    +- name = fieldname
    +
    +:param field: the field needing the widget
    +:param value: value
    +:param attributes: any other attributes to be applied
    +
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.sqlhtml.IntegerWidget-class.html Index: applications/examples/static/epydoc/web2py.gluon.sqlhtml.IntegerWidget-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.sqlhtml.IntegerWidget-class.html +++ applications/examples/static/epydoc/web2py.gluon.sqlhtml.IntegerWidget-class.html @@ -0,0 +1,214 @@ + + + + + web2py.gluon.sqlhtml.IntegerWidget + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module sqlhtml :: + Class IntegerWidget + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class IntegerWidget

    source code

    +
    +object --+        
    +         |        
    +FormWidget --+    
    +             |    
    +  StringWidget --+
    +                 |
    +                IntegerWidget
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __init__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +

    Inherited from StringWidget: + widget +

    +

    Inherited from FormWidget (private): + _attributes +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.sqlhtml.ListWidget-class.html Index: applications/examples/static/epydoc/web2py.gluon.sqlhtml.ListWidget-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.sqlhtml.ListWidget-class.html +++ applications/examples/static/epydoc/web2py.gluon.sqlhtml.ListWidget-class.html @@ -0,0 +1,276 @@ + + + + + web2py.gluon.sqlhtml.ListWidget + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module sqlhtml :: + Class ListWidget + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class ListWidget

    source code

    +
    +object --+        
    +         |        
    +FormWidget --+    
    +             |    
    +  StringWidget --+
    +                 |
    +                ListWidget
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __init__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +   + + + + + + +
    widget(field, + value, + **attributes)
    + generates an INPUT text tag.
    + source code + +
    + +
    +

    Inherited from FormWidget (private): + _attributes +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    widget(field, + value, + **attributes) +
    Static Method +

    +
    source code  +
    + +

    generates an INPUT text tag.

    + see also: :meth:`FormWidget.widget` +
    +
    Overrides: + StringWidget.widget +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.sqlhtml.MultipleOptionsWidget-class.html Index: applications/examples/static/epydoc/web2py.gluon.sqlhtml.MultipleOptionsWidget-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.sqlhtml.MultipleOptionsWidget-class.html +++ applications/examples/static/epydoc/web2py.gluon.sqlhtml.MultipleOptionsWidget-class.html @@ -0,0 +1,288 @@ + + + + + web2py.gluon.sqlhtml.MultipleOptionsWidget + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module sqlhtml :: + Class MultipleOptionsWidget + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class MultipleOptionsWidget

    source code

    +
    +object --+        
    +         |        
    +FormWidget --+    
    +             |    
    + OptionsWidget --+
    +                 |
    +                MultipleOptionsWidget
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __init__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +   + + + + + + +
    widget(field, + value, + size=5, + **attributes)
    + generates a SELECT tag, including OPTIONs (multiple options allowed)...
    + source code + +
    + +
    +

    Inherited from OptionsWidget: + has_options +

    +

    Inherited from FormWidget (private): + _attributes +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    widget(field, + value, + size=5, + **attributes) +
    Static Method +

    +
    source code  +
    + +
    +
    +generates a SELECT tag, including OPTIONs (multiple options allowed)
    +
    +see also: :meth:`FormWidget.widget`
    +
    +:param size: optional param (default=5) to indicate how many rows must
    +    be shown
    +
    +
    +
    +
    Overrides: + OptionsWidget.widget +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.sqlhtml.OptionsWidget-class.html Index: applications/examples/static/epydoc/web2py.gluon.sqlhtml.OptionsWidget-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.sqlhtml.OptionsWidget-class.html +++ applications/examples/static/epydoc/web2py.gluon.sqlhtml.OptionsWidget-class.html @@ -0,0 +1,320 @@ + + + + + web2py.gluon.sqlhtml.OptionsWidget + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module sqlhtml :: + Class OptionsWidget + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class OptionsWidget

    source code

    +
    +object --+    
    +         |    
    +FormWidget --+
    +             |
    +            OptionsWidget
    +
    + +
    Known Subclasses:
    +
    + CheckboxesWidget, + MultipleOptionsWidget, + RadioWidget +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __init__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +   + + + + + + +
    has_options(field)
    + checks if the field has selectable options
    + source code + +
    + +
    +   + + + + + + +
    widget(field, + value, + **attributes)
    + generates a SELECT tag, including OPTIONs (only 1 option + allowed)
    + source code + +
    + +
    +

    Inherited from FormWidget (private): + _attributes +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    has_options(field) +
    Static Method +

    +
    source code  +
    + +

    checks if the field has selectable options

    + :param field: the field needing checking :returns: True if the field + has options +
    +
    +
    +
    + +
    + +
    + + +
    +

    widget(field, + value, + **attributes) +
    Static Method +

    +
    source code  +
    + +

    generates a SELECT tag, including OPTIONs (only 1 option allowed)

    + see also: :meth:`FormWidget.widget` +
    +
    Overrides: + FormWidget.widget +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.sqlhtml.PasswordWidget-class.html Index: applications/examples/static/epydoc/web2py.gluon.sqlhtml.PasswordWidget-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.sqlhtml.PasswordWidget-class.html +++ applications/examples/static/epydoc/web2py.gluon.sqlhtml.PasswordWidget-class.html @@ -0,0 +1,299 @@ + + + + + web2py.gluon.sqlhtml.PasswordWidget + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module sqlhtml :: + Class PasswordWidget + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class PasswordWidget

    source code

    +
    +object --+    
    +         |    
    +FormWidget --+
    +             |
    +            PasswordWidget
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __init__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +   + + + + + + +
    widget(field, + value, + **attributes)
    + generates a INPUT password tag.
    + source code + +
    + +
    +

    Inherited from FormWidget (private): + _attributes +

    +
    + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + DEFAULT_PASSWORD_DISPLAY = '********' +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    widget(field, + value, + **attributes) +
    Static Method +

    +
    source code  +
    + +

    generates a INPUT password tag. If a value is present it will be shown + as a number of '*', not related to the length of the actual value.

    + see also: :meth:`FormWidget.widget` +
    +
    Overrides: + FormWidget.widget +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.sqlhtml.RadioWidget-class.html Index: applications/examples/static/epydoc/web2py.gluon.sqlhtml.RadioWidget-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.sqlhtml.RadioWidget-class.html +++ applications/examples/static/epydoc/web2py.gluon.sqlhtml.RadioWidget-class.html @@ -0,0 +1,280 @@ + + + + + web2py.gluon.sqlhtml.RadioWidget + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module sqlhtml :: + Class RadioWidget + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class RadioWidget

    source code

    +
    +object --+        
    +         |        
    +FormWidget --+    
    +             |    
    + OptionsWidget --+
    +                 |
    +                RadioWidget
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __init__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +   + + + + + + +
    widget(field, + value, + **attributes)
    + generates a TABLE tag, including INPUT radios (only 1 option + allowed)
    + source code + +
    + +
    +

    Inherited from OptionsWidget: + has_options +

    +

    Inherited from FormWidget (private): + _attributes +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    widget(field, + value, + **attributes) +
    Static Method +

    +
    source code  +
    + +

    generates a TABLE tag, including INPUT radios (only 1 option + allowed)

    + see also: :meth:`FormWidget.widget` +
    +
    Overrides: + OptionsWidget.widget +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.sqlhtml.SQLFORM-class.html Index: applications/examples/static/epydoc/web2py.gluon.sqlhtml.SQLFORM-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.sqlhtml.SQLFORM-class.html +++ applications/examples/static/epydoc/web2py.gluon.sqlhtml.SQLFORM-class.html @@ -0,0 +1,587 @@ + + + + + web2py.gluon.sqlhtml.SQLFORM + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module sqlhtml :: + Class SQLFORM + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class SQLFORM

    source code

    +
    +       object --+            
    +                |            
    +html.XmlComponent --+        
    +                    |        
    +             html.DIV --+    
    +                        |    
    +                html.FORM --+
    +                            |
    +                           SQLFORM
    +
    + +
    +
    +
    +SQLFORM is used to map a table (and a current record) into an HTML form
    +
    +given a SQLTable stored in db.table
    +
    +generates an insert form::
    +
    +    SQLFORM(db.table)
    +
    +generates an update form::
    +
    +    record=db.table[some_id]
    +    SQLFORM(db.table, record)
    +
    +generates an update with a delete button::
    +
    +    SQLFORM(db.table, record, deletable=True)
    +
    +if record is an int::
    +
    +    record=db.table[record]
    +
    +optional arguments:
    +
    +:param fields: a list of fields that should be placed in the form,
    +    default is all.
    +:param labels: a dictionary with labels for each field, keys are the field
    +    names.
    +:param col3: a dictionary with content for an optional third column
    +        (right of each field). keys are field names.
    +:param linkto: the URL of a controller/function to access referencedby
    +    records
    +        see controller appadmin.py for examples
    +:param upload: the URL of a controller/function to download an uploaded file
    +        see controller appadmin.py for examples
    +
    +any named optional attribute is passed to the <form> tag
    +        for example _class, _id, _style, _action, _method, etc.
    +
    +


    + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__()
    + SQLFORM(db.table,...
    + source code + +
    + +
    +   + + + + + + +
    createform(self, + xfields) + source code + +
    + +
    +   + + + + + + +
    accepts(self, + request_vars, + session=1, + formname='%(tablename)s/%(record_id)s', + keepvalues=True, + onvalidation=1, + dbio=True, + hideerror=True, + detect_record_change=True)
    + similar FORM.accepts but also does insert, update or delete in DAL.
    + source code + +
    + +
    +

    Inherited from html.FORM: + hidden_fields, + process, + validate, + xml +

    +

    Inherited from html.FORM (private): + _postprocessing +

    +

    Inherited from html.DIV: + __delitem__, + __getitem__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update +

    +

    Inherited from html.DIV (private): + _fixup, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +   + + + + + + +
    factory(*fields, + **attributes)
    + generates a SQLFORM for the given fields.
    + source code + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + widgets = <Storage {'multiple': <class 'web2py.gluon.sqlhtml.M... +
    +   + + FIELDNAME_REQUEST_DELETE = 'delete_this_record' +
    +   + + FIELDKEY_DELETE_RECORD = 'delete_record' +
    +   + + ID_LABEL_SUFFIX = '__label' +
    +   + + ID_ROW_SUFFIX = '__row' +
    +

    Inherited from html.FORM: + tag +

    +

    Inherited from html.DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__() +
    (Constructor) +

    +
    source code  +
    + +
    +
    +SQLFORM(db.table,
    +       record=None,
    +       fields=['name'],
    +       labels={'name': 'Your name'},
    +       linkto=URL(r=request, f='table/db/')
    +
    +
    +
    +
    Overrides: + html.FORM.__init__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    accepts(self, + request_vars, + session=1, + formname='%(tablename)s/%(record_id)s', + keepvalues=True, + onvalidation=1, + dbio=True, + hideerror=True, + detect_record_change=True) +

    +
    source code  +
    + +
    +
    +similar FORM.accepts but also does insert, update or delete in DAL.
    +but if detect_record_change == True than:
    +  form.record_changed = False (record is properly validated/submitted)
    +  form.record_changed = True (record cannot be submitted because changed)
    +elseif detect_record_change == False than:
    +  form.record_changed = None
    +
    +
    +
    +
    Overrides: + html.FORM.accepts +
    +
    +
    +
    + +
    + +
    + + +
    +

    factory(*fields, + **attributes) +
    Static Method +

    +
    source code  +
    + +

    generates a SQLFORM for the given fields.

    + Internally will build a non-database based data model to hold the + fields. +
    +
    +
    +
    +
    + + + + + + +
    + + + + + +
    Class Variable Details[hide private]
    +
    + +
    + +
    +

    widgets

    + +
    +
    +
    +
    Value:
    +
    +<Storage {'multiple': <class 'web2py.gluon.sqlhtml.MultipleOptionsWidg\
    +et'>, 'string': <class 'web2py.gluon.sqlhtml.StringWidget'>, 'text': <\
    +class 'web2py.gluon.sqlhtml.TextWidget'>, 'datetime': <class 'web2py.g\
    +luon.sqlhtml.DatetimeWidget'>, 'boolean': <class 'web2py.gluon.sqlhtml\
    +.BooleanWidget'>, 'radio': <class 'web2py.gluon.sqlhtml.RadioWidget'>,\
    + 'date': <class 'web2py.gluon.sqlhtml.DateWidget'>, 'integer': <class \
    +'web2py.gluon.sqlhtml.IntegerWidget'>, 'password': <class 'web2py.gluo\
    +n.sqlhtml.PasswordWidget'>, 'double': <class 'web2py.gluon.sqlhtml.Dou\
    +...
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.sqlhtml.SQLTABLE-class.html Index: applications/examples/static/epydoc/web2py.gluon.sqlhtml.SQLTABLE-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.sqlhtml.SQLTABLE-class.html +++ applications/examples/static/epydoc/web2py.gluon.sqlhtml.SQLTABLE-class.html @@ -0,0 +1,424 @@ + + + + + web2py.gluon.sqlhtml.SQLTABLE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module sqlhtml :: + Class SQLTABLE + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class SQLTABLE

    source code

    +
    +       object --+            
    +                |            
    +html.XmlComponent --+        
    +                    |        
    +             html.DIV --+    
    +                        |    
    +               html.TABLE --+
    +                            |
    +                           SQLTABLE
    +
    + +
    +
    +
    +given a Rows object, as returned by a db().select(), generates
    +an html table with the rows.
    +
    +optional arguments:
    +
    +:param linkto: URL (or lambda to generate a URL) to edit individual records
    +:param upload: URL to download uploaded files
    +:param orderby: Add an orderby link to column headers.
    +:param headers: dictionary of headers to headers redefinions
    +                headers can also be a string to gerenare the headers from data
    +                for now only headers="fieldname:capitalize",
    +                headers="labels" and headers=None are supported
    +:param truncate: length at which to truncate text in table cells.
    +    Defaults to 16 characters.
    +:param columns: a list or dict contaning the names of the columns to be shown
    +    Defaults to all
    +
    +Optional names attributes for passed to the <table> tag
    +
    +The keys of headers and columns must be of the form "tablename.fieldname"
    +
    +Simple linkto example::
    +
    +    rows = db.select(db.sometable.ALL)
    +    table = SQLTABLE(rows, linkto='someurl')
    +
    +This will link rows[id] to .../sometable/value_of_id
    +
    +
    +More advanced linkto example::
    +
    +    def mylink(field, type, ref):
    +        return URL(r=request, args=[field])
    +
    +    rows = db.select(db.sometable.ALL)
    +    table = SQLTABLE(rows, linkto=mylink)
    +
    +This will link rows[id] to
    +    current_app/current_controlle/current_function/value_of_id
    +    
    +New Implements: 24 June 2011:
    +-----------------------------
    +
    +:param selectid: The id you want to select
    +:param renderstyle: Boolean render the style with the table
    +
    +:param extracolums = [{'label':A('Extra',_href='#'),
    +                'class': '', #class name of the header
    +                'width':'', #width in pixels or %
    +                'content':lambda row, rc: A('Edit',_href='edit/%s'%row.id),                     
    +                'selected': False #agregate class selected to this column
    +                }]
    +                
    +                
    +:param headers = {'table.id':{'label':'Id',
    +                       'class':'', #class name of the header
    +                       'width':'', #width in pixels or %
    +                       'truncate': 16, #truncate the content to...
    +                       'selected': False #agregate class selected to this column
    +                       }, 
    +           'table.myfield':{'label':'My field',
    +                            'class':'', #class name of the header
    +                            'width':'', #width in pixels or %
    +                            'truncate': 16, #truncate the content to...
    +                            'selected': False #agregate class selected to this column
    +                            },
    +           }
    +           
    +table = SQLTABLE(rows, headers=headers, extracolums=extracolums)
    +
    +


    + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + sqlrows, + linkto=1, + upload=1, + orderby=1, + headers={}, + truncate=16, + columns=1, + th_link='', + extracolumns=1, + selectid=1, + renderstyle=True, + **attributes)
    + :param *components: any components that should be nested in this + element :param **attributes: any attributes you want to give to this + element
    + source code + +
    + +
    +   + + + + + + +
    style(self) + source code + +
    + +
    +

    Inherited from html.TABLE (private): + _fixup +

    +

    Inherited from html.DIV: + __delitem__, + __getitem__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update, + xml +

    +

    Inherited from html.DIV (private): + _postprocessing, + _setnode, + _traverse, + _validate, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +

    Inherited from html.TABLE: + tag +

    +

    Inherited from html.DIV: + regex_attr, + regex_class, + regex_id, + regex_tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + sqlrows, + linkto=1, + upload=1, + orderby=1, + headers={}, + truncate=16, + columns=1, + th_link='', + extracolumns=1, + selectid=1, + renderstyle=True, + **attributes) +
    (Constructor) +

    +
    source code  +
    + +

    :param *components: any components that should be nested in this + element :param **attributes: any attributes you want to give to this + element

    + :raises SyntaxError: when a stand alone tag receives components +
    +
    Overrides: + html.DIV.__init__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.sqlhtml.StringWidget-class.html Index: applications/examples/static/epydoc/web2py.gluon.sqlhtml.StringWidget-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.sqlhtml.StringWidget-class.html +++ applications/examples/static/epydoc/web2py.gluon.sqlhtml.StringWidget-class.html @@ -0,0 +1,284 @@ + + + + + web2py.gluon.sqlhtml.StringWidget + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module sqlhtml :: + Class StringWidget + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class StringWidget

    source code

    +
    +object --+    
    +         |    
    +FormWidget --+
    +             |
    +            StringWidget
    +
    + +
    Known Subclasses:
    +
    + DateWidget, + DatetimeWidget, + DecimalWidget, + DoubleWidget, + IntegerWidget, + ListWidget, + TimeWidget +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __init__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +   + + + + + + +
    widget(field, + value, + **attributes)
    + generates an INPUT text tag.
    + source code + +
    + +
    +

    Inherited from FormWidget (private): + _attributes +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    widget(field, + value, + **attributes) +
    Static Method +

    +
    source code  +
    + +

    generates an INPUT text tag.

    + see also: :meth:`FormWidget.widget` +
    +
    Overrides: + FormWidget.widget +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.sqlhtml.TextWidget-class.html Index: applications/examples/static/epydoc/web2py.gluon.sqlhtml.TextWidget-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.sqlhtml.TextWidget-class.html +++ applications/examples/static/epydoc/web2py.gluon.sqlhtml.TextWidget-class.html @@ -0,0 +1,273 @@ + + + + + web2py.gluon.sqlhtml.TextWidget + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module sqlhtml :: + Class TextWidget + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class TextWidget

    source code

    +
    +object --+    
    +         |    
    +FormWidget --+
    +             |
    +            TextWidget
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __init__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +   + + + + + + +
    widget(field, + value, + **attributes)
    + generates a TEXTAREA tag.
    + source code + +
    + +
    +

    Inherited from FormWidget (private): + _attributes +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    widget(field, + value, + **attributes) +
    Static Method +

    +
    source code  +
    + +

    generates a TEXTAREA tag.

    + see also: :meth:`FormWidget.widget` +
    +
    Overrides: + FormWidget.widget +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.sqlhtml.TimeWidget-class.html Index: applications/examples/static/epydoc/web2py.gluon.sqlhtml.TimeWidget-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.sqlhtml.TimeWidget-class.html +++ applications/examples/static/epydoc/web2py.gluon.sqlhtml.TimeWidget-class.html @@ -0,0 +1,214 @@ + + + + + web2py.gluon.sqlhtml.TimeWidget + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module sqlhtml :: + Class TimeWidget + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class TimeWidget

    source code

    +
    +object --+        
    +         |        
    +FormWidget --+    
    +             |    
    +  StringWidget --+
    +                 |
    +                TimeWidget
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __init__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +

    Inherited from StringWidget: + widget +

    +

    Inherited from FormWidget (private): + _attributes +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.sqlhtml.UploadWidget-class.html Index: applications/examples/static/epydoc/web2py.gluon.sqlhtml.UploadWidget-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.sqlhtml.UploadWidget-class.html +++ applications/examples/static/epydoc/web2py.gluon.sqlhtml.UploadWidget-class.html @@ -0,0 +1,426 @@ + + + + + web2py.gluon.sqlhtml.UploadWidget + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module sqlhtml :: + Class UploadWidget + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class UploadWidget

    source code

    +
    +object --+    
    +         |    
    +FormWidget --+
    +             |
    +            UploadWidget
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __init__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +   + + + + + + +
    widget(field, + value, + download_url=1, + **attributes)
    + generates a INPUT file tag.
    + source code + +
    + +
    +   + + + + + + +
    represent(field, + value, + download_url=1)
    + how to represent the file:...
    + source code + +
    + +
    +   + + + + + + +
    is_image(value)
    + Tries to check if the filename provided references to an image + +Checking is based on filename extension.
    + source code + +
    + +
    +

    Inherited from FormWidget (private): + _attributes +

    +
    + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + DEFAULT_WIDTH = '150px' +
    +   + + ID_DELETE_SUFFIX = '__delete' +
    +   + + GENERIC_DESCRIPTION = 'file' +
    +   + + DELETE_FILE = 'delete' +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    widget(field, + value, + download_url=1, + **attributes) +
    Static Method +

    +
    source code  +
    + +

    generates a INPUT file tag.

    +

    Optionally provides an A link to the file, including a checkbox so the + file can be deleted. All is wrapped in a DIV.

    +

    see also: :meth:`FormWidget.widget`

    + :param download_url: Optional URL to link to the file (default = + None) +
    +
    Overrides: + FormWidget.widget +
    +
    +
    +
    + +
    + +
    + + +
    +

    represent(field, + value, + download_url=1) +
    Static Method +

    +
    source code  +
    + +
    +
    +how to represent the file:
    +
    +- with download url and if it is an image: <A href=...><IMG ...></A>
    +- otherwise with download url: <A href=...>file</A>
    +- otherwise: file
    +
    +:param field: the field
    +:param value: the field value
    +:param download_url: url for the file download (default = None)
    +
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    is_image(value) +
    Static Method +

    +
    source code  +
    + +
    +
    +Tries to check if the filename provided references to an image
    +
    +Checking is based on filename extension. Currently recognized:
    +   gif, png, jp(e)g, bmp
    +
    +:param value: filename
    +
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.storage-module.html Index: applications/examples/static/epydoc/web2py.gluon.storage-module.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.storage-module.html +++ applications/examples/static/epydoc/web2py.gluon.storage-module.html @@ -0,0 +1,238 @@ + + + + + web2py.gluon.storage + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module storage + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Module storage

    source code

    +
    +
    +This file is part of the web2py Web Framework
    +Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
    +License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
    +
    +Provides:
    +
    +- List; like list but returns None instead of IndexOutOfBounds
    +- Storage; like dictionary allowing also for `obj.foo` for `obj['foo']`
    +
    +


    + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Classes[hide private]
    +
    +   + + List
    + Like a regular python list but a[i] if i is out of bounds return + None instead of IndexOutOfBounds +
    +   + + Storage
    + A Storage object is like a dictionary except `obj.foo` can be + used in addition to `obj['foo']`. +
    +   + + StorageList
    + like Storage but missing elements default to [] instead of + None +
    +   + + Settings +
    +   + + Messages +
    + + + + + + + + + + + + +
    + + + + + +
    Functions[hide private]
    +
    +   + + + + + + +
    load_storage(filename) + source code + +
    + +
    +   + + + + + + +
    save_storage(storage, + filename) + source code + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.storage-pysrc.html Index: applications/examples/static/epydoc/web2py.gluon.storage-pysrc.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.storage-pysrc.html +++ applications/examples/static/epydoc/web2py.gluon.storage-pysrc.html @@ -0,0 +1,361 @@ + + + + + web2py.gluon.storage + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module storage + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Module web2py.gluon.storage

    +
    +  1  #!/usr/bin/env python 
    +  2  # -*- coding: utf-8 -*- 
    +  3   
    +  4  """ 
    +  5  This file is part of the web2py Web Framework 
    +  6  Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> 
    +  7  License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) 
    +  8   
    +  9  Provides: 
    + 10   
    + 11  - List; like list but returns None instead of IndexOutOfBounds 
    + 12  - Storage; like dictionary allowing also for `obj.foo` for `obj['foo']` 
    + 13  """ 
    + 14   
    + 15  import cPickle 
    + 16  import portalocker 
    + 17   
    + 18  __all__ = ['List', 'Storage', 'Settings', 'Messages', 
    + 19             'StorageList', 'load_storage', 'save_storage'] 
    + 20   
    + 21   
    +
    22 -class List(list): +
    23 """ + 24 Like a regular python list but a[i] if i is out of bounds return None + 25 instead of IndexOutOfBounds + 26 """ + 27 +
    28 - def __call__(self, i, default=None): +
    29 if 0<=i<len(self): + 30 return self[i] + 31 else: + 32 return default +
    33 +
    34 -class Storage(dict): +
    35 + 36 """ + 37 A Storage object is like a dictionary except `obj.foo` can be used + 38 in addition to `obj['foo']`. + 39 + 40 >>> o = Storage(a=1) + 41 >>> print o.a + 42 1 + 43 + 44 >>> o['a'] + 45 1 + 46 + 47 >>> o.a = 2 + 48 >>> print o['a'] + 49 2 + 50 + 51 >>> del o.a + 52 >>> print o.a + 53 None + 54 + 55 """ + 56 +
    57 - def __getattr__(self, key): +
    58 if key in self: + 59 return self[key] + 60 else: + 61 return None +
    62 +
    63 - def __setattr__(self, key, value): +
    64 if value == None: + 65 if key in self: + 66 del self[key] + 67 else: + 68 self[key] = value +
    69 +
    70 - def __delattr__(self, key): +
    71 if key in self: + 72 del self[key] + 73 else: + 74 raise AttributeError, "missing key=%s" % key +
    75 +
    76 - def __repr__(self): +
    77 return '<Storage ' + dict.__repr__(self) + '>' +
    78 +
    79 - def __getstate__(self): +
    80 return dict(self) +
    81 +
    82 - def __setstate__(self, value): +
    83 for (k, v) in value.items(): + 84 self[k] = v +
    85 +
    86 - def getlist(self, key): +
    87 """Return a Storage value as a list. + 88 + 89 If the value is a list it will be returned as-is. + 90 If object is None, an empty list will be returned. + 91 Otherwise, [value] will be returned. + 92 + 93 Example output for a query string of ?x=abc&y=abc&y=def + 94 >>> request = Storage() + 95 >>> request.vars = Storage() + 96 >>> request.vars.x = 'abc' + 97 >>> request.vars.y = ['abc', 'def'] + 98 >>> request.vars.getlist('x') + 99 ['abc'] +100 >>> request.vars.getlist('y') +101 ['abc', 'def'] +102 >>> request.vars.getlist('z') +103 [] +104 +105 """ +106 value = self.get(key, None) +107 if isinstance(value, (list, tuple)): +108 return value +109 elif value is None: +110 return [] +111 return [value] +
    112 +
    113 - def getfirst(self, key): +
    114 """Return the first or only value when given a request.vars-style key. +115 +116 If the value is a list, its first item will be returned; +117 otherwise, the value will be returned as-is. +118 +119 Example output for a query string of ?x=abc&y=abc&y=def +120 >>> request = Storage() +121 >>> request.vars = Storage() +122 >>> request.vars.x = 'abc' +123 >>> request.vars.y = ['abc', 'def'] +124 >>> request.vars.getfirst('x') +125 'abc' +126 >>> request.vars.getfirst('y') +127 'abc' +128 >>> request.vars.getfirst('z') +129 +130 """ +131 value = self.getlist(key) +132 if len(value): +133 return value[0] +134 return None +
    135 +
    136 - def getlast(self, key): +
    137 """Returns the last or only single value when given a request.vars-style key. +138 +139 If the value is a list, the last item will be returned; +140 otherwise, the value will be returned as-is. +141 +142 Simulated output with a query string of ?x=abc&y=abc&y=def +143 >>> request = Storage() +144 >>> request.vars = Storage() +145 >>> request.vars.x = 'abc' +146 >>> request.vars.y = ['abc', 'def'] +147 >>> request.vars.getlast('x') +148 'abc' +149 >>> request.vars.getlast('y') +150 'def' +151 >>> request.vars.getlast('z') +152 +153 """ +154 value = self.getlist(key) +155 if len(value): +156 return value[-1] +157 return None +
    158 +
    159 -class StorageList(Storage): +
    160 """ +161 like Storage but missing elements default to [] instead of None +162 """ +
    163 - def __getattr__(self, key): +
    164 if key in self: +165 return self[key] +166 else: +167 self[key] = [] +168 return self[key] +
    169 +
    170 -def load_storage(filename): +
    171 fp = open(filename, 'rb') +172 try: +173 portalocker.lock(fp, portalocker.LOCK_EX) +174 storage = cPickle.load(fp) +175 portalocker.unlock(fp) +176 finally: +177 fp.close() +178 return Storage(storage) +
    179 +180 +
    181 -def save_storage(storage, filename): +
    182 fp = open(filename, 'wb') +183 try: +184 portalocker.lock(fp, portalocker.LOCK_EX) +185 cPickle.dump(dict(storage), fp) +186 portalocker.unlock(fp) +187 finally: +188 fp.close() +
    189 +190 +
    191 -class Settings(Storage): +
    192 +
    193 - def __setattr__(self, key, value): +
    194 if key != 'lock_keys' and self.get('lock_keys', None)\ +195 and not key in self: +196 raise SyntaxError, 'setting key \'%s\' does not exist' % key +197 if key != 'lock_values' and self.get('lock_values', None): +198 raise SyntaxError, 'setting value cannot be changed: %s' % key +199 self[key] = value +
    200 +201 +
    202 -class Messages(Storage): +
    203 +
    204 - def __init__(self, T): +
    205 self['T'] = T +
    206 +
    207 - def __setattr__(self, key, value): +
    208 if key != 'lock_keys' and self.get('lock_keys', None)\ +209 and not key in self: +210 raise SyntaxError, 'setting key \'%s\' does not exist' % key +211 if key != 'lock_values' and self.get('lock_values', None): +212 raise SyntaxError, 'setting value cannot be changed: %s' % key +213 self[key] = value +
    214 +
    215 - def __getattr__(self, key): +
    216 value = self[key] +217 if isinstance(value, str): +218 return str(self['T'](value)) +219 return value +
    220 +221 if __name__ == '__main__': +222 import doctest +223 doctest.testmod() +224 +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.storage.List-class.html Index: applications/examples/static/epydoc/web2py.gluon.storage.List-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.storage.List-class.html +++ applications/examples/static/epydoc/web2py.gluon.storage.List-class.html @@ -0,0 +1,237 @@ + + + + + web2py.gluon.storage.List + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module storage :: + Class List + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class List

    source code

    +
    +object --+    
    +         |    
    +      list --+
    +             |
    +            List
    +
    + +
    +Like a regular python list but a[i] if i is out of bounds return None + instead of IndexOutOfBounds

    + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __call__(self, + i, + default=1) + source code + +
    + +
    +

    Inherited from list: + __add__, + __contains__, + __delitem__, + __delslice__, + __eq__, + __ge__, + __getattribute__, + __getitem__, + __getslice__, + __gt__, + __hash__, + __iadd__, + __imul__, + __init__, + __iter__, + __le__, + __len__, + __lt__, + __mul__, + __ne__, + __new__, + __repr__, + __reversed__, + __rmul__, + __setitem__, + __setslice__, + append, + count, + extend, + index, + insert, + pop, + remove, + reverse, + sort +

    +

    Inherited from object: + __delattr__, + __reduce__, + __reduce_ex__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.storage.Messages-class.html Index: applications/examples/static/epydoc/web2py.gluon.storage.Messages-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.storage.Messages-class.html +++ applications/examples/static/epydoc/web2py.gluon.storage.Messages-class.html @@ -0,0 +1,373 @@ + + + + + web2py.gluon.storage.Messages + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module storage :: + Class Messages + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class Messages

    source code

    +
    +object --+        
    +         |        
    +      dict --+    
    +             |    
    +       Storage --+
    +                 |
    +                Messages
    +
    + +
    + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + T)
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    __setattr__(self, + key, + value) + source code + +
    + +
    +   + + + + + + +
    __getattr__(self, + key) + source code + +
    + +
    +

    Inherited from Storage: + __delattr__, + __getstate__, + __repr__, + __setstate__, + getfirst, + getlast, + getlist +

    +

    Inherited from dict: + __cmp__, + __contains__, + __delitem__, + __eq__, + __ge__, + __getattribute__, + __getitem__, + __gt__, + __hash__, + __iter__, + __le__, + __len__, + __lt__, + __ne__, + __new__, + __setitem__, + clear, + copy, + fromkeys, + get, + has_key, + items, + iteritems, + iterkeys, + itervalues, + keys, + pop, + popitem, + setdefault, + update, + values +

    +

    Inherited from object: + __reduce__, + __reduce_ex__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + T) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Returns:
    +
    +new empty dictionary
    +
    +
    +
    Overrides: + dict.__init__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    __setattr__(self, + key, + value) +

    +
    source code  +
    + + +
    +
    Overrides: + Storage.__setattr__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    __getattr__(self, + key) +
    (Qualification operator) +

    +
    source code  +
    + + +
    +
    Overrides: + Storage.__getattr__ +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.storage.Settings-class.html Index: applications/examples/static/epydoc/web2py.gluon.storage.Settings-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.storage.Settings-class.html +++ applications/examples/static/epydoc/web2py.gluon.storage.Settings-class.html @@ -0,0 +1,284 @@ + + + + + web2py.gluon.storage.Settings + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module storage :: + Class Settings + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class Settings

    source code

    +
    +object --+        
    +         |        
    +      dict --+    
    +             |    
    +       Storage --+
    +                 |
    +                Settings
    +
    + +
    + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __setattr__(self, + key, + value) + source code + +
    + +
    +

    Inherited from Storage: + __delattr__, + __getattr__, + __getstate__, + __repr__, + __setstate__, + getfirst, + getlast, + getlist +

    +

    Inherited from dict: + __cmp__, + __contains__, + __delitem__, + __eq__, + __ge__, + __getattribute__, + __getitem__, + __gt__, + __hash__, + __init__, + __iter__, + __le__, + __len__, + __lt__, + __ne__, + __new__, + __setitem__, + clear, + copy, + fromkeys, + get, + has_key, + items, + iteritems, + iterkeys, + itervalues, + keys, + pop, + popitem, + setdefault, + update, + values +

    +

    Inherited from object: + __reduce__, + __reduce_ex__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __setattr__(self, + key, + value) +

    +
    source code  +
    + + +
    +
    Overrides: + Storage.__setattr__ +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.storage.Storage-class.html Index: applications/examples/static/epydoc/web2py.gluon.storage.Storage-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.storage.Storage-class.html +++ applications/examples/static/epydoc/web2py.gluon.storage.Storage-class.html @@ -0,0 +1,578 @@ + + + + + web2py.gluon.storage.Storage + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module storage :: + Class Storage + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class Storage

    source code

    +
    +object --+    
    +         |    
    +      dict --+
    +             |
    +            Storage
    +
    + +
    Known Subclasses:
    +
    + globals.Request, + globals.Response, + globals.Session, + Messages, + Settings, + StorageList, + restricted.TicketStorage +
    + +
    +A Storage object is like a dictionary except `obj.foo` can be used in + addition to `obj['foo']`. +
    +>>> o = Storage(a=1)
    +>>> print o.a
    +1
    +
    +>>> o['a']
    +1
    +
    +>>> o.a = 2
    +>>> print o['a']
    +2
    +
    +>>> del o.a
    +>>> print o.a
    +None


    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __delattr__(self, + key)
    + x.__delattr__('name') <==> del x.name
    + source code + +
    + +
    +   + + + + + + +
    __getattr__(self, + key) + source code + +
    + +
    +   + + + + + + +
    __getstate__(self) + source code + +
    + +
    +   + + + + + + +
    __repr__(self)
    + repr(x)
    + source code + +
    + +
    +   + + + + + + +
    __setattr__(self, + key, + value)
    + x.__setattr__('name', value) <==> x.name = value
    + source code + +
    + +
    +   + + + + + + +
    __setstate__(self, + value) + source code + +
    + +
    +   + + + + + + +
    getfirst(self, + key)
    + Return the first or only value when given a request.vars-style + key.
    + source code + +
    + +
    +   + + + + + + +
    getlast(self, + key)
    + Returns the last or only single value when given a + request.vars-style key.
    + source code + +
    + +
    +   + + + + + + +
    getlist(self, + key)
    + Return a Storage value as a list.
    + source code + +
    + +
    +

    Inherited from dict: + __cmp__, + __contains__, + __delitem__, + __eq__, + __ge__, + __getattribute__, + __getitem__, + __gt__, + __hash__, + __init__, + __iter__, + __le__, + __len__, + __lt__, + __ne__, + __new__, + __setitem__, + clear, + copy, + fromkeys, + get, + has_key, + items, + iteritems, + iterkeys, + itervalues, + keys, + pop, + popitem, + setdefault, + update, + values +

    +

    Inherited from object: + __reduce__, + __reduce_ex__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __delattr__(self, + key) +

    +
    source code  +
    + + x.__delattr__('name') <==> del x.name +
    +
    Overrides: + object.__delattr__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    __repr__(self) +
    (Representation operator) +

    +
    source code  +
    + + repr(x) +
    +
    Overrides: + dict.__repr__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    __setattr__(self, + key, + value) +

    +
    source code  +
    + + x.__setattr__('name', value) <==> x.name = value +
    +
    Overrides: + object.__setattr__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    getfirst(self, + key) +

    +
    source code  +
    + +

    Return the first or only value when given a request.vars-style + key.

    +

    If the value is a list, its first item will be returned; otherwise, + the value will be returned as-is.

    + Example output for a query string of ?x=abc&y=abc&y=def + >>> request = Storage() >>> request.vars = Storage() + >>> request.vars.x = 'abc' >>> request.vars.y = ['abc', + 'def'] >>> request.vars.getfirst('x') 'abc' >>> + request.vars.getfirst('y') 'abc' >>> + request.vars.getfirst('z') +
    +
    +
    +
    + +
    + +
    + + +
    +

    getlast(self, + key) +

    +
    source code  +
    + +

    Returns the last or only single value when given a request.vars-style + key.

    +

    If the value is a list, the last item will be returned; otherwise, the + value will be returned as-is.

    + Simulated output with a query string of ?x=abc&y=abc&y=def + >>> request = Storage() >>> request.vars = Storage() + >>> request.vars.x = 'abc' >>> request.vars.y = ['abc', + 'def'] >>> request.vars.getlast('x') 'abc' >>> + request.vars.getlast('y') 'def' >>> + request.vars.getlast('z') +
    +
    +
    +
    + +
    + +
    + + +
    +

    getlist(self, + key) +

    +
    source code  +
    + +

    Return a Storage value as a list.

    +

    If the value is a list it will be returned as-is. If object is None, + an empty list will be returned. Otherwise, [value] will be returned.

    + Example output for a query string of ?x=abc&y=abc&y=def + >>> request = Storage() >>> request.vars = Storage() + >>> request.vars.x = 'abc' >>> request.vars.y = ['abc', + 'def'] >>> request.vars.getlist('x') ['abc'] >>> + request.vars.getlist('y') ['abc', 'def'] >>> + request.vars.getlist('z') [] +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.storage.StorageList-class.html Index: applications/examples/static/epydoc/web2py.gluon.storage.StorageList-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.storage.StorageList-class.html +++ applications/examples/static/epydoc/web2py.gluon.storage.StorageList-class.html @@ -0,0 +1,285 @@ + + + + + web2py.gluon.storage.StorageList + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module storage :: + Class StorageList + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class StorageList

    source code

    +
    +object --+        
    +         |        
    +      dict --+    
    +             |    
    +       Storage --+
    +                 |
    +                StorageList
    +
    + +
    +like Storage but missing elements default to [] instead of None

    + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __getattr__(self, + key) + source code + +
    + +
    +

    Inherited from Storage: + __delattr__, + __getstate__, + __repr__, + __setattr__, + __setstate__, + getfirst, + getlast, + getlist +

    +

    Inherited from dict: + __cmp__, + __contains__, + __delitem__, + __eq__, + __ge__, + __getattribute__, + __getitem__, + __gt__, + __hash__, + __init__, + __iter__, + __le__, + __len__, + __lt__, + __ne__, + __new__, + __setitem__, + clear, + copy, + fromkeys, + get, + has_key, + items, + iteritems, + iterkeys, + itervalues, + keys, + pop, + popitem, + setdefault, + update, + values +

    +

    Inherited from object: + __reduce__, + __reduce_ex__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __getattr__(self, + key) +
    (Qualification operator) +

    +
    source code  +
    + + +
    +
    Overrides: + Storage.__getattr__ +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.streamer-module.html Index: applications/examples/static/epydoc/web2py.gluon.streamer-module.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.streamer-module.html +++ applications/examples/static/epydoc/web2py.gluon.streamer-module.html @@ -0,0 +1,214 @@ + + + + + web2py.gluon.streamer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module streamer + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Module streamer

    source code

    +This file is part of the web2py Web Framework Copyrighted by Massimo + Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)

    + + + + + + + + + + + + + +
    + + + + + +
    Functions[hide private]
    +
    +   + + + + + + +
    streamer(stream, + chunk_size=65536, + bytes=1) + source code + +
    + +
    +   + + + + + + +
    stream_file_or_304_or_206(static_file, + chunk_size=65536, + request=1, + headers={}, + error_message=1) + source code + +
    + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    Variables[hide private]
    +
    +   + + regex_start_range = re.compile(r'\d+(?=-)') +
    +   + + regex_stop_range = re.compile(r'(?<=-)\d+') +
    +   + + DEFAULT_CHUNK_SIZE = 65536 +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.streamer-pysrc.html Index: applications/examples/static/epydoc/web2py.gluon.streamer-pysrc.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.streamer-pysrc.html +++ applications/examples/static/epydoc/web2py.gluon.streamer-pysrc.html @@ -0,0 +1,245 @@ + + + + + web2py.gluon.streamer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module streamer + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Module web2py.gluon.streamer

    +
    +  1  #!/usr/bin/env python 
    +  2  # -*- coding: utf-8 -*- 
    +  3   
    +  4  """ 
    +  5  This file is part of the web2py Web Framework 
    +  6  Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> 
    +  7  License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) 
    +  8  """ 
    +  9   
    + 10  import os 
    + 11  import stat 
    + 12  import time 
    + 13  import re 
    + 14  import errno 
    + 15  import rewrite 
    + 16  from http import HTTP 
    + 17  from contenttype import contenttype 
    + 18   
    + 19   
    + 20  regex_start_range = re.compile('\d+(?=\-)') 
    + 21  regex_stop_range = re.compile('(?<=\-)\d+') 
    + 22   
    + 23  DEFAULT_CHUNK_SIZE = 64*1024 
    + 24   
    +
    25 -def streamer(stream, chunk_size = DEFAULT_CHUNK_SIZE, bytes = None): +
    26 offset = 0 + 27 while bytes == None or offset < bytes: + 28 if bytes != None and bytes - offset < chunk_size: + 29 chunk_size = bytes - offset + 30 data = stream.read(chunk_size) + 31 length = len(data) + 32 if not length: + 33 break + 34 else: + 35 yield data + 36 if length < chunk_size: + 37 break + 38 offset += length + 39 stream.close() +
    40 +
    41 -def stream_file_or_304_or_206( + 42 static_file, + 43 chunk_size = DEFAULT_CHUNK_SIZE, + 44 request = None, + 45 headers = {}, + 46 error_message = None, + 47 ): +
    48 if error_message is None: + 49 error_message = rewrite.thread.routes.error_message % 'invalid request' + 50 try: + 51 fp = open(static_file) + 52 except IOError, e: + 53 if e[0] == errno.EISDIR: + 54 raise HTTP(403, error_message, web2py_error='file is a directory') + 55 elif e[0] == errno.EACCES: + 56 raise HTTP(403, error_message, web2py_error='inaccessible file') + 57 else: + 58 raise HTTP(404, error_message, web2py_error='invalid file') + 59 else: + 60 fp.close() + 61 stat_file = os.stat(static_file) + 62 fsize = stat_file[stat.ST_SIZE] + 63 mtime = time.strftime('%a, %d %b %Y %H:%M:%S GMT', + 64 time.gmtime(stat_file[stat.ST_MTIME])) + 65 headers['Content-Type'] = contenttype(static_file) + 66 headers['Last-Modified'] = mtime + 67 headers['Pragma'] = 'cache' + 68 headers['Cache-Control'] = 'private' + 69 + 70 if request and request.env.http_if_modified_since == mtime: + 71 raise HTTP(304, **{'Content-Type': headers['Content-Type']}) + 72 + 73 elif request and request.env.http_range: + 74 start_items = regex_start_range.findall(request.env.http_range) + 75 if not start_items: + 76 start_items = [0] + 77 stop_items = regex_stop_range.findall(request.env.http_range) + 78 if not stop_items or int(stop_items[0]) > fsize - 1: + 79 stop_items = [fsize - 1] + 80 part = (int(start_items[0]), int(stop_items[0]), fsize) + 81 bytes = part[1] - part[0] + 1 + 82 try: + 83 stream = open(static_file, 'rb') + 84 except IOError, e: + 85 if e[0] in (errno.EISDIR, errno.EACCES): + 86 raise HTTP(403) + 87 else: + 88 raise HTTP(404) + 89 stream.seek(part[0]) + 90 headers['Content-Range'] = 'bytes %i-%i/%i' % part + 91 headers['Content-Length'] = '%i' % bytes + 92 status = 206 + 93 else: + 94 try: + 95 stream = open(static_file, 'rb') + 96 except IOError, e: + 97 if e[0] in (errno.EISDIR, errno.EACCES): + 98 raise HTTP(403) + 99 else: +100 raise HTTP(404) +101 headers['Content-Length'] = fsize +102 bytes = None +103 status = 200 +104 if request and request.env.web2py_use_wsgi_file_wrapper: +105 wrapped = request.env.wsgi_file_wrapper(stream, chunk_size) +106 else: +107 wrapped = streamer(stream, chunk_size=chunk_size, bytes=bytes) +108 raise HTTP(status, wrapped, **headers) +
    109 +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.template-module.html Index: applications/examples/static/epydoc/web2py.gluon.template-module.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.template-module.html +++ applications/examples/static/epydoc/web2py.gluon.template-module.html @@ -0,0 +1,304 @@ + + + + + web2py.gluon.template + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module template + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Module template

    source code

    +
    +
    +This file is part of the web2py Web Framework (Copyrighted, 2007-2011).
    +License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
    +
    +Author: Thadeus Burgess
    +
    +Contributors:
    +
    +- Thank you to Massimo Di Pierro for creating the original gluon/template.py
    +- Thank you to Jonathan Lundell for extensively testing the regex on Jython.
    +- Thank you to Limodou (creater of uliweb) who inspired the block-element support for web2py.
    +
    +


    + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Classes[hide private]
    +
    +   + + Node
    + Basic Container Object +
    +   + + SuperNode +
    +   + + BlockNode
    + Block Container. +
    +   + + Content
    + Parent Container -- Used as the root level BlockNode. +
    +   + + TemplateParser +
    + + + + + + + + + + + + + + + +
    + + + + + +
    Functions[hide private]
    +
    +   + + + + + + +
    parse_template(filename, + path='views/', + context={}, + lexers={}, + delimiters=('{{', '}}'))
    + filename can be a view filename in the views folder or an input + stream path is the path of a views folder context is a dictionary of + symbols used to render the template
    + source code + +
    + +
    +   + + + + + + +
    get_parsed(text)
    + Returns the indented python code of text.
    + source code + +
    + +
    +   + + + + + + +
    render(content='hello world', + stream=1, + filename=1, + path=1, + context={}, + lexers={}, + delimiters=('{{', '}}')) + source code + +
    + +
    + + + + + + +
    + + + + + +
    Function Details[hide private]
    +
    + +
    + +
    + + +
    +

    get_parsed(text) +

    +
    source code  +
    + + Returns the indented python code of text. Useful for unit testing. +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.template-pysrc.html Index: applications/examples/static/epydoc/web2py.gluon.template-pysrc.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.template-pysrc.html +++ applications/examples/static/epydoc/web2py.gluon.template-pysrc.html @@ -0,0 +1,1234 @@ + + + + + web2py.gluon.template + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module template + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Module web2py.gluon.template

    +
    +  1  #!/usr/bin/env python 
    +  2  # -*- coding: utf-8 -*- 
    +  3   
    +  4  """ 
    +  5  This file is part of the web2py Web Framework (Copyrighted, 2007-2011). 
    +  6  License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) 
    +  7   
    +  8  Author: Thadeus Burgess 
    +  9   
    + 10  Contributors: 
    + 11   
    + 12  - Thank you to Massimo Di Pierro for creating the original gluon/template.py 
    + 13  - Thank you to Jonathan Lundell for extensively testing the regex on Jython. 
    + 14  - Thank you to Limodou (creater of uliweb) who inspired the block-element support for web2py. 
    + 15  """ 
    + 16   
    + 17  import os 
    + 18  import re 
    + 19  import cgi 
    + 20  import cStringIO 
    + 21  import logging 
    + 22  try: 
    + 23      from restricted import RestrictedError 
    + 24  except: 
    +
    25 - def RestrictedError(a,b,c): +
    26 logging.error(str(a)+':'+str(b)+':'+str(c)) + 27 return RuntimeError +
    28 +
    29 -class Node(object): +
    30 """ + 31 Basic Container Object + 32 """ +
    33 - def __init__(self, value = None, pre_extend = False): +
    34 self.value = value + 35 self.pre_extend = pre_extend +
    36 +
    37 - def __str__(self): +
    38 return str(self.value) +
    39 +
    40 -class SuperNode(Node): +
    41 - def __init__(self, name = '', pre_extend = False): +
    42 self.name = name + 43 self.value = None + 44 self.pre_extend = pre_extend +
    45 +
    46 - def __str__(self): +
    47 if self.value: + 48 return str(self.value) + 49 else: + 50 raise SyntaxError("Undefined parent block ``%s``. \n" % self.name + \ + 51 "You must define a block before referencing it.\nMake sure you have not left out an ``{{end}}`` tag." ) +
    52 +
    53 - def __repr__(self): +
    54 return "%s->%s" % (self.name, self.value) +
    55 +
    56 -class BlockNode(Node): +
    57 """ + 58 Block Container. + 59 + 60 This Node can contain other Nodes and will render in a hierarchical order + 61 of when nodes were added. + 62 + 63 ie:: + 64 + 65 {{ block test }} + 66 This is default block test + 67 {{ end }} + 68 """ +
    69 - def __init__(self, name = '', pre_extend = False, delimiters = ('{{','}}')): +
    70 """ + 71 name - Name of this Node. + 72 """ + 73 self.nodes = [] + 74 self.name = name + 75 self.pre_extend = pre_extend + 76 self.left, self.right = delimiters +
    77 +
    78 - def __repr__(self): +
    79 lines = ['%sblock %s%s' % (self.left,self.name,self.right)] + 80 for node in self.nodes: + 81 lines.append(str(node)) + 82 lines.append('%send%s' % (self.left, self.right)) + 83 return ''.join(lines) +
    84 +
    85 - def __str__(self): +
    86 """ + 87 Get this BlockNodes content, not including child Nodes + 88 """ + 89 lines = [] + 90 for node in self.nodes: + 91 if not isinstance(node, BlockNode): + 92 lines.append(str(node)) + 93 return ''.join(lines) +
    94 +
    95 - def append(self, node): +
    96 """ + 97 Add an element to the nodes. + 98 + 99 Keyword Arguments +100 +101 - node -- Node object or string to append. +102 """ +103 if isinstance(node, str) or isinstance(node, Node): +104 self.nodes.append(node) +105 else: +106 raise TypeError("Invalid type; must be instance of ``str`` or ``BlockNode``. %s" % node) +
    107 +
    108 - def extend(self, other): +
    109 """ +110 Extend the list of nodes with another BlockNode class. +111 +112 Keyword Arguments +113 +114 - other -- BlockNode or Content object to extend from. +115 """ +116 if isinstance(other, BlockNode): +117 self.nodes.extend(other.nodes) +118 else: +119 raise TypeError("Invalid type; must be instance of ``BlockNode``. %s" % other) +
    120 +
    121 - def output(self, blocks): +
    122 """ +123 Merges all nodes into a single string. +124 +125 blocks -- Dictionary of blocks that are extending +126 from this template. +127 """ +128 lines = [] +129 # Get each of our nodes +130 for node in self.nodes: +131 # If we have a block level node. +132 if isinstance(node, BlockNode): +133 # If we can override this block. +134 if node.name in blocks: +135 # Override block from vars. +136 lines.append(blocks[node.name].output(blocks)) +137 # Else we take the default +138 else: +139 lines.append(node.output(blocks)) +140 # Else its just a string +141 else: +142 lines.append(str(node)) +143 # Now combine all of our lines together. +144 return ''.join(lines) +
    145 +
    146 -class Content(BlockNode): +
    147 """ +148 Parent Container -- Used as the root level BlockNode. +149 +150 Contains functions that operate as such. +151 """ +
    152 - def __init__(self, name = "ContentBlock", pre_extend = False): +
    153 """ +154 Keyword Arguments +155 +156 name -- Unique name for this BlockNode +157 """ +158 self.name = name +159 self.nodes = [] +160 self.blocks = {} +161 self.pre_extend = pre_extend +
    162 +
    163 - def __str__(self): +
    164 lines = [] +165 # For each of our nodes +166 for node in self.nodes: +167 # If it is a block node. +168 if isinstance(node, BlockNode): +169 # And the node has a name that corresponds with a block in us +170 if node.name in self.blocks: +171 # Use the overriding output. +172 lines.append(self.blocks[node.name].output(self.blocks)) +173 else: +174 # Otherwise we just use the nodes output. +175 lines.append(node.output(self.blocks)) +176 else: +177 # It is just a string, so include it. +178 lines.append(str(node)) +179 # Merge our list together. +180 return ''.join(lines) +
    181 +
    182 - def _insert(self, other, index = 0): +
    183 """ +184 Inserts object at index. +185 """ +186 if isinstance(other, str) or isinstance(other, Node): +187 self.nodes.insert(index, other) +188 else: +189 raise TypeError("Invalid type, must be instance of ``str`` or ``Node``.") +
    190 +
    191 - def insert(self, other, index = 0): +
    192 """ +193 Inserts object at index. +194 +195 You may pass a list of objects and have them inserted. +196 """ +197 if isinstance(other, (list, tuple)): +198 # Must reverse so the order stays the same. +199 other.reverse() +200 for item in other: +201 self._insert(item, index) +202 else: +203 self._insert(other, index) +
    204 +
    205 - def append(self, node): +
    206 """ +207 Adds a node to list. If it is a BlockNode then we assign a block for it. +208 """ +209 if isinstance(node, str) or isinstance(node, Node): +210 self.nodes.append(node) +211 if isinstance(node, BlockNode): +212 self.blocks[node.name] = node +213 else: +214 raise TypeError("Invalid type, must be instance of ``str`` or ``BlockNode``. %s" % node) +
    215 +
    216 - def extend(self, other): +
    217 """ +218 Extends the objects list of nodes with another objects nodes +219 """ +220 if isinstance(other, BlockNode): +221 self.nodes.extend(other.nodes) +222 self.blocks.update(other.blocks) +223 else: +224 raise TypeError("Invalid type; must be instance of ``BlockNode``. %s" % other) +
    225 +
    226 - def clear_content(self): +
    227 self.nodes = [] +
    228 +
    229 -class TemplateParser(object): +
    230 +231 r_tag = re.compile(r'(\{\{.*?\}\})', re.DOTALL) +232 +233 r_multiline = re.compile(r'(""".*?""")|(\'\'\'.*?\'\'\')', re.DOTALL) +234 +235 # These are used for re-indentation. +236 # Indent + 1 +237 re_block = re.compile('^(elif |else:|except:|except |finally:).*$', +238 re.DOTALL) +239 # Indent - 1 +240 re_unblock = re.compile('^(return|continue|break|raise)( .*)?$', re.DOTALL) +241 # Indent - 1 +242 re_pass = re.compile('^pass( .*)?$', re.DOTALL) +243 +
    244 - def __init__(self, text, +245 name = "ParserContainer", +246 context = dict(), +247 path = 'views/', +248 writer = 'response.write', +249 lexers = {}, +250 delimiters = ('{{','}}'), +251 _super_nodes = [], +252 ): +
    253 """ +254 text -- text to parse +255 context -- context to parse in +256 path -- folder path to templates +257 writer -- string of writer class to use +258 lexers -- dict of custom lexers to use. +259 delimiters -- for example ('{{','}}') +260 _super_nodes -- a list of nodes to check for inclusion +261 this should only be set by "self.extend" +262 It contains a list of SuperNodes from a child +263 template that need to be handled. +264 """ +265 +266 # Keep a root level name. +267 self.name = name +268 # Raw text to start parsing. +269 self.text = text +270 # Writer to use (refer to the default for an example). +271 # This will end up as +272 # "%s(%s, escape=False)" % (self.writer, value) +273 self.writer = writer +274 +275 # Dictionary of custom name lexers to use. +276 if isinstance(lexers, dict): +277 self.lexers = lexers +278 else: +279 self.lexers = {} +280 +281 # Path of templates +282 self.path = path +283 # Context for templates. +284 self.context = context +285 +286 # allow optional alternative delimiters +287 self.delimiters = delimiters +288 if delimiters!=('{{','}}'): +289 escaped_delimiters = (re.escape(delimiters[0]),re.escape(delimiters[1])) +290 self.r_tag = re.compile(r'(%s.*?%s)' % escaped_delimiters, re.DOTALL) +291 +292 +293 # Create a root level Content that everything will go into. +294 self.content = Content(name=name) +295 +296 # Stack will hold our current stack of nodes. +297 # As we descend into a node, it will be added to the stack +298 # And when we leave, it will be removed from the stack. +299 # self.content should stay on the stack at all times. +300 self.stack = [self.content] +301 +302 # This variable will hold a reference to every super block +303 # that we come across in this template. +304 self.super_nodes = [] +305 +306 # This variable will hold a reference to the child +307 # super nodes that need handling. +308 self.child_super_nodes = _super_nodes +309 +310 # This variable will hold a reference to every block +311 # that we come across in this template +312 self.blocks = {} +313 +314 # Begin parsing. +315 self.parse(text) +
    316 +
    317 - def to_string(self): +
    318 """ +319 Return the parsed template with correct indentation. +320 +321 Used to make it easier to port to python3. +322 """ +323 return self.reindent(str(self.content)) +
    324 +
    325 - def __str__(self): +
    326 "Make sure str works exactly the same as python 3" +327 return self.to_string() +
    328 +
    329 - def __unicode__(self): +
    330 "Make sure str works exactly the same as python 3" +331 return self.to_string() +
    332 +
    333 - def reindent(self, text): +
    334 """ +335 Reindents a string of unindented python code. +336 """ +337 +338 # Get each of our lines into an array. +339 lines = text.split('\n') +340 +341 # Our new lines +342 new_lines = [] +343 +344 # Keeps track of how many indents we have. +345 # Used for when we need to drop a level of indentation +346 # only to reindent on the next line. +347 credit = 0 +348 +349 # Current indentation +350 k = 0 +351 +352 ################# +353 # THINGS TO KNOW +354 ################# +355 +356 # k += 1 means indent +357 # k -= 1 means unindent +358 # credit = 1 means unindent on the next line. +359 +360 for raw_line in lines: +361 line = raw_line.strip() +362 +363 # ignore empty lines +364 if not line: +365 continue +366 +367 # If we have a line that contains python code that +368 # should be unindented for this line of code. +369 # and then reindented for the next line. +370 if TemplateParser.re_block.match(line): +371 k = k + credit - 1 +372 +373 # We obviously can't have a negative indentation +374 k = max(k,0) +375 +376 # Add the indentation! +377 new_lines.append(' '*(4*k)+line) +378 +379 # Bank account back to 0 again :( +380 credit = 0 +381 +382 # If we are a pass block, we obviously de-dent. +383 if TemplateParser.re_pass.match(line): +384 k -= 1 +385 +386 # If we are any of the following, de-dent. +387 # However, we should stay on the same level +388 # But the line right after us will be de-dented. +389 # So we add one credit to keep us at the level +390 # while moving back one indentation level. +391 if TemplateParser.re_unblock.match(line): +392 credit = 1 +393 k -= 1 +394 +395 # If we are an if statement, a try, or a semi-colon we +396 # probably need to indent the next line. +397 if line.endswith(':') and not line.startswith('#'): +398 k += 1 +399 +400 # This must come before so that we can raise an error with the +401 # right content. +402 new_text = '\n'.join(new_lines) +403 +404 if k > 0: +405 self._raise_error('missing "pass" in view', new_text) +406 elif k < 0: +407 self._raise_error('too many "pass" in view', new_text) +408 +409 return new_text +
    410 +
    411 - def _raise_error(self, message='', text=None): +
    412 """ +413 Raise an error using itself as the filename and textual content. +414 """ +415 raise RestrictedError(self.name, text or self.text, message) +
    416 +
    417 - def _get_file_text(self, filename): +
    418 """ +419 Attempt to open ``filename`` and retrieve its text. +420 +421 This will use self.path to search for the file. +422 """ +423 +424 # If they didn't specify a filename, how can we find one! +425 if not filename.strip(): +426 self._raise_error('Invalid template filename') +427 +428 # Get the filename; filename looks like ``"template.html"``. +429 # We need to eval to remove the quotes and get the string type. +430 filename = eval(filename, self.context) +431 +432 # Get the path of the file on the system. +433 filepath = os.path.join(self.path, filename) +434 +435 # try to read the text. +436 try: +437 fileobj = open(filepath, 'rb') +438 text = fileobj.read() +439 fileobj.close() +440 except IOError: +441 self._raise_error('Unable to open included view file: ' + filepath) +442 +443 return text +
    444 +
    445 - def include(self, content, filename): +
    446 """ +447 Include ``filename`` here. +448 """ +449 text = self._get_file_text(filename) +450 +451 t = TemplateParser(text, +452 name = filename, +453 context = self.context, +454 path = self.path, +455 writer = self.writer, +456 delimiters = self.delimiters) +457 +458 content.append(t.content) +
    459 +
    460 - def extend(self, filename): +
    461 """ +462 Extend ``filename``. Anything not declared in a block defined by the +463 parent will be placed in the parent templates ``{{include}}`` block. +464 """ +465 text = self._get_file_text(filename) +466 +467 # Create out nodes list to send to the parent +468 super_nodes = [] +469 # We want to include any non-handled nodes. +470 super_nodes.extend(self.child_super_nodes) +471 # And our nodes as well. +472 super_nodes.extend(self.super_nodes) +473 +474 t = TemplateParser(text, +475 name = filename, +476 context = self.context, +477 path = self.path, +478 writer = self.writer, +479 delimiters = self.delimiters, +480 _super_nodes = super_nodes) +481 +482 # Make a temporary buffer that is unique for parent +483 # template. +484 buf = BlockNode(name='__include__' + filename, delimiters=self.delimiters) +485 pre = [] +486 +487 # Iterate through each of our nodes +488 for node in self.content.nodes: +489 # If a node is a block +490 if isinstance(node, BlockNode): +491 # That happens to be in the parent template +492 if node.name in t.content.blocks: +493 # Do not include it +494 continue +495 +496 if isinstance(node, Node): +497 # Or if the node was before the extension +498 # we should not include it +499 if node.pre_extend: +500 pre.append(node) +501 continue +502 +503 # Otherwise, it should go int the +504 # Parent templates {{include}} section. +505 buf.append(node) +506 else: +507 buf.append(node) +508 +509 # Clear our current nodes. We will be replacing this with +510 # the parent nodes. +511 self.content.nodes = [] +512 +513 # Set our include, unique by filename +514 t.content.blocks['__include__' + filename] = buf +515 +516 # Make sure our pre_extended nodes go first +517 t.content.insert(pre) +518 +519 # Then we extend our blocks +520 t.content.extend(self.content) +521 +522 # Work off the parent node. +523 self.content = t.content +
    524 +
    525 - def parse(self, text): +
    526 +527 # Basically, r_tag.split will split the text into +528 # an array containing, 'non-tag', 'tag', 'non-tag', 'tag' +529 # so if we alternate this variable, we know +530 # what to look for. This is alternate to +531 # line.startswith("{{") +532 in_tag = False +533 extend = None +534 pre_extend = True +535 +536 # Use a list to store everything in +537 # This is because later the code will "look ahead" +538 # for missing strings or brackets. +539 ij = self.r_tag.split(text) +540 # j = current index +541 # i = current item +542 for j in range(len(ij)): +543 i = ij[j] +544 +545 if i: +546 if len(self.stack) == 0: +547 self._raise_error('The "end" tag is unmatched, please check if you have a starting "block" tag') +548 +549 # Our current element in the stack. +550 top = self.stack[-1] +551 +552 if in_tag: +553 line = i +554 +555 # If we are missing any strings!!!! +556 # This usually happens with the following example +557 # template code +558 # +559 # {{a = '}}'}} +560 # or +561 # {{a = '}}blahblah{{'}} +562 # +563 # This will fix these +564 # This is commented out because the current template +565 # system has this same limitation. Since this has a +566 # performance hit on larger templates, I do not recommend +567 # using this code on production systems. This is still here +568 # for "i told you it *can* be fixed" purposes. +569 # +570 # +571 # if line.count("'") % 2 != 0 or line.count('"') % 2 != 0: +572 # +573 # # Look ahead +574 # la = 1 +575 # nextline = ij[j+la] +576 # +577 # # As long as we have not found our ending +578 # # brackets keep going +579 # while '}}' not in nextline: +580 # la += 1 +581 # nextline += ij[j+la] +582 # # clear this line, so we +583 # # don't attempt to parse it +584 # # this is why there is an "if i" +585 # # around line 530 +586 # ij[j+la] = '' +587 # +588 # # retrieve our index. +589 # index = nextline.index('}}') +590 # +591 # # Everything before the new brackets +592 # before = nextline[:index+2] +593 # +594 # # Everything after +595 # after = nextline[index+2:] +596 # +597 # # Make the next line everything after +598 # # so it parses correctly, this *should* be +599 # # all html +600 # ij[j+1] = after +601 # +602 # # Add everything before to the current line +603 # line += before +604 +605 # Get rid of '{{' and '}}' +606 line = line[2:-2].strip() +607 +608 # This is bad juju, but let's do it anyway +609 if not line: +610 continue +611 +612 # We do not want to replace the newlines in code, +613 # only in block comments. +614 def remove_newline(re_val): +615 # Take the entire match and replace newlines with +616 # escaped newlines. +617 return re_val.group(0).replace('\n', '\\n') +
    618 +619 # Perform block comment escaping. +620 # This performs escaping ON anything +621 # in between """ and """ +622 line = re.sub(TemplateParser.r_multiline, +623 remove_newline, +624 line) +625 +626 if line.startswith('='): +627 # IE: {{=response.title}} +628 name, value = '=', line[1:].strip() +629 else: +630 v = line.split(' ', 1) +631 if len(v) == 1: +632 # Example +633 # {{ include }} +634 # {{ end }} +635 name = v[0] +636 value = '' +637 else: +638 # Example +639 # {{ block pie }} +640 # {{ include "layout.html" }} +641 # {{ for i in range(10): }} +642 name = v[0] +643 value = v[1] +644 +645 # This will replace newlines in block comments +646 # with the newline character. This is so that they +647 # retain their formatting, but squish down to one +648 # line in the rendered template. +649 +650 # First check if we have any custom lexers +651 if name in self.lexers: +652 # Pass the information to the lexer +653 # and allow it to inject in the environment +654 +655 # You can define custom names such as +656 # '{{<<variable}}' which could potentially +657 # write unescaped version of the variable. +658 self.lexers[name](parser = self, +659 value = value, +660 top = top, +661 stack = self.stack,) +662 +663 elif name == '=': +664 # So we have a variable to insert into +665 # the template +666 buf = "\n%s(%s)" % (self.writer, value) +667 top.append(Node(buf, pre_extend = pre_extend)) +668 +669 elif name == 'block' and not value.startswith('='): +670 # Make a new node with name. +671 node = BlockNode(name = value.strip(), +672 pre_extend = pre_extend, +673 delimiters = self.delimiters) +674 +675 # Append this node to our active node +676 top.append(node) +677 +678 # Make sure to add the node to the stack. +679 # so anything after this gets added +680 # to this node. This allows us to +681 # "nest" nodes. +682 self.stack.append(node) +683 +684 elif name == 'end' and not value.startswith('='): +685 # We are done with this node. +686 +687 # Save an instance of it +688 self.blocks[top.name] = top +689 +690 # Pop it. +691 self.stack.pop() +692 +693 elif name == 'super' and not value.startswith('='): +694 # Get our correct target name +695 # If they just called {{super}} without a name +696 # attempt to assume the top blocks name. +697 if value: +698 target_node = value +699 else: +700 target_node = top.name +701 +702 # Create a SuperNode instance +703 node = SuperNode(name = target_node, +704 pre_extend = pre_extend) +705 +706 # Add this to our list to be taken care of +707 self.super_nodes.append(node) +708 +709 # And put in in the tree +710 top.append(node) +711 +712 elif name == 'include' and not value.startswith('='): +713 # If we know the target file to include +714 if value: +715 self.include(top, value) +716 +717 # Otherwise, make a temporary include node +718 # That the child node will know to hook into. +719 else: +720 include_node = BlockNode(name = '__include__' + self.name, +721 pre_extend = pre_extend, +722 delimiters = self.delimiters) +723 top.append(include_node) +724 +725 elif name == 'extend' and not value.startswith('='): +726 # We need to extend the following +727 # template. +728 extend = value +729 pre_extend = False +730 +731 else: +732 # If we don't know where it belongs +733 # we just add it anyways without formatting. +734 if line and in_tag: +735 +736 # Split on the newlines >.< +737 tokens = line.split('\n') +738 +739 # We need to look for any instances of +740 # for i in range(10): +741 # = i +742 # pass +743 # So we can properly put a response.write() in place. +744 continuation = False +745 len_parsed = 0 +746 for k in range(len(tokens)): +747 +748 tokens[k] = tokens[k].strip() +749 len_parsed += len(tokens[k]) +750 +751 if tokens[k].startswith('='): +752 if tokens[k].endswith('\\'): +753 continuation = True +754 tokens[k] = "\n%s(%s" % (self.writer, tokens[k][1:].strip()) +755 else: +756 tokens[k] = "\n%s(%s)" % (self.writer, tokens[k][1:].strip()) +757 elif continuation: +758 tokens[k] += ')' +759 continuation = False +760 +761 +762 buf = "\n%s" % '\n'.join(tokens) +763 top.append(Node(buf, pre_extend = pre_extend)) +764 +765 else: +766 # It is HTML so just include it. +767 buf = "\n%s(%r, escape=False)" % (self.writer, i) +768 top.append(Node(buf, pre_extend = pre_extend)) +769 +770 # Remember: tag, not tag, tag, not tag +771 in_tag = not in_tag +772 +773 # Make a list of items to remove from child +774 to_rm = [] +775 +776 # Go through each of the children nodes +777 for node in self.child_super_nodes: +778 # If we declared a block that this node wants to include +779 if node.name in self.blocks: +780 # Go ahead and include it! +781 node.value = self.blocks[node.name] +782 # Since we processed this child, we don't need to +783 # pass it along to the parent +784 to_rm.append(node) +785 +786 # Remove some of the processed nodes +787 for node in to_rm: +788 # Since this is a pointer, it works beautifully. +789 # Sometimes I miss C-Style pointers... I want my asterisk... +790 self.child_super_nodes.remove(node) +791 +792 # If we need to extend a template. +793 if extend: +794 self.extend(extend) +
    795 +796 # We need this for integration with gluon +
    797 -def parse_template(filename, +798 path = 'views/', +799 context = dict(), +800 lexers = {}, +801 delimiters = ('{{','}}') +802 ): +
    803 """ +804 filename can be a view filename in the views folder or an input stream +805 path is the path of a views folder +806 context is a dictionary of symbols used to render the template +807 """ +808 +809 # First, if we have a str try to open the file +810 if isinstance(filename, str): +811 try: +812 fp = open(os.path.join(path, filename), 'rb') +813 text = fp.read() +814 fp.close() +815 except IOError: +816 raise RestrictedError(filename, '', 'Unable to find the file') +817 else: +818 text = filename.read() +819 +820 # Use the file contents to get a parsed template and return it. +821 return str(TemplateParser(text, context=context, path=path, lexers=lexers, delimiters=delimiters)) +
    822 +
    823 -def get_parsed(text): +
    824 """ +825 Returns the indented python code of text. Useful for unit testing. +826 +827 """ +828 return str(TemplateParser(text)) +
    829 +830 # And this is a generic render function. +831 # Here for integration with gluon. +
    832 -def render(content = "hello world", +833 stream = None, +834 filename = None, +835 path = None, +836 context = {}, +837 lexers = {}, +838 delimiters = ('{{','}}') +839 ): +
    840 """ +841 >>> render() +842 'hello world' +843 >>> render(content='abc') +844 'abc' +845 >>> render(content='abc\\'') +846 "abc'" +847 >>> render(content='a"\\'bc') +848 'a"\\'bc' +849 >>> render(content='a\\nbc') +850 'a\\nbc' +851 >>> render(content='a"bcd"e') +852 'a"bcd"e' +853 >>> render(content="'''a\\nc'''") +854 "'''a\\nc'''" +855 >>> render(content="'''a\\'c'''") +856 "'''a\'c'''" +857 >>> render(content='{{for i in range(a):}}{{=i}}<br />{{pass}}', context=dict(a=5)) +858 '0<br />1<br />2<br />3<br />4<br />' +859 >>> render(content='{%for i in range(a):%}{%=i%}<br />{%pass%}', context=dict(a=5),delimiters=('{%','%}')) +860 '0<br />1<br />2<br />3<br />4<br />' +861 >>> render(content="{{='''hello\\nworld'''}}") +862 'hello\\nworld' +863 >>> render(content='{{for i in range(3):\\n=i\\npass}}') +864 '012' +865 """ +866 # Here to avoid circular Imports +867 try: +868 from globals import Response +869 except: +870 # Working standalone. Build a mock Response object. +871 class Response(): +872 def __init__(self): +873 self.body = cStringIO.StringIO() +
    874 def write(self, data, escape=True): +875 if not escape: +876 self.body.write(str(data)) +877 elif hasattr(data,'xml') and callable(data.xml): +878 self.body.write(data.xml()) +879 else: +880 # make it a string +881 if not isinstance(data, (str, unicode)): +882 data = str(data) +883 elif isinstance(data, unicode): +884 data = data.encode('utf8', 'xmlcharrefreplace') +885 data = cgi.escape(data, True).replace("'","&#x27;") +886 self.body.write(data) +887 +888 # A little helper to avoid escaping. +889 class NOESCAPE(): +890 def __init__(self, text): +891 self.text = text +892 def xml(self): +893 return self.text +894 # Add it to the context so we can use it. +895 context['NOESCAPE'] = NOESCAPE +896 +897 # If we don't have anything to render, why bother? +898 if not content and not stream and not filename: +899 raise SyntaxError, "Must specify a stream or filename or content" +900 +901 # Here for legacy purposes, probably can be reduced to something more simple. +902 close_stream = False +903 if not stream: +904 if filename: +905 stream = open(filename, 'rb') +906 close_stream = True +907 elif content: +908 stream = cStringIO.StringIO(content) +909 +910 # Get a response class. +911 context['response'] = Response() +912 +913 # Execute the template. +914 code = str(TemplateParser(stream.read(), context=context, path=path, lexers=lexers, delimiters=delimiters)) +915 try: +916 exec(code) in context +917 except Exception: +918 # for i,line in enumerate(code.split('\n')): print i,line +919 raise +920 +921 if close_stream: +922 stream.close() +923 +924 # Returned the rendered content. +925 return context['response'].body.getvalue() +926 +927 +928 if __name__ == '__main__': +929 import doctest +930 doctest.testmod() +931 +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.template.BlockNode-class.html Index: applications/examples/static/epydoc/web2py.gluon.template.BlockNode-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.template.BlockNode-class.html +++ applications/examples/static/epydoc/web2py.gluon.template.BlockNode-class.html @@ -0,0 +1,474 @@ + + + + + web2py.gluon.template.BlockNode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module template :: + Class BlockNode + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class BlockNode

    source code

    +
    +object --+    
    +         |    
    +      Node --+
    +             |
    +            BlockNode
    +
    + +
    Known Subclasses:
    +
    + Content +
    + +
    +

    Block Container.

    +

    This Node can contain other Nodes and will render in a hierarchical + order of when nodes were added.

    + ie: +
    +   {{ block test }}
    +       This is default block test
    +   {{ end }}
    +


    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + name='', + pre_extend=True, + delimiters=('{{', '}}'))
    + name - Name of this Node.
    + source code + +
    + +
    +   + + + + + + +
    __repr__(self)
    + repr(x)
    + source code + +
    + +
    +   + + + + + + +
    __str__(self)
    + Get this BlockNodes content, not including child Nodes
    + source code + +
    + +
    +   + + + + + + +
    append(self, + node)
    + Add an element to the nodes.
    + source code + +
    + +
    +   + + + + + + +
    extend(self, + other)
    + Extend the list of nodes with another BlockNode class.
    + source code + +
    + +
    +   + + + + + + +
    output(self, + blocks)
    + Merges all nodes into a single string.
    + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __setattr__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + name='', + pre_extend=True, + delimiters=('{{', '}}')) +
    (Constructor) +

    +
    source code  +
    + + name - Name of this Node. +
    +
    Overrides: + Node.__init__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    __repr__(self) +
    (Representation operator) +

    +
    source code  +
    + + repr(x) +
    +
    Overrides: + object.__repr__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    __str__(self) +
    (Informal representation operator) +

    +
    source code  +
    + + Get this BlockNodes content, not including child Nodes +
    +
    Overrides: + Node.__str__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    append(self, + node) +

    +
    source code  +
    + +
    +
    +Add an element to the nodes.
    +
    +Keyword Arguments
    +
    +- node -- Node object or string to append.
    +
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    extend(self, + other) +

    +
    source code  +
    + +
    +
    +Extend the list of nodes with another BlockNode class.
    +
    +Keyword Arguments
    +
    +- other -- BlockNode or Content object to extend from.
    +
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    output(self, + blocks) +

    +
    source code  +
    + +

    Merges all nodes into a single string.

    + blocks -- Dictionary of blocks that are extending from this + template. +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.template.Content-class.html Index: applications/examples/static/epydoc/web2py.gluon.template.Content-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.template.Content-class.html +++ applications/examples/static/epydoc/web2py.gluon.template.Content-class.html @@ -0,0 +1,454 @@ + + + + + web2py.gluon.template.Content + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module template :: + Class Content + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class Content

    source code

    +
    +object --+        
    +         |        
    +      Node --+    
    +             |    
    +     BlockNode --+
    +                 |
    +                Content
    +
    + +
    +

    Parent Container -- Used as the root level BlockNode.

    + Contains functions that operate as such.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + name='ContentBlock', + pre_extend=True)
    + Keyword Arguments
    + source code + +
    + +
    +   + + + + + + +
    __str__(self)
    + Get this BlockNodes content, not including child Nodes
    + source code + +
    + +
    +   + + + + + + +
    _insert(self, + other, + index=0)
    + Inserts object at index.
    + source code + +
    + +
    +   + + + + + + +
    insert(self, + other, + index=0)
    + Inserts object at index.
    + source code + +
    + +
    +   + + + + + + +
    append(self, + node)
    + Adds a node to list.
    + source code + +
    + +
    +   + + + + + + +
    extend(self, + other)
    + Extends the objects list of nodes with another objects nodes
    + source code + +
    + +
    +   + + + + + + +
    clear_content(self) + source code + +
    + +
    +

    Inherited from BlockNode: + __repr__, + output +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __setattr__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + name='ContentBlock', + pre_extend=True) +
    (Constructor) +

    +
    source code  +
    + +

    Keyword Arguments

    + name -- Unique name for this BlockNode +
    +
    Overrides: + BlockNode.__init__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    __str__(self) +
    (Informal representation operator) +

    +
    source code  +
    + + Get this BlockNodes content, not including child Nodes +
    +
    Overrides: + BlockNode.__str__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    insert(self, + other, + index=0) +

    +
    source code  +
    + +

    Inserts object at index.

    + You may pass a list of objects and have them inserted. +
    +
    +
    +
    + +
    + +
    + + +
    +

    append(self, + node) +

    +
    source code  +
    + + Adds a node to list. If it is a BlockNode then we assign a block for + it. +
    +
    Overrides: + BlockNode.append +
    +
    +
    +
    + +
    + +
    + + +
    +

    extend(self, + other) +

    +
    source code  +
    + + Extends the objects list of nodes with another objects nodes +
    +
    Overrides: + BlockNode.extend +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.template.Node-class.html Index: applications/examples/static/epydoc/web2py.gluon.template.Node-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.template.Node-class.html +++ applications/examples/static/epydoc/web2py.gluon.template.Node-class.html @@ -0,0 +1,295 @@ + + + + + web2py.gluon.template.Node + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module template :: + Class Node + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class Node

    source code

    +
    +object --+
    +         |
    +        Node
    +
    + +
    Known Subclasses:
    +
    + BlockNode, + SuperNode +
    + +
    +Basic Container Object

    + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + value=1, + pre_extend=True)
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    __str__(self)
    + str(x)
    + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + value=1, + pre_extend=True) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    __str__(self) +
    (Informal representation operator) +

    +
    source code  +
    + + str(x) +
    +
    Overrides: + object.__str__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.template.SuperNode-class.html Index: applications/examples/static/epydoc/web2py.gluon.template.SuperNode-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.template.SuperNode-class.html +++ applications/examples/static/epydoc/web2py.gluon.template.SuperNode-class.html @@ -0,0 +1,327 @@ + + + + + web2py.gluon.template.SuperNode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module template :: + Class SuperNode + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class SuperNode

    source code

    +
    +object --+    
    +         |    
    +      Node --+
    +             |
    +            SuperNode
    +
    + +
    + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + name='', + pre_extend=True)
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    __str__(self)
    + str(x)
    + source code + +
    + +
    +   + + + + + + +
    __repr__(self)
    + repr(x)
    + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __setattr__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + name='', + pre_extend=True) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + Node.__init__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    __str__(self) +
    (Informal representation operator) +

    +
    source code  +
    + + str(x) +
    +
    Overrides: + Node.__str__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    __repr__(self) +
    (Representation operator) +

    +
    source code  +
    + + repr(x) +
    +
    Overrides: + object.__repr__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.template.TemplateParser-class.html Index: applications/examples/static/epydoc/web2py.gluon.template.TemplateParser-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.template.TemplateParser-class.html +++ applications/examples/static/epydoc/web2py.gluon.template.TemplateParser-class.html @@ -0,0 +1,626 @@ + + + + + web2py.gluon.template.TemplateParser + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module template :: + Class TemplateParser + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class TemplateParser

    source code

    +
    +object --+
    +         |
    +        TemplateParser
    +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + text, + name='ParserContainer', + context={}, + path='views/', + writer='response.write', + lexers={}, + delimiters=('{{', '}}'), + _super_nodes=[])
    + text -- text to parse +context -- context to parse in +path -- folder path to templates +writer -- string of writer class to use +lexers -- dict of custom lexers to use.
    + source code + +
    + +
    +   + + + + + + +
    to_string(self)
    + Return the parsed template with correct indentation.
    + source code + +
    + +
    +   + + + + + + +
    __str__(self)
    + Make sure str works exactly the same as python 3
    + source code + +
    + +
    +   + + + + + + +
    __unicode__(self)
    + Make sure str works exactly the same as python 3
    + source code + +
    + +
    +   + + + + + + +
    reindent(self, + text)
    + Reindents a string of unindented python code.
    + source code + +
    + +
    +   + + + + + + +
    _raise_error(self, + message='', + text=1)
    + Raise an error using itself as the filename and textual + content.
    + source code + +
    + +
    +   + + + + + + +
    _get_file_text(self, + filename)
    + Attempt to open ``filename`` and retrieve its text.
    + source code + +
    + +
    +   + + + + + + +
    include(self, + content, + filename)
    + Include ``filename`` here.
    + source code + +
    + +
    +   + + + + + + +
    extend(self, + filename)
    + Extend ``filename``.
    + source code + +
    + +
    +   + + + + + + +
    parse(self, + text) + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + r_tag = re.compile(r'(?s)(\{\{.*?\}\})') +
    +   + + r_multiline = re.compile(r'(?s)(""".*?""")|(\'\'\'.*?\'\'\')') +
    +   + + re_block = re.compile(r'(?s)^(elif |else:|except:|except |fina... +
    +   + + re_unblock = re.compile(r'(?s)^(return|continue|break|raise)( ... +
    +   + + re_pass = re.compile(r'(?s)^pass( .*)?$') +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + text, + name='ParserContainer', + context={}, + path='views/', + writer='response.write', + lexers={}, + delimiters=('{{', '}}'), + _super_nodes=[]) +
    (Constructor) +

    +
    source code  +
    + +
    +
    +text -- text to parse
    +context -- context to parse in
    +path -- folder path to templates
    +writer -- string of writer class to use
    +lexers -- dict of custom lexers to use.
    +delimiters -- for example ('{{','}}')
    +_super_nodes -- a list of nodes to check for inclusion
    +                this should only be set by "self.extend"
    +                It contains a list of SuperNodes from a child
    +                template that need to be handled.
    +
    +
    +
    +
    Overrides: + object.__init__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    to_string(self) +

    +
    source code  +
    + +

    Return the parsed template with correct indentation.

    + Used to make it easier to port to python3. +
    +
    +
    +
    + +
    + +
    + + +
    +

    __str__(self) +
    (Informal representation operator) +

    +
    source code  +
    + + Make sure str works exactly the same as python 3 +
    +
    Overrides: + object.__str__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    _get_file_text(self, + filename) +

    +
    source code  +
    + +

    Attempt to open ``filename`` and retrieve its text.

    + This will use self.path to search for the file. +
    +
    +
    +
    + +
    + +
    + + +
    +

    extend(self, + filename) +

    +
    source code  +
    + + Extend ``filename``. Anything not declared in a block defined by the + parent will be placed in the parent templates ``{{include}}`` block. +
    +
    +
    +
    +
    + + + + + + +
    + + + + + +
    Class Variable Details[hide private]
    +
    + +
    + +
    +

    re_block

    + +
    +
    +
    +
    Value:
    +
    +re.compile(r'(?s)^(elif |else:|except:|except |finally:).*$')
    +
    +
    +
    +
    +
    + +
    + +
    +

    re_unblock

    + +
    +
    +
    +
    Value:
    +
    +re.compile(r'(?s)^(return|continue|break|raise)( .*)?$')
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.tools-module.html Index: applications/examples/static/epydoc/web2py.gluon.tools-module.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.tools-module.html +++ applications/examples/static/epydoc/web2py.gluon.tools-module.html @@ -0,0 +1,539 @@ + + + + + web2py.gluon.tools + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module tools + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Module tools

    source code

    +This file is part of the web2py Web Framework Copyrighted by Massimo + Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Classes[hide private]
    +
    +   + + Mail
    + Class for configuring and sending emails with alternative text / + html body, multiple attachments and encryption support +
    +   + + Recaptcha +
    +   + + Auth
    + Class for authentication, authorization, role based access control. +
    +   + + Crud +
    +   + + Service +
    +   + + PluginManager
    + Plugin Manager is similar to a storage object but it is a single + level singleton this means that multiple instances within the same + thread share the same attributes Its constructor is also + special. +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Functions[hide private]
    +
    +   + + + + + + +
    DEFAULT() + source code + +
    + +
    +   + + + + + + +
    callback(actions, + form, + tablename=1) + source code + +
    + +
    +   + + + + + + +
    validators(*a) + source code + +
    + +
    +   + + + + + + +
    call_or_redirect(f, + *args) + source code + +
    + +
    +   + + + + + + +
    addrow(form, + a, + b, + c, + style, + _id, + position=-1) + source code + +
    + +
    +   + + + + + + +
    fetch(url, + data=1, + headers={}, + cookie=<SimpleCookie: >, + user_agent='Mozilla/5.0') + source code + +
    + +
    +   + + + + + + +
    geocode(address) + source code + +
    + +
    +   + + + + + + +
    universal_caller(f, + *a, + **b) + source code + +
    + +
    +   + + + + + + +
    completion(callback)
    + Executes a task on completion of the called action.
    + source code + +
    + +
    +   + + + + + + +
    prettydate(d, + T=<function <lambda> at 0x2543668>) + source code + +
    + +
    +   + + + + + + +
    test_thread_separation() + source code + +
    + +
    + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Variables[hide private]
    +
    +   + + logger = logging.getLogger("web2py") +
    +   + + regex_geocode = re.compile(r'<coordinates>(?P<la>[^,]*),(?P<lo... +
    +   + + ON = True +
    +   + + TAG = <gluon.html.__TAG__ object at 0x2128f10> +
    +   + + current = <thread._local object at 0x1f4ab10> +
    + + + + + + +
    + + + + + +
    Function Details[hide private]
    +
    + +
    + +
    + + +
    +

    completion(callback) +

    +
    source code  +
    + +
    +
    +Executes a task on completion of the called action. For example:
    +
    +    from gluon.tools import completion
    +    @completion(lambda d: logging.info(repr(d)))
    +    def index():
    +        return dict(message='hello')
    +
    +It logs the output of the function every time input is called.
    +The argument of completion is executed in a new thread.
    +
    +
    +
    +
    +
    +
    +
    + + + + + + +
    + + + + + +
    Variables Details[hide private]
    +
    + +
    + +
    +

    regex_geocode

    + +
    +
    +
    +
    Value:
    +
    +re.compile(r'<coordinates>(?P<la>[^,]*),(?P<lo>[^,]*).*?</coordinates>\
    +')
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.tools-pysrc.html Index: applications/examples/static/epydoc/web2py.gluon.tools-pysrc.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.tools-pysrc.html +++ applications/examples/static/epydoc/web2py.gluon.tools-pysrc.html @@ -0,0 +1,6250 @@ + + + + + web2py.gluon.tools + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module tools + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Module web2py.gluon.tools

    +
    +   1  #!/bin/python 
    +   2  # -*- coding: utf-8 -*- 
    +   3   
    +   4  """ 
    +   5  This file is part of the web2py Web Framework 
    +   6  Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> 
    +   7  License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) 
    +   8  """ 
    +   9   
    +  10  import base64 
    +  11  import cPickle 
    +  12  import datetime 
    +  13  import thread 
    +  14  import logging 
    +  15  import sys 
    +  16  import os 
    +  17  import re 
    +  18  import time 
    +  19  import copy 
    +  20  import smtplib 
    +  21  import urllib 
    +  22  import urllib2 
    +  23  import Cookie 
    +  24  import cStringIO 
    +  25  from email import MIMEBase, MIMEMultipart, MIMEText, Encoders, Header, message_from_string 
    +  26   
    +  27  from contenttype import contenttype 
    +  28  from storage import Storage, StorageList, Settings, Messages 
    +  29  from utils import web2py_uuid 
    +  30  from gluon import * 
    +  31  from fileutils import read_file 
    +  32   
    +  33  import serializers 
    +  34  import contrib.simplejson as simplejson 
    +  35   
    +  36   
    +  37  __all__ = ['Mail', 'Auth', 'Recaptcha', 'Crud', 'Service', 'PluginManager', 'fetch', 'geocode', 'prettydate'] 
    +  38   
    +  39  logger = logging.getLogger("web2py") 
    +  40   
    +  41  DEFAULT = lambda: None 
    +  42   
    +
    43 -def callback(actions,form,tablename=None): +
    44 if actions: + 45 if tablename and isinstance(actions,dict): + 46 actions = actions.get(tablename, []) + 47 if not isinstance(actions,(list, tuple)): + 48 actions = [actions] + 49 [action(form) for action in actions] +
    50 +
    51 -def validators(*a): +
    52 b = [] + 53 for item in a: + 54 if isinstance(item, (list, tuple)): + 55 b = b + list(item) + 56 else: + 57 b.append(item) + 58 return b +
    59 +
    60 -def call_or_redirect(f,*args): +
    61 if callable(f): + 62 redirect(f(*args)) + 63 else: + 64 redirect(f) +
    65 +
    66 -class Mail(object): +
    67 """ + 68 Class for configuring and sending emails with alternative text / html + 69 body, multiple attachments and encryption support + 70 + 71 Works with SMTP and Google App Engine. + 72 """ + 73 +
    74 - class Attachment(MIMEBase.MIMEBase): +
    75 """ + 76 Email attachment + 77 + 78 Arguments:: + 79 + 80 payload: path to file or file-like object with read() method + 81 filename: name of the attachment stored in message; if set to + 82 None, it will be fetched from payload path; file-like + 83 object payload must have explicit filename specified + 84 content_id: id of the attachment; automatically contained within + 85 < and > + 86 content_type: content type of the attachment; if set to None, + 87 it will be fetched from filename using gluon.contenttype + 88 module + 89 encoding: encoding of all strings passed to this function (except + 90 attachment body) + 91 + 92 Content ID is used to identify attachments within the html body; + 93 in example, attached image with content ID 'photo' may be used in + 94 html message as a source of img tag <img src="cid:photo" />. + 95 + 96 Examples:: + 97 + 98 #Create attachment from text file: + 99 attachment = Mail.Attachment('/path/to/file.txt') + 100 + 101 Content-Type: text/plain + 102 MIME-Version: 1.0 + 103 Content-Disposition: attachment; filename="file.txt" + 104 Content-Transfer-Encoding: base64 + 105 + 106 SOMEBASE64CONTENT= + 107 + 108 #Create attachment from image file with custom filename and cid: + 109 attachment = Mail.Attachment('/path/to/file.png', + 110 filename='photo.png', + 111 content_id='photo') + 112 + 113 Content-Type: image/png + 114 MIME-Version: 1.0 + 115 Content-Disposition: attachment; filename="photo.png" + 116 Content-Id: <photo> + 117 Content-Transfer-Encoding: base64 + 118 + 119 SOMEOTHERBASE64CONTENT= + 120 """ + 121 +
    122 - def __init__( + 123 self, + 124 payload, + 125 filename=None, + 126 content_id=None, + 127 content_type=None, + 128 encoding='utf-8'): +
    129 if isinstance(payload, str): + 130 if filename == None: + 131 filename = os.path.basename(payload) + 132 payload = read_file(payload, 'rb') + 133 else: + 134 if filename == None: + 135 raise Exception('Missing attachment name') + 136 payload = payload.read() + 137 filename = filename.encode(encoding) + 138 if content_type == None: + 139 content_type = contenttype(filename) + 140 self.my_filename = filename + 141 self.my_payload = payload + 142 MIMEBase.MIMEBase.__init__(self, *content_type.split('/', 1)) + 143 self.set_payload(payload) + 144 self['Content-Disposition'] = 'attachment; filename="%s"' % filename + 145 if content_id != None: + 146 self['Content-Id'] = '<%s>' % content_id.encode(encoding) + 147 Encoders.encode_base64(self) +
    148 +
    149 - def __init__(self, server=None, sender=None, login=None, tls=True): +
    150 """ + 151 Main Mail object + 152 + 153 Arguments:: + 154 + 155 server: SMTP server address in address:port notation + 156 sender: sender email address + 157 login: sender login name and password in login:password notation + 158 or None if no authentication is required + 159 tls: enables/disables encryption (True by default) + 160 + 161 In Google App Engine use:: + 162 + 163 server='gae' + 164 + 165 For sake of backward compatibility all fields are optional and default + 166 to None, however, to be able to send emails at least server and sender + 167 must be specified. They are available under following fields: + 168 + 169 mail.settings.server + 170 mail.settings.sender + 171 mail.settings.login + 172 + 173 When server is 'logging', email is logged but not sent (debug mode) + 174 + 175 Optionally you can use PGP encryption or X509: + 176 + 177 mail.settings.cipher_type = None + 178 mail.settings.sign = True + 179 mail.settings.sign_passphrase = None + 180 mail.settings.encrypt = True + 181 mail.settings.x509_sign_keyfile = None + 182 mail.settings.x509_sign_certfile = None + 183 mail.settings.x509_crypt_certfiles = None + 184 + 185 cipher_type : None + 186 gpg - need a python-pyme package and gpgme lib + 187 x509 - smime + 188 sign : sign the message (True or False) + 189 sign_passphrase : passphrase for key signing + 190 encrypt : encrypt the message + 191 ... x509 only ... + 192 x509_sign_keyfile : the signers private key filename (PEM format) + 193 x509_sign_certfile: the signers certificate filename (PEM format) + 194 x509_crypt_certfiles: the certificates file to encrypt the messages + 195 with can be a file name or a list of + 196 file names (PEM format) + 197 + 198 Examples:: + 199 + 200 #Create Mail object with authentication data for remote server: + 201 mail = Mail('example.com:25', 'me@example.com', 'me:password') + 202 """ + 203 + 204 settings = self.settings = Settings() + 205 settings.server = server + 206 settings.sender = sender + 207 settings.login = login + 208 settings.tls = tls + 209 settings.ssl = False + 210 settings.cipher_type = None + 211 settings.sign = True + 212 settings.sign_passphrase = None + 213 settings.encrypt = True + 214 settings.x509_sign_keyfile = None + 215 settings.x509_sign_certfile = None + 216 settings.x509_crypt_certfiles = None + 217 settings.debug = False + 218 settings.lock_keys = True + 219 self.result = {} + 220 self.error = None +
    221 +
    222 - def send( + 223 self, + 224 to, + 225 subject='None', + 226 message='None', + 227 attachments=None, + 228 cc=None, + 229 bcc=None, + 230 reply_to=None, + 231 encoding='utf-8', + 232 ): +
    233 """ + 234 Sends an email using data specified in constructor + 235 + 236 Arguments:: + 237 + 238 to: list or tuple of receiver addresses; will also accept single + 239 object + 240 subject: subject of the email + 241 message: email body text; depends on type of passed object: + 242 if 2-list or 2-tuple is passed: first element will be + 243 source of plain text while second of html text; + 244 otherwise: object will be the only source of plain text + 245 and html source will be set to None; + 246 If text or html source is: + 247 None: content part will be ignored, + 248 string: content part will be set to it, + 249 file-like object: content part will be fetched from + 250 it using it's read() method + 251 attachments: list or tuple of Mail.Attachment objects; will also + 252 accept single object + 253 cc: list or tuple of carbon copy receiver addresses; will also + 254 accept single object + 255 bcc: list or tuple of blind carbon copy receiver addresses; will + 256 also accept single object + 257 reply_to: address to which reply should be composed + 258 encoding: encoding of all strings passed to this method (including + 259 message bodies) + 260 + 261 Examples:: + 262 + 263 #Send plain text message to single address: + 264 mail.send('you@example.com', + 265 'Message subject', + 266 'Plain text body of the message') + 267 + 268 #Send html message to single address: + 269 mail.send('you@example.com', + 270 'Message subject', + 271 '<html>Plain text body of the message</html>') + 272 + 273 #Send text and html message to three addresses (two in cc): + 274 mail.send('you@example.com', + 275 'Message subject', + 276 ('Plain text body', '<html>html body</html>'), + 277 cc=['other1@example.com', 'other2@example.com']) + 278 + 279 #Send html only message with image attachment available from + 280 the message by 'photo' content id: + 281 mail.send('you@example.com', + 282 'Message subject', + 283 (None, '<html><img src="cid:photo" /></html>'), + 284 Mail.Attachment('/path/to/photo.jpg' + 285 content_id='photo')) + 286 + 287 #Send email with two attachments and no body text + 288 mail.send('you@example.com, + 289 'Message subject', + 290 None, + 291 [Mail.Attachment('/path/to/fist.file'), + 292 Mail.Attachment('/path/to/second.file')]) + 293 + 294 Returns True on success, False on failure. + 295 + 296 Before return, method updates two object's fields: + 297 self.result: return value of smtplib.SMTP.sendmail() or GAE's + 298 mail.send_mail() method + 299 self.error: Exception message or None if above was successful + 300 """ + 301 + 302 def encode_header(key): + 303 if [c for c in key if 32>ord(c) or ord(c)>127]: + 304 return Header.Header(key.encode('utf-8'),'utf-8') + 305 else: + 306 return key +
    307 + 308 if not isinstance(self.settings.server, str): + 309 raise Exception('Server address not specified') + 310 if not isinstance(self.settings.sender, str): + 311 raise Exception('Sender address not specified') + 312 payload_in = MIMEMultipart.MIMEMultipart('mixed') + 313 if to: + 314 if not isinstance(to, (list,tuple)): + 315 to = [to] + 316 else: + 317 raise Exception('Target receiver address not specified') + 318 if cc: + 319 if not isinstance(cc, (list, tuple)): + 320 cc = [cc] + 321 if bcc: + 322 if not isinstance(bcc, (list, tuple)): + 323 bcc = [bcc] + 324 if message == None: + 325 text = html = None + 326 elif isinstance(message, (list, tuple)): + 327 text, html = message + 328 elif message.strip().startswith('<html') and message.strip().endswith('</html>'): + 329 text = self.settings.server=='gae' and message or None + 330 html = message + 331 else: + 332 text = message + 333 html = None + 334 if text != None or html != None: + 335 attachment = MIMEMultipart.MIMEMultipart('alternative') + 336 if text != None: + 337 if isinstance(text, basestring): + 338 text = text.decode(encoding).encode('utf-8') + 339 else: + 340 text = text.read().decode(encoding).encode('utf-8') + 341 attachment.attach(MIMEText.MIMEText(text,_charset='utf-8')) + 342 if html != None: + 343 if isinstance(html, basestring): + 344 html = html.decode(encoding).encode('utf-8') + 345 else: + 346 html = html.read().decode(encoding).encode('utf-8') + 347 attachment.attach(MIMEText.MIMEText(html, 'html',_charset='utf-8')) + 348 payload_in.attach(attachment) + 349 if attachments == None: + 350 pass + 351 elif isinstance(attachments, (list, tuple)): + 352 for attachment in attachments: + 353 payload_in.attach(attachment) + 354 else: + 355 payload_in.attach(attachments) + 356 + 357 + 358 ####################################################### + 359 # CIPHER # + 360 ####################################################### + 361 cipher_type = self.settings.cipher_type + 362 sign = self.settings.sign + 363 sign_passphrase = self.settings.sign_passphrase + 364 encrypt = self.settings.encrypt + 365 ####################################################### + 366 # GPGME # + 367 ####################################################### + 368 if cipher_type == 'gpg': + 369 if not sign and not encrypt: + 370 self.error="No sign and no encrypt is set but cipher type to gpg" + 371 return False + 372 + 373 # need a python-pyme package and gpgme lib + 374 from pyme import core, errors + 375 from pyme.constants.sig import mode + 376 ############################################ + 377 # sign # + 378 ############################################ + 379 if sign: + 380 import string + 381 core.check_version(None) + 382 pin=string.replace(payload_in.as_string(),'\n','\r\n') + 383 plain = core.Data(pin) + 384 sig = core.Data() + 385 c = core.Context() + 386 c.set_armor(1) + 387 c.signers_clear() + 388 # search for signing key for From: + 389 for sigkey in c.op_keylist_all(self.settings.sender, 1): + 390 if sigkey.can_sign: + 391 c.signers_add(sigkey) + 392 if not c.signers_enum(0): + 393 self.error='No key for signing [%s]' % self.settings.sender + 394 return False + 395 c.set_passphrase_cb(lambda x,y,z: sign_passphrase) + 396 try: + 397 # make a signature + 398 c.op_sign(plain,sig,mode.DETACH) + 399 sig.seek(0,0) + 400 # make it part of the email + 401 payload=MIMEMultipart.MIMEMultipart('signed', + 402 boundary=None, + 403 _subparts=None, + 404 **dict(micalg="pgp-sha1", + 405 protocol="application/pgp-signature")) + 406 # insert the origin payload + 407 payload.attach(payload_in) + 408 # insert the detached signature + 409 p=MIMEBase.MIMEBase("application",'pgp-signature') + 410 p.set_payload(sig.read()) + 411 payload.attach(p) + 412 # it's just a trick to handle the no encryption case + 413 payload_in=payload + 414 except errors.GPGMEError, ex: + 415 self.error="GPG error: %s" % ex.getstring() + 416 return False + 417 ############################################ + 418 # encrypt # + 419 ############################################ + 420 if encrypt: + 421 core.check_version(None) + 422 plain = core.Data(payload_in.as_string()) + 423 cipher = core.Data() + 424 c = core.Context() + 425 c.set_armor(1) + 426 # collect the public keys for encryption + 427 recipients=[] + 428 rec=to[:] + 429 if cc: + 430 rec.extend(cc) + 431 if bcc: + 432 rec.extend(bcc) + 433 for addr in rec: + 434 c.op_keylist_start(addr,0) + 435 r = c.op_keylist_next() + 436 if r == None: + 437 self.error='No key for [%s]' % addr + 438 return False + 439 recipients.append(r) + 440 try: + 441 # make the encryption + 442 c.op_encrypt(recipients, 1, plain, cipher) + 443 cipher.seek(0,0) + 444 # make it a part of the email + 445 payload=MIMEMultipart.MIMEMultipart('encrypted', + 446 boundary=None, + 447 _subparts=None, + 448 **dict(protocol="application/pgp-encrypted")) + 449 p=MIMEBase.MIMEBase("application",'pgp-encrypted') + 450 p.set_payload("Version: 1\r\n") + 451 payload.attach(p) + 452 p=MIMEBase.MIMEBase("application",'octet-stream') + 453 p.set_payload(cipher.read()) + 454 payload.attach(p) + 455 except errors.GPGMEError, ex: + 456 self.error="GPG error: %s" % ex.getstring() + 457 return False + 458 ####################################################### + 459 # X.509 # + 460 ####################################################### + 461 elif cipher_type == 'x509': + 462 if not sign and not encrypt: + 463 self.error="No sign and no encrypt is set but cipher type to x509" + 464 return False + 465 x509_sign_keyfile=self.settings.x509_sign_keyfile + 466 if self.settings.x509_sign_certfile: + 467 x509_sign_certfile=self.settings.x509_sign_certfile + 468 else: + 469 # if there is no sign certfile we'll assume the + 470 # cert is in keyfile + 471 x509_sign_certfile=self.settings.x509_sign_keyfile + 472 # crypt certfiles could be a string or a list + 473 x509_crypt_certfiles=self.settings.x509_crypt_certfiles + 474 + 475 + 476 # need m2crypto + 477 from M2Crypto import BIO, SMIME, X509 + 478 msg_bio = BIO.MemoryBuffer(payload_in.as_string()) + 479 s = SMIME.SMIME() + 480 + 481 # SIGN + 482 if sign: + 483 #key for signing + 484 try: + 485 s.load_key(x509_sign_keyfile, x509_sign_certfile, callback=lambda x: sign_passphrase) + 486 if encrypt: + 487 p7 = s.sign(msg_bio) + 488 else: + 489 p7 = s.sign(msg_bio,flags=SMIME.PKCS7_DETACHED) + 490 msg_bio = BIO.MemoryBuffer(payload_in.as_string()) # Recreate coz sign() has consumed it. + 491 except Exception,e: + 492 self.error="Something went wrong on signing: <%s>" %str(e) + 493 return False + 494 + 495 # ENCRYPT + 496 if encrypt: + 497 try: + 498 sk = X509.X509_Stack() + 499 if not isinstance(x509_crypt_certfiles, (list, tuple)): + 500 x509_crypt_certfiles = [x509_crypt_certfiles] + 501 + 502 # make an encryption cert's stack + 503 for x in x509_crypt_certfiles: + 504 sk.push(X509.load_cert(x)) + 505 s.set_x509_stack(sk) + 506 + 507 s.set_cipher(SMIME.Cipher('des_ede3_cbc')) + 508 tmp_bio = BIO.MemoryBuffer() + 509 if sign: + 510 s.write(tmp_bio, p7) + 511 else: + 512 tmp_bio.write(payload_in.as_string()) + 513 p7 = s.encrypt(tmp_bio) + 514 except Exception,e: + 515 self.error="Something went wrong on encrypting: <%s>" %str(e) + 516 return False + 517 + 518 # Final stage in sign and encryption + 519 out = BIO.MemoryBuffer() + 520 if encrypt: + 521 s.write(out, p7) + 522 else: + 523 if sign: + 524 s.write(out, p7, msg_bio, SMIME.PKCS7_DETACHED) + 525 else: + 526 out.write('\r\n') + 527 out.write(payload_in.as_string()) + 528 out.close() + 529 st=str(out.read()) + 530 payload=message_from_string(st) + 531 else: + 532 # no cryptography process as usual + 533 payload=payload_in + 534 payload['From'] = encode_header(self.settings.sender.decode(encoding)) + 535 origTo = to[:] + 536 if to: + 537 payload['To'] = encode_header(', '.join(to).decode(encoding)) + 538 if reply_to: + 539 payload['Reply-To'] = encode_header(reply_to.decode(encoding)) + 540 if cc: + 541 payload['Cc'] = encode_header(', '.join(cc).decode(encoding)) + 542 to.extend(cc) + 543 if bcc: + 544 to.extend(bcc) + 545 payload['Subject'] = encode_header(subject.decode(encoding)) + 546 payload['Date'] = time.strftime("%a, %d %b %Y %H:%M:%S +0000", + 547 time.gmtime()) + 548 result = {} + 549 try: + 550 if self.settings.server == 'logging': + 551 logger.warn('email not sent\n%s\nFrom: %s\nTo: %s\n\n%s\n%s\n' % \ + 552 ('-'*40,self.settings.sender, + 553 ', '.join(to),text or html,'-'*40)) + 554 elif self.settings.server == 'gae': + 555 xcc = dict() + 556 if cc: + 557 xcc['cc'] = cc + 558 if bcc: + 559 xcc['bcc'] = bcc + 560 from google.appengine.api import mail + 561 attachments = attachments and [(a.my_filename,a.my_payload) for a in attachments] + 562 if attachments: + 563 result = mail.send_mail(sender=self.settings.sender, to=origTo, + 564 subject=subject, body=text, html=html, + 565 attachments=attachments, **xcc) + 566 elif html: + 567 result = mail.send_mail(sender=self.settings.sender, to=origTo, + 568 subject=subject, body=text, html=html, **xcc) + 569 else: + 570 result = mail.send_mail(sender=self.settings.sender, to=origTo, + 571 subject=subject, body=text, **xcc) + 572 else: + 573 smtp_args = self.settings.server.split(':') + 574 if self.settings.ssl: + 575 server = smtplib.SMTP_SSL(*smtp_args) + 576 else: + 577 server = smtplib.SMTP(*smtp_args) + 578 if self.settings.tls and not self.settings.ssl: + 579 server.ehlo() + 580 server.starttls() + 581 server.ehlo() + 582 if self.settings.login != None: + 583 server.login(*self.settings.login.split(':',1)) + 584 result = server.sendmail(self.settings.sender, to, payload.as_string()) + 585 server.quit() + 586 except Exception, e: + 587 logger.warn('Mail.send failure:%s' % e) + 588 self.result = result + 589 self.error = e + 590 return False + 591 self.result = result + 592 self.error = None + 593 return True +
    594 + 595 +
    596 -class Recaptcha(DIV): +
    597 + 598 API_SSL_SERVER = 'https://www.google.com/recaptcha/api' + 599 API_SERVER = 'http://www.google.com/recaptcha/api' + 600 VERIFY_SERVER = 'http://www.google.com/recaptcha/api/verify' + 601 +
    602 - def __init__( + 603 self, + 604 request, + 605 public_key='', + 606 private_key='', + 607 use_ssl=False, + 608 error=None, + 609 error_message='invalid', + 610 label = 'Verify:', + 611 options = '' + 612 ): +
    613 self.remote_addr = request.env.remote_addr + 614 self.public_key = public_key + 615 self.private_key = private_key + 616 self.use_ssl = use_ssl + 617 self.error = error + 618 self.errors = Storage() + 619 self.error_message = error_message + 620 self.components = [] + 621 self.attributes = {} + 622 self.label = label + 623 self.options = options + 624 self.comment = '' +
    625 +
    626 - def _validate(self): +
    627 + 628 # for local testing: + 629 + 630 recaptcha_challenge_field = \ + 631 self.request_vars.recaptcha_challenge_field + 632 recaptcha_response_field = \ + 633 self.request_vars.recaptcha_response_field + 634 private_key = self.private_key + 635 remoteip = self.remote_addr + 636 if not (recaptcha_response_field and recaptcha_challenge_field + 637 and len(recaptcha_response_field) + 638 and len(recaptcha_challenge_field)): + 639 self.errors['captcha'] = self.error_message + 640 return False + 641 params = urllib.urlencode({ + 642 'privatekey': private_key, + 643 'remoteip': remoteip, + 644 'challenge': recaptcha_challenge_field, + 645 'response': recaptcha_response_field, + 646 }) + 647 request = urllib2.Request( + 648 url=self.VERIFY_SERVER, + 649 data=params, + 650 headers={'Content-type': 'application/x-www-form-urlencoded', + 651 'User-agent': 'reCAPTCHA Python'}) + 652 httpresp = urllib2.urlopen(request) + 653 return_values = httpresp.read().splitlines() + 654 httpresp.close() + 655 return_code = return_values[0] + 656 if return_code == 'true': + 657 del self.request_vars.recaptcha_challenge_field + 658 del self.request_vars.recaptcha_response_field + 659 self.request_vars.captcha = '' + 660 return True + 661 self.errors['captcha'] = self.error_message + 662 return False +
    663 +
    664 - def xml(self): +
    665 public_key = self.public_key + 666 use_ssl = self.use_ssl + 667 error_param = '' + 668 if self.error: + 669 error_param = '&error=%s' % self.error + 670 if use_ssl: + 671 server = self.API_SSL_SERVER + 672 else: + 673 server = self.API_SERVER + 674 captcha = DIV( + 675 SCRIPT("var RecaptchaOptions = {%s};" % self.options), + 676 SCRIPT(_type="text/javascript", + 677 _src="%s/challenge?k=%s%s" % (server,public_key,error_param)), + 678 TAG.noscript(IFRAME(_src="%s/noscript?k=%s%s" % (server,public_key,error_param), + 679 _height="300",_width="500",_frameborder="0"), BR(), + 680 INPUT(_type='hidden', _name='recaptcha_response_field', + 681 _value='manual_challenge')), _id='recaptcha') + 682 if not self.errors.captcha: + 683 return XML(captcha).xml() + 684 else: + 685 captcha.append(DIV(self.errors['captcha'], _class='error')) + 686 return XML(captcha).xml() +
    687 + 688 +
    689 -def addrow(form,a,b,c,style,_id,position=-1): +
    690 if style == "divs": + 691 form[0].insert(position, DIV(DIV(LABEL(a),_class='w2p_fl'), + 692 DIV(b, _class='w2p_fw'), + 693 DIV(c, _class='w2p_fc'), + 694 _id = _id)) + 695 elif style == "table2cols": + 696 form[0].insert(position, TR(LABEL(a),'')) + 697 form[0].insert(position+1, TR(b, _colspan=2, _id = _id)) + 698 elif style == "ul": + 699 form[0].insert(position, LI(DIV(LABEL(a),_class='w2p_fl'), + 700 DIV(b, _class='w2p_fw'), + 701 DIV(c, _class='w2p_fc'), + 702 _id = _id)) + 703 else: + 704 form[0].insert(position, TR(LABEL(a),b,c,_id = _id)) +
    705 + 706 +
    707 -class Auth(object): +
    708 """ + 709 Class for authentication, authorization, role based access control. + 710 + 711 Includes: + 712 + 713 - registration and profile + 714 - login and logout + 715 - username and password retrieval + 716 - event logging + 717 - role creation and assignment + 718 - user defined group/role based permission + 719 + 720 Authentication Example:: + 721 + 722 from contrib.utils import * + 723 mail=Mail() + 724 mail.settings.server='smtp.gmail.com:587' + 725 mail.settings.sender='you@somewhere.com' + 726 mail.settings.login='username:password' + 727 auth=Auth(globals(), db) + 728 auth.settings.mailer=mail + 729 # auth.settings....=... + 730 auth.define_tables() + 731 def authentication(): + 732 return dict(form=auth()) + 733 + 734 exposes: + 735 + 736 - http://.../{application}/{controller}/authentication/login + 737 - http://.../{application}/{controller}/authentication/logout + 738 - http://.../{application}/{controller}/authentication/register + 739 - http://.../{application}/{controller}/authentication/verify_email + 740 - http://.../{application}/{controller}/authentication/retrieve_username + 741 - http://.../{application}/{controller}/authentication/retrieve_password + 742 - http://.../{application}/{controller}/authentication/reset_password + 743 - http://.../{application}/{controller}/authentication/profile + 744 - http://.../{application}/{controller}/authentication/change_password + 745 + 746 On registration a group with role=new_user.id is created + 747 and user is given membership of this group. + 748 + 749 You can create a group with:: + 750 + 751 group_id=auth.add_group('Manager', 'can access the manage action') + 752 auth.add_permission(group_id, 'access to manage') + 753 + 754 Here \"access to manage\" is just a user defined string. + 755 You can give access to a user:: + 756 + 757 auth.add_membership(group_id, user_id) + 758 + 759 If user id is omitted, the logged in user is assumed + 760 + 761 Then you can decorate any action:: + 762 + 763 @auth.requires_permission('access to manage') + 764 def manage(): + 765 return dict() + 766 + 767 You can restrict a permission to a specific table:: + 768 + 769 auth.add_permission(group_id, 'edit', db.sometable) + 770 @auth.requires_permission('edit', db.sometable) + 771 + 772 Or to a specific record:: + 773 + 774 auth.add_permission(group_id, 'edit', db.sometable, 45) + 775 @auth.requires_permission('edit', db.sometable, 45) + 776 + 777 If authorization is not granted calls:: + 778 + 779 auth.settings.on_failed_authorization + 780 + 781 Other options:: + 782 + 783 auth.settings.mailer=None + 784 auth.settings.expiration=3600 # seconds + 785 + 786 ... + 787 + 788 ### these are messages that can be customized + 789 ... + 790 """ + 791 + 792 +
    793 - def url(self, f=None, args=[], vars={}): +
    794 return URL(c=self.settings.controller,f=f,args=args,vars=vars) +
    795 +
    796 - def __init__(self, environment=None, db=None, + 797 controller='default', cas_provider = None): +
    798 """ + 799 auth=Auth(globals(), db) + 800 + 801 - environment is there for legacy but unused (awful) + 802 - db has to be the database where to create tables for authentication + 803 + 804 """ + 805 ## next two lines for backward compatibility + 806 if not db and environment and isinstance(environment,DAL): + 807 db = environment + 808 self.db = db + 809 self.environment = current + 810 request = current.request + 811 session = current.session + 812 auth = session.auth + 813 if auth and auth.last_visit and auth.last_visit + \ + 814 datetime.timedelta(days=0, seconds=auth.expiration) > request.now: + 815 self.user = auth.user + 816 # this is a trick to speed up sessions + 817 if (request.now - auth.last_visit).seconds > (auth.expiration/10): + 818 auth.last_visit = request.now + 819 else: + 820 self.user = None + 821 session.auth = None + 822 settings = self.settings = Settings() + 823 + 824 # ## what happens after login? + 825 + 826 # ## what happens after registration? + 827 + 828 settings.hideerror = False + 829 settings.cas_domains = [request.env.http_host] + 830 settings.cas_provider = cas_provider + 831 settings.extra_fields = {} + 832 settings.actions_disabled = [] + 833 settings.reset_password_requires_verification = False + 834 settings.registration_requires_verification = False + 835 settings.registration_requires_approval = False + 836 settings.alternate_requires_registration = False + 837 settings.create_user_groups = True + 838 + 839 settings.controller = controller + 840 settings.login_url = self.url('user', args='login') + 841 settings.logged_url = self.url('user', args='profile') + 842 settings.download_url = self.url('download') + 843 settings.mailer = None + 844 settings.login_captcha = None + 845 settings.register_captcha = None + 846 settings.retrieve_username_captcha = None + 847 settings.retrieve_password_captcha = None + 848 settings.captcha = None + 849 settings.expiration = 3600 # one hour + 850 settings.long_expiration = 3600*30*24 # one month + 851 settings.remember_me_form = True + 852 settings.allow_basic_login = False + 853 settings.allow_basic_login_only = False + 854 settings.on_failed_authorization = \ + 855 self.url('user',args='not_authorized') + 856 + 857 settings.on_failed_authentication = lambda x: redirect(x) + 858 + 859 settings.formstyle = 'table3cols' + 860 settings.label_separator = ': ' + 861 + 862 # ## table names to be used + 863 + 864 settings.password_field = 'password' + 865 settings.table_user_name = 'auth_user' + 866 settings.table_group_name = 'auth_group' + 867 settings.table_membership_name = 'auth_membership' + 868 settings.table_permission_name = 'auth_permission' + 869 settings.table_event_name = 'auth_event' + 870 settings.table_cas_name = 'auth_cas' + 871 + 872 # ## if none, they will be created + 873 + 874 settings.table_user = None + 875 settings.table_group = None + 876 settings.table_membership = None + 877 settings.table_permission = None + 878 settings.table_event = None + 879 settings.table_cas = None + 880 + 881 # ## + 882 + 883 settings.showid = False + 884 + 885 # ## these should be functions or lambdas + 886 + 887 settings.login_next = self.url('index') + 888 settings.login_onvalidation = [] + 889 settings.login_onaccept = [] + 890 settings.login_methods = [self] + 891 settings.login_form = self + 892 settings.login_email_validate = True + 893 settings.login_userfield = None + 894 + 895 settings.logout_next = self.url('index') + 896 settings.logout_onlogout = None + 897 + 898 settings.register_next = self.url('index') + 899 settings.register_onvalidation = [] + 900 settings.register_onaccept = [] + 901 settings.register_fields = None + 902 + 903 settings.verify_email_next = self.url('user', args='login') + 904 settings.verify_email_onaccept = [] + 905 + 906 settings.profile_next = self.url('index') + 907 settings.profile_onvalidation = [] + 908 settings.profile_onaccept = [] + 909 settings.profile_fields = None + 910 settings.retrieve_username_next = self.url('index') + 911 settings.retrieve_password_next = self.url('index') + 912 settings.request_reset_password_next = self.url('user', args='login') + 913 settings.reset_password_next = self.url('user', args='login') + 914 + 915 settings.change_password_next = self.url('index') + 916 settings.change_password_onvalidation = [] + 917 settings.change_password_onaccept = [] + 918 + 919 settings.retrieve_password_onvalidation = [] + 920 settings.reset_password_onvalidation = [] + 921 + 922 settings.hmac_key = None + 923 settings.lock_keys = True + 924 + 925 + 926 # ## these are messages that can be customized + 927 messages = self.messages = Messages(current.T) + 928 messages.login_button = 'Login' + 929 messages.register_button = 'Register' + 930 messages.password_reset_button = 'Request reset password' + 931 messages.password_change_button = 'Change password' + 932 messages.profile_save_button = 'Save profile' + 933 messages.submit_button = 'Submit' + 934 messages.verify_password = 'Verify Password' + 935 messages.delete_label = 'Check to delete:' + 936 messages.function_disabled = 'Function disabled' + 937 messages.access_denied = 'Insufficient privileges' + 938 messages.registration_verifying = 'Registration needs verification' + 939 messages.registration_pending = 'Registration is pending approval' + 940 messages.login_disabled = 'Login disabled by administrator' + 941 messages.logged_in = 'Logged in' + 942 messages.email_sent = 'Email sent' + 943 messages.unable_to_send_email = 'Unable to send email' + 944 messages.email_verified = 'Email verified' + 945 messages.logged_out = 'Logged out' + 946 messages.registration_successful = 'Registration successful' + 947 messages.invalid_email = 'Invalid email' + 948 messages.unable_send_email = 'Unable to send email' + 949 messages.invalid_login = 'Invalid login' + 950 messages.invalid_user = 'Invalid user' + 951 messages.invalid_password = 'Invalid password' + 952 messages.is_empty = "Cannot be empty" + 953 messages.mismatched_password = "Password fields don't match" + 954 messages.verify_email = \ + 955 'Click on the link http://...verify_email/%(key)s to verify your email' + 956 messages.verify_email_subject = 'Email verification' + 957 messages.username_sent = 'Your username was emailed to you' + 958 messages.new_password_sent = 'A new password was emailed to you' + 959 messages.password_changed = 'Password changed' + 960 messages.retrieve_username = 'Your username is: %(username)s' + 961 messages.retrieve_username_subject = 'Username retrieve' + 962 messages.retrieve_password = 'Your password is: %(password)s' + 963 messages.retrieve_password_subject = 'Password retrieve' + 964 messages.reset_password = \ + 965 'Click on the link http://...reset_password/%(key)s to reset your password' + 966 messages.reset_password_subject = 'Password reset' + 967 messages.invalid_reset_password = 'Invalid reset password' + 968 messages.profile_updated = 'Profile updated' + 969 messages.new_password = 'New password' + 970 messages.old_password = 'Old password' + 971 messages.group_description = \ + 972 'Group uniquely assigned to user %(id)s' + 973 + 974 messages.register_log = 'User %(id)s Registered' + 975 messages.login_log = 'User %(id)s Logged-in' + 976 messages.login_failed_log = None + 977 messages.logout_log = 'User %(id)s Logged-out' + 978 messages.profile_log = 'User %(id)s Profile updated' + 979 messages.verify_email_log = 'User %(id)s Verification email sent' + 980 messages.retrieve_username_log = 'User %(id)s Username retrieved' + 981 messages.retrieve_password_log = 'User %(id)s Password retrieved' + 982 messages.reset_password_log = 'User %(id)s Password reset' + 983 messages.change_password_log = 'User %(id)s Password changed' + 984 messages.add_group_log = 'Group %(group_id)s created' + 985 messages.del_group_log = 'Group %(group_id)s deleted' + 986 messages.add_membership_log = None + 987 messages.del_membership_log = None + 988 messages.has_membership_log = None + 989 messages.add_permission_log = None + 990 messages.del_permission_log = None + 991 messages.has_permission_log = None + 992 messages.impersonate_log = 'User %(id)s is impersonating %(other_id)s' + 993 + 994 messages.label_first_name = 'First name' + 995 messages.label_last_name = 'Last name' + 996 messages.label_username = 'Username' + 997 messages.label_email = 'E-mail' + 998 messages.label_password = 'Password' + 999 messages.label_registration_key = 'Registration key' +1000 messages.label_reset_password_key = 'Reset Password key' +1001 messages.label_registration_id = 'Registration identifier' +1002 messages.label_role = 'Role' +1003 messages.label_description = 'Description' +1004 messages.label_user_id = 'User ID' +1005 messages.label_group_id = 'Group ID' +1006 messages.label_name = 'Name' +1007 messages.label_table_name = 'Table name' +1008 messages.label_record_id = 'Record ID' +1009 messages.label_time_stamp = 'Timestamp' +1010 messages.label_client_ip = 'Client IP' +1011 messages.label_origin = 'Origin' +1012 messages.label_remember_me = "Remember me (for 30 days)" +1013 messages['T'] = current.T +1014 messages.verify_password_comment = 'please input your password again' +1015 messages.lock_keys = True +1016 +1017 # for "remember me" option +1018 response = current.response +1019 if auth and auth.remember: #when user wants to be logged in for longer +1020 response.cookies[response.session_id_name]["expires"] = \ +1021 auth.expiration +1022 +1023 def lazy_user (auth = self): return auth.user_id +1024 reference_user = 'reference %s' % settings.table_user_name +1025 def represent(id,record=None,s=settings): +1026 try: +1027 user = s.table_user(id) +1028 return '%(first_name)s %(last_name)s' % user +1029 except: return id +
    1030 self.signature = db.Table(self.db,'auth_signature', +1031 Field('is_active','boolean',default=True), +1032 Field('created_on','datetime', +1033 default=request.now, +1034 writable=False,readable=False), +1035 Field('created_by', +1036 reference_user, +1037 default=lazy_user,represent=represent, +1038 writable=False,readable=False, +1039 ), +1040 Field('modified_on','datetime', +1041 update=request.now,default=request.now, +1042 writable=False,readable=False), +1043 Field('modified_by', +1044 reference_user,represent=represent, +1045 default=lazy_user,update=lazy_user, +1046 writable=False,readable=False)) +
    1047 +1048 +1049 +
    1050 - def _get_user_id(self): +
    1051 "accessor for auth.user_id" +1052 return self.user and self.user.id or None +
    1053 user_id = property(_get_user_id, doc="user.id or None") +1054 +
    1055 - def _HTTP(self, *a, **b): +
    1056 """ +1057 only used in lambda: self._HTTP(404) +1058 """ +1059 +1060 raise HTTP(*a, **b) +
    1061 +
    1062 - def __call__(self): +
    1063 """ +1064 usage: +1065 +1066 def authentication(): return dict(form=auth()) +1067 """ +1068 +1069 request = current.request +1070 args = request.args +1071 if not args: +1072 redirect(self.url(args='login',vars=request.vars)) +1073 elif args[0] in self.settings.actions_disabled: +1074 raise HTTP(404) +1075 if args[0] in ('login','logout','register','verify_email', +1076 'retrieve_username','retrieve_password', +1077 'reset_password','request_reset_password', +1078 'change_password','profile','groups', +1079 'impersonate','not_authorized'): +1080 return getattr(self,args[0])() +1081 elif args[0]=='cas' and not self.settings.cas_provider: +1082 if args(1) == 'login': return self.cas_login(version=2) +1083 if args(1) == 'validate': return self.cas_validate(version=2) +1084 if args(1) == 'logout': return self.logout() +1085 else: +1086 raise HTTP(404) +
    1087 +
    1088 - def navbar(self,prefix='Welcome',action=None): +
    1089 request = current.request +1090 T = current.T +1091 if isinstance(prefix,str): +1092 prefix = T(prefix) +1093 if not action: +1094 action=URL(request.application,request.controller,'user') +1095 if prefix: +1096 prefix = prefix.strip()+' ' +1097 if self.user_id: +1098 logout=A(T('logout'),_href=action+'/logout') +1099 profile=A(T('profile'),_href=action+'/profile') +1100 password=A(T('password'),_href=action+'/change_password') +1101 bar = SPAN(prefix,self.user.first_name,' [ ', logout, ']',_class='auth_navbar') +1102 if not 'profile' in self.settings.actions_disabled: +1103 bar.insert(4, ' | ') +1104 bar.insert(5, profile) +1105 if not 'change_password' in self.settings.actions_disabled: +1106 bar.insert(-1, ' | ') +1107 bar.insert(-1, password) +1108 else: +1109 login=A(T('login'),_href=action+'/login') +1110 register=A(T('register'),_href=action+'/register') +1111 retrieve_username=A(T('forgot username?'), +1112 _href=action+'/retrieve_username') +1113 lost_password=A(T('lost password?'), +1114 _href=action+'/request_reset_password') +1115 bar = SPAN('[ ',login,' ]',_class='auth_navbar') +1116 +1117 if not 'register' in self.settings.actions_disabled: +1118 bar.insert(2, ' | ') +1119 bar.insert(3, register) +1120 if 'username' in self.settings.table_user.fields() and \ +1121 not 'retrieve_username' in self.settings.actions_disabled: +1122 bar.insert(-1, ' | ') +1123 bar.insert(-1, retrieve_username) +1124 if not 'request_reset_password' in self.settings.actions_disabled: +1125 bar.insert(-1, ' | ') +1126 bar.insert(-1, lost_password) +1127 return bar +
    1128 +
    1129 - def __get_migrate(self, tablename, migrate=True): +
    1130 +1131 if type(migrate).__name__ == 'str': +1132 return (migrate + tablename + '.table') +1133 elif migrate == False: +1134 return False +1135 else: +1136 return True +
    1137 +
    1138 - def define_tables(self, username=False, migrate=True, fake_migrate=False): +
    1139 """ +1140 to be called unless tables are defined manually +1141 +1142 usages:: +1143 +1144 # defines all needed tables and table files +1145 # 'myprefix_auth_user.table', ... +1146 auth.define_tables(migrate='myprefix_') +1147 +1148 # defines all needed tables without migration/table files +1149 auth.define_tables(migrate=False) +1150 +1151 """ +1152 +1153 db = self.db +1154 settings = self.settings +1155 if not settings.table_user_name in db.tables: +1156 passfield = settings.password_field +1157 if username or settings.cas_provider: +1158 table = db.define_table( +1159 settings.table_user_name, +1160 Field('first_name', length=128, default='', +1161 label=self.messages.label_first_name), +1162 Field('last_name', length=128, default='', +1163 label=self.messages.label_last_name), +1164 Field('username', length=128, default='', +1165 label=self.messages.label_username), +1166 Field('email', length=512, default='', +1167 label=self.messages.label_email), +1168 Field(passfield, 'password', length=512, +1169 readable=False, label=self.messages.label_password), +1170 Field('registration_key', length=512, +1171 writable=False, readable=False, default='', +1172 label=self.messages.label_registration_key), +1173 Field('reset_password_key', length=512, +1174 writable=False, readable=False, default='', +1175 label=self.messages.label_reset_password_key), +1176 Field('registration_id', length=512, +1177 writable=False, readable=False, default='', +1178 label=self.messages.label_registration_id), +1179 *settings.extra_fields.get(settings.table_user_name,[]), +1180 **dict( +1181 migrate=self.__get_migrate(settings.table_user_name, +1182 migrate), +1183 fake_migrate=fake_migrate, +1184 format='%(username)s')) +1185 table.username.requires = (IS_MATCH('[\w\.\-]+'), +1186 IS_NOT_IN_DB(db, table.username)) +1187 else: +1188 table = db.define_table( +1189 settings.table_user_name, +1190 Field('first_name', length=128, default='', +1191 label=self.messages.label_first_name), +1192 Field('last_name', length=128, default='', +1193 label=self.messages.label_last_name), +1194 Field('email', length=512, default='', +1195 label=self.messages.label_email), +1196 Field(passfield, 'password', length=512, +1197 readable=False, label=self.messages.label_password), +1198 Field('registration_key', length=512, +1199 writable=False, readable=False, default='', +1200 label=self.messages.label_registration_key), +1201 Field('reset_password_key', length=512, +1202 writable=False, readable=False, default='', +1203 label=self.messages.label_reset_password_key), +1204 *settings.extra_fields.get(settings.table_user_name,[]), +1205 **dict( +1206 migrate=self.__get_migrate(settings.table_user_name, +1207 migrate), +1208 fake_migrate=fake_migrate, +1209 format='%(first_name)s %(last_name)s (%(id)s)')) +1210 table.first_name.requires = \ +1211 IS_NOT_EMPTY(error_message=self.messages.is_empty) +1212 table.last_name.requires = \ +1213 IS_NOT_EMPTY(error_message=self.messages.is_empty) +1214 table[passfield].requires = [CRYPT(key=settings.hmac_key)] +1215 table.email.requires = \ +1216 [IS_EMAIL(error_message=self.messages.invalid_email), +1217 IS_NOT_IN_DB(db, table.email)] +1218 table.registration_key.default = '' +1219 settings.table_user = db[settings.table_user_name] +1220 if not settings.table_group_name in db.tables: +1221 table = db.define_table( +1222 settings.table_group_name, +1223 Field('role', length=512, default='', +1224 label=self.messages.label_role), +1225 Field('description', 'text', +1226 label=self.messages.label_description), +1227 *settings.extra_fields.get(settings.table_group_name,[]), +1228 **dict( +1229 migrate=self.__get_migrate( +1230 settings.table_group_name, migrate), +1231 fake_migrate=fake_migrate, +1232 format = '%(role)s (%(id)s)')) +1233 table.role.requires = IS_NOT_IN_DB(db, '%s.role' +1234 % settings.table_group_name) +1235 settings.table_group = db[settings.table_group_name] +1236 if not settings.table_membership_name in db.tables: +1237 table = db.define_table( +1238 settings.table_membership_name, +1239 Field('user_id', settings.table_user, +1240 label=self.messages.label_user_id), +1241 Field('group_id', settings.table_group, +1242 label=self.messages.label_group_id), +1243 *settings.extra_fields.get(settings.table_membership_name,[]), +1244 **dict( +1245 migrate=self.__get_migrate( +1246 settings.table_membership_name, migrate), +1247 fake_migrate=fake_migrate)) +1248 table.user_id.requires = IS_IN_DB(db, '%s.id' % +1249 settings.table_user_name, +1250 '%(first_name)s %(last_name)s (%(id)s)') +1251 table.group_id.requires = IS_IN_DB(db, '%s.id' % +1252 settings.table_group_name, +1253 '%(role)s (%(id)s)') +1254 settings.table_membership = db[settings.table_membership_name] +1255 if not settings.table_permission_name in db.tables: +1256 table = db.define_table( +1257 settings.table_permission_name, +1258 Field('group_id', settings.table_group, +1259 label=self.messages.label_group_id), +1260 Field('name', default='default', length=512, +1261 label=self.messages.label_name), +1262 Field('table_name', length=512, +1263 label=self.messages.label_table_name), +1264 Field('record_id', 'integer',default=0, +1265 label=self.messages.label_record_id), +1266 *settings.extra_fields.get(settings.table_permission_name,[]), +1267 **dict( +1268 migrate=self.__get_migrate( +1269 settings.table_permission_name, migrate), +1270 fake_migrate=fake_migrate)) +1271 table.group_id.requires = IS_IN_DB(db, '%s.id' % +1272 settings.table_group_name, +1273 '%(role)s (%(id)s)') +1274 table.name.requires = IS_NOT_EMPTY(error_message=self.messages.is_empty) +1275 table.table_name.requires = IS_EMPTY_OR(IS_IN_SET(self.db.tables)) +1276 table.record_id.requires = IS_INT_IN_RANGE(0, 10 ** 9) +1277 settings.table_permission = db[settings.table_permission_name] +1278 if not settings.table_event_name in db.tables: +1279 table = db.define_table( +1280 settings.table_event_name, +1281 Field('time_stamp', 'datetime', +1282 default=current.request.now, +1283 label=self.messages.label_time_stamp), +1284 Field('client_ip', +1285 default=current.request.client, +1286 label=self.messages.label_client_ip), +1287 Field('user_id', settings.table_user, default=None, +1288 label=self.messages.label_user_id), +1289 Field('origin', default='auth', length=512, +1290 label=self.messages.label_origin), +1291 Field('description', 'text', default='', +1292 label=self.messages.label_description), +1293 *settings.extra_fields.get(settings.table_event_name,[]), +1294 **dict( +1295 migrate=self.__get_migrate( +1296 settings.table_event_name, migrate), +1297 fake_migrate=fake_migrate)) +1298 table.user_id.requires = IS_IN_DB(db, '%s.id' % +1299 settings.table_user_name, +1300 '%(first_name)s %(last_name)s (%(id)s)') +1301 table.origin.requires = IS_NOT_EMPTY(error_message=self.messages.is_empty) +1302 table.description.requires = IS_NOT_EMPTY(error_message=self.messages.is_empty) +1303 settings.table_event = db[settings.table_event_name] +1304 now = current.request.now +1305 if settings.cas_domains: +1306 if not settings.table_cas_name in db.tables: +1307 table = db.define_table( +1308 settings.table_cas_name, +1309 Field('user_id', settings.table_user, default=None, +1310 label=self.messages.label_user_id), +1311 Field('created_on','datetime',default=now), +1312 Field('url',requires=IS_URL()), +1313 Field('uuid'), +1314 *settings.extra_fields.get(settings.table_cas_name,[]), +1315 **dict( +1316 migrate=self.__get_migrate( +1317 settings.table_event_name, migrate), +1318 fake_migrate=fake_migrate)) +1319 table.user_id.requires = IS_IN_DB(db, '%s.id' % \ +1320 settings.table_user_name, +1321 '%(first_name)s %(last_name)s (%(id)s)') +1322 settings.table_cas = db[settings.table_cas_name] +1323 if settings.cas_provider: +1324 settings.actions_disabled = \ +1325 ['profile','register','change_password','request_reset_password'] +1326 from gluon.contrib.login_methods.cas_auth import CasAuth +1327 maps = dict((name,lambda v,n=name:v.get(n,None)) for name in \ +1328 settings.table_user.fields if name!='id' \ +1329 and settings.table_user[name].readable) +1330 maps['registration_id'] = \ +1331 lambda v,p=settings.cas_provider:'%s/%s' % (p,v['user']) +1332 settings.login_form = CasAuth( +1333 casversion = 2, +1334 urlbase = settings.cas_provider, +1335 actions=['login','validate','logout'], +1336 maps=maps) +
    1337 +1338 +
    1339 - def log_event(self, description, origin='auth'): +
    1340 """ +1341 usage:: +1342 +1343 auth.log_event(description='this happened', origin='auth') +1344 """ +1345 +1346 if self.is_logged_in(): +1347 user_id = self.user.id +1348 else: +1349 user_id = None # user unknown +1350 self.settings.table_event.insert(description=description, +1351 origin=origin, user_id=user_id) +
    1352 +
    1353 - def get_or_create_user(self, keys): +
    1354 """ +1355 Used for alternate login methods: +1356 If the user exists already then password is updated. +1357 If the user doesn't yet exist, then they are created. +1358 """ +1359 table_user = self.settings.table_user +1360 if 'registration_id' in table_user.fields() and \ +1361 'registration_id' in keys: +1362 username = 'registration_id' +1363 elif 'username' in table_user.fields(): +1364 username = 'username' +1365 elif 'email' in table_user.fields(): +1366 username = 'email' +1367 else: +1368 raise SyntaxError, "user must have username or email" +1369 passfield = self.settings.password_field +1370 user = self.db(table_user[username] == keys[username]).select().first() +1371 keys['registration_key']='' +1372 if user: +1373 user.update_record(**table_user._filter_fields(keys)) +1374 else: +1375 if not 'first_name' in keys and 'first_name' in table_user.fields: +1376 keys['first_name'] = keys[username] +1377 user_id = table_user.insert(**table_user._filter_fields(keys)) +1378 user = self.user = table_user[user_id] +1379 if self.settings.create_user_groups: +1380 group_id = self.add_group("user_%s" % user_id) +1381 self.add_membership(group_id, user_id) +1382 return user +
    1383 +
    1384 - def basic(self): +
    1385 if not self.settings.allow_basic_login: +1386 return False +1387 basic = current.request.env.http_authorization +1388 if not basic or not basic[:6].lower() == 'basic ': +1389 return False +1390 (username, password) = base64.b64decode(basic[6:]).split(':') +1391 return self.login_bare(username, password) +
    1392 +
    1393 - def login_bare(self, username, password): +
    1394 """ +1395 logins user +1396 """ +1397 +1398 request = current.request +1399 session = current.session +1400 table_user = self.settings.table_user +1401 if self.settings.login_userfield: +1402 userfield = self.settings.login_userfield +1403 elif 'username' in table_user.fields: +1404 userfield = 'username' +1405 else: +1406 userfield = 'email' +1407 passfield = self.settings.password_field +1408 user = self.db(table_user[userfield] == username).select().first() +1409 password = table_user[passfield].validate(password)[0] +1410 if user: +1411 if not user.registration_key and user[passfield] == password: +1412 user = Storage(table_user._filter_fields(user, id=True)) +1413 session.auth = Storage(user=user, last_visit=request.now, +1414 expiration=self.settings.expiration, +1415 hmac_key = web2py_uuid()) +1416 self.user = user +1417 return user +1418 return False +
    1419 +
    1420 - def cas_login( +1421 self, +1422 next=DEFAULT, +1423 onvalidation=DEFAULT, +1424 onaccept=DEFAULT, +1425 log=DEFAULT, +1426 version=2, +1427 ): +
    1428 request, session = current.request, current.session +1429 db, table = self.db, self.settings.table_cas +1430 session._cas_service = request.vars.service or session._cas_service +1431 if not request.env.http_host in self.settings.cas_domains or \ +1432 not session._cas_service: +1433 raise HTTP(403,'not authorized') +1434 def allow_access(): +1435 row = table(url=session._cas_service,user_id=self.user.id) +1436 if row: +1437 row.update_record(created_on=request.now) +1438 uuid = row.uuid +1439 else: +1440 uuid = web2py_uuid() +1441 table.insert(url=session._cas_service, user_id=self.user.id, +1442 uuid=uuid, created_on=request.now) +1443 url = session._cas_service +1444 del session._cas_service +1445 redirect(url+"?ticket="+uuid) +
    1446 if self.is_logged_in(): +1447 allow_access() +1448 def cas_onaccept(form, onaccept=onaccept): +1449 if onaccept!=DEFAULT: onaccept(form) +1450 allow_access() +1451 return self.login(next,onvalidation,cas_onaccept,log) +1452 +1453 +
    1454 - def cas_validate(self,version=2): +
    1455 request = current.request +1456 db, table = self.db, self.settings.table_cas +1457 current.response.headers['Content-Type']='text' +1458 ticket = table(uuid=request.vars.ticket) +1459 url = request.env.path_info.rsplit('/',1)[0] +1460 if ticket: # and ticket.created_on>request.now-datetime.timedelta(60): +1461 user = self.settings.table_user(ticket.user_id) +1462 fullname = user.first_name+' '+user.last_name +1463 if version==1: +1464 raise HTTP(200,'yes\n%s:%s:%s'%(user.id,user.email,fullname)) +1465 # assume version 2 +1466 username = user.get('username',user.email) +1467 raise HTTP(200,'<?xml version="1.0" encoding="UTF-8"?>\n'+\ +1468 TAG['cas:serviceResponse']( +1469 TAG['cas:authenticationSuccess']( +1470 TAG['cas:user'](username), +1471 *[TAG['cas:'+field.name](user[field.name]) \ +1472 for field in self.settings.table_user \ +1473 if field.readable]), +1474 **{'_xmlns:cas':'http://www.yale.edu/tp/cas'}).xml()) +1475 if version==1: +1476 raise HTTP(200,'no\n') +1477 # assume version 2 +1478 raise HTTP(200,'<?xml version="1.0" encoding="UTF-8"?>\n'+\ +1479 TAG['cas:serviceResponse']( +1480 TAG['cas:authenticationFailure']( +1481 'Ticket %s not recognized' % ticket, +1482 _code='INVALID TICKET'), +1483 **{'_xmlns:cas':'http://www.yale.edu/tp/cas'}).xml()) +
    1484 +1485 +
    1486 - def login( +1487 self, +1488 next=DEFAULT, +1489 onvalidation=DEFAULT, +1490 onaccept=DEFAULT, +1491 log=DEFAULT, +1492 ): +
    1493 """ +1494 returns a login form +1495 +1496 .. method:: Auth.login([next=DEFAULT [, onvalidation=DEFAULT +1497 [, onaccept=DEFAULT [, log=DEFAULT]]]]) +1498 +1499 """ +1500 +1501 table_user = self.settings.table_user +1502 if self.settings.login_userfield: +1503 username = self.settings.login_userfield +1504 elif 'username' in table_user.fields: +1505 username = 'username' +1506 else: +1507 username = 'email' +1508 if 'username' in table_user.fields or not self.settings.login_email_validate: +1509 tmpvalidator = IS_NOT_EMPTY(error_message=self.messages.is_empty) +1510 else: +1511 tmpvalidator = IS_EMAIL(error_message=self.messages.invalid_email) +1512 old_requires = table_user[username].requires +1513 table_user[username].requires = tmpvalidator +1514 +1515 request = current.request +1516 response = current.response +1517 session = current.session +1518 +1519 passfield = self.settings.password_field +1520 if next == DEFAULT: +1521 next = request.get_vars._next \ +1522 or request.post_vars._next \ +1523 or self.settings.login_next +1524 if onvalidation == DEFAULT: +1525 onvalidation = self.settings.login_onvalidation +1526 if onaccept == DEFAULT: +1527 onaccept = self.settings.login_onaccept +1528 if log == DEFAULT: +1529 log = self.messages.login_log +1530 +1531 user = None # default +1532 +1533 # do we use our own login form, or from a central source? +1534 if self.settings.login_form == self: +1535 form = SQLFORM( +1536 table_user, +1537 fields=[username, passfield], +1538 hidden=dict(_next=next), +1539 showid=self.settings.showid, +1540 submit_button=self.messages.login_button, +1541 delete_label=self.messages.delete_label, +1542 formstyle=self.settings.formstyle, +1543 separator=self.settings.label_separator +1544 ) +1545 +1546 if self.settings.remember_me_form: +1547 ## adds a new input checkbox "remember me for longer" +1548 addrow(form,XML("&nbsp;"), +1549 DIV(XML("&nbsp;"), +1550 INPUT(_type='checkbox', +1551 _class='checkbox', +1552 _id="auth_user_remember", +1553 _name="remember", +1554 ), +1555 XML("&nbsp;&nbsp;"), +1556 LABEL( +1557 self.messages.label_remember_me, +1558 _for="auth_user_remember", +1559 )),"", +1560 self.settings.formstyle, +1561 'auth_user_remember__row') +1562 +1563 captcha = self.settings.login_captcha or \ +1564 (self.settings.login_captcha!=False and self.settings.captcha) +1565 if captcha: +1566 addrow(form, captcha.label, captcha, captcha.comment, self.settings.formstyle,'captcha__row') +1567 accepted_form = False +1568 +1569 if form.accepts(request, session, +1570 formname='login', dbio=False, +1571 onvalidation=onvalidation, +1572 hideerror=self.settings.hideerror): +1573 +1574 accepted_form = True +1575 # check for username in db +1576 user = self.db(table_user[username] == form.vars[username]).select().first() +1577 if user: +1578 # user in db, check if registration pending or disabled +1579 temp_user = user +1580 if temp_user.registration_key == 'pending': +1581 response.flash = self.messages.registration_pending +1582 return form +1583 elif temp_user.registration_key in ('disabled','blocked'): +1584 response.flash = self.messages.login_disabled +1585 return form +1586 elif temp_user.registration_key!=None and \ +1587 temp_user.registration_key.strip(): +1588 response.flash = \ +1589 self.messages.registration_verifying +1590 return form +1591 # try alternate logins 1st as these have the +1592 # current version of the password +1593 user = None +1594 for login_method in self.settings.login_methods: +1595 if login_method != self and \ +1596 login_method(request.vars[username], +1597 request.vars[passfield]): +1598 if not self in self.settings.login_methods: +1599 # do not store password in db +1600 form.vars[passfield] = None +1601 user = self.get_or_create_user(form.vars) +1602 break +1603 if not user: +1604 # alternates have failed, maybe because service inaccessible +1605 if self.settings.login_methods[0] == self: +1606 # try logging in locally using cached credentials +1607 if temp_user[passfield] == form.vars.get(passfield, ''): +1608 # success +1609 user = temp_user +1610 else: +1611 # user not in db +1612 if not self.settings.alternate_requires_registration: +1613 # we're allowed to auto-register users from external systems +1614 for login_method in self.settings.login_methods: +1615 if login_method != self and \ +1616 login_method(request.vars[username], +1617 request.vars[passfield]): +1618 if not self in self.settings.login_methods: +1619 # do not store password in db +1620 form.vars[passfield] = None +1621 user = self.get_or_create_user(form.vars) +1622 break +1623 if not user: +1624 if self.settings.login_failed_log: +1625 self.log_event(self.settings.login_failed_log % request.post_vars) +1626 # invalid login +1627 session.flash = self.messages.invalid_login +1628 redirect(self.url(args=request.args,vars=request.get_vars)) +1629 +1630 else: +1631 # use a central authentication server +1632 cas = self.settings.login_form +1633 cas_user = cas.get_user() +1634 +1635 if cas_user: +1636 cas_user[passfield] = None +1637 user = self.get_or_create_user(table_user._filter_fields(cas_user)) +1638 elif hasattr(cas,'login_form'): +1639 return cas.login_form() +1640 else: +1641 # we need to pass through login again before going on +1642 next = self.url('user',args='login',vars=dict(_next=next)) +1643 redirect(cas.login_url(next)) +1644 +1645 +1646 # process authenticated users +1647 if user: +1648 user = Storage(table_user._filter_fields(user, id=True)) +1649 +1650 if log: +1651 self.log_event(log % user) +1652 +1653 # process authenticated users +1654 # user wants to be logged in for longer +1655 session.auth = Storage( +1656 user = user, +1657 last_visit = request.now, +1658 expiration = self.settings.long_expiration, +1659 remember = request.vars.has_key("remember"), +1660 hmac_key = web2py_uuid() +1661 ) +1662 +1663 self.user = user +1664 session.flash = self.messages.logged_in +1665 +1666 # how to continue +1667 if self.settings.login_form == self: +1668 if accepted_form: +1669 callback(onaccept,form) +1670 if isinstance(next, (list, tuple)): +1671 # fix issue with 2.6 +1672 next = next[0] +1673 if next and not next[0] == '/' and next[:4] != 'http': +1674 next = self.url(next.replace('[id]', str(form.vars.id))) +1675 redirect(next) +1676 table_user[username].requires = old_requires +1677 return form +1678 elif user: +1679 callback(onaccept,None) +1680 redirect(next) +
    1681 +
    1682 - def logout(self, next=DEFAULT, onlogout=DEFAULT, log=DEFAULT): +
    1683 """ +1684 logout and redirects to login +1685 +1686 .. method:: Auth.logout ([next=DEFAULT[, onlogout=DEFAULT[, +1687 log=DEFAULT]]]) +1688 +1689 """ +1690 +1691 if next == DEFAULT: +1692 next = self.settings.logout_next +1693 if onlogout == DEFAULT: +1694 onlogout = self.settings.logout_onlogout +1695 if onlogout: +1696 onlogout(self.user) +1697 if log == DEFAULT: +1698 log = self.messages.logout_log +1699 if log and self.user: +1700 self.log_event(log % self.user) +1701 +1702 if self.settings.login_form != self: +1703 cas = self.settings.login_form +1704 cas_user = cas.get_user() +1705 if cas_user: +1706 next = cas.logout_url(next) +1707 +1708 current.session.auth = None +1709 current.session.flash = self.messages.logged_out +1710 if next: +1711 redirect(next) +
    1712 +
    1713 - def register( +1714 self, +1715 next=DEFAULT, +1716 onvalidation=DEFAULT, +1717 onaccept=DEFAULT, +1718 log=DEFAULT, +1719 ): +
    1720 """ +1721 returns a registration form +1722 +1723 .. method:: Auth.register([next=DEFAULT [, onvalidation=DEFAULT +1724 [, onaccept=DEFAULT [, log=DEFAULT]]]]) +1725 +1726 """ +1727 +1728 table_user = self.settings.table_user +1729 request = current.request +1730 response = current.response +1731 session = current.session +1732 if self.is_logged_in(): +1733 redirect(self.settings.logged_url) +1734 if next == DEFAULT: +1735 next = request.get_vars._next \ +1736 or request.post_vars._next \ +1737 or self.settings.register_next +1738 if onvalidation == DEFAULT: +1739 onvalidation = self.settings.register_onvalidation +1740 if onaccept == DEFAULT: +1741 onaccept = self.settings.register_onaccept +1742 if log == DEFAULT: +1743 log = self.messages.register_log +1744 +1745 passfield = self.settings.password_field +1746 formstyle = self.settings.formstyle +1747 form = SQLFORM(table_user, +1748 fields = self.settings.register_fields, +1749 hidden=dict(_next=next), +1750 showid=self.settings.showid, +1751 submit_button=self.messages.register_button, +1752 delete_label=self.messages.delete_label, +1753 formstyle=formstyle, +1754 separator=self.settings.label_separator +1755 ) +1756 for i, row in enumerate(form[0].components): +1757 item = row.element('input',_name=passfield) +1758 if item: +1759 form.custom.widget.password_two = \ +1760 INPUT(_name="password_two", _type="password", +1761 requires=IS_EXPR('value==%s' % \ +1762 repr(request.vars.get(passfield, None)), +1763 error_message=self.messages.mismatched_password)) +1764 +1765 addrow(form, self.messages.verify_password + ':', +1766 form.custom.widget.password_two, +1767 self.messages.verify_password_comment, +1768 formstyle, +1769 '%s_%s__row' % (table_user, 'password_two'), +1770 position=i+1) +1771 break +1772 captcha = self.settings.register_captcha or self.settings.captcha +1773 if captcha: +1774 addrow(form, captcha.label, captcha, captcha.comment,self.settings.formstyle, 'captcha__row') +1775 +1776 table_user.registration_key.default = key = web2py_uuid() +1777 if form.accepts(request, session, formname='register', +1778 onvalidation=onvalidation,hideerror=self.settings.hideerror): +1779 description = self.messages.group_description % form.vars +1780 if self.settings.create_user_groups: +1781 group_id = self.add_group("user_%s" % form.vars.id, description) +1782 self.add_membership(group_id, form.vars.id) +1783 if self.settings.registration_requires_verification: +1784 if not self.settings.mailer or \ +1785 not self.settings.mailer.send(to=form.vars.email, +1786 subject=self.messages.verify_email_subject, +1787 message=self.messages.verify_email +1788 % dict(key=key)): +1789 self.db.rollback() +1790 response.flash = self.messages.unable_send_email +1791 return form +1792 session.flash = self.messages.email_sent +1793 elif self.settings.registration_requires_approval: +1794 table_user[form.vars.id] = dict(registration_key='pending') +1795 session.flash = self.messages.registration_pending +1796 else: +1797 table_user[form.vars.id] = dict(registration_key='') +1798 session.flash = self.messages.registration_successful +1799 table_user = self.settings.table_user +1800 if 'username' in table_user.fields: +1801 username = 'username' +1802 else: +1803 username = 'email' +1804 user = self.db(table_user[username] == form.vars[username]).select().first() +1805 user = Storage(table_user._filter_fields(user, id=True)) +1806 session.auth = Storage(user=user, last_visit=request.now, +1807 expiration=self.settings.expiration, +1808 hmac_key = web2py_uuid()) +1809 self.user = user +1810 session.flash = self.messages.logged_in +1811 if log: +1812 self.log_event(log % form.vars) +1813 callback(onaccept,form) +1814 if not next: +1815 next = self.url(args = request.args) +1816 elif isinstance(next, (list, tuple)): ### fix issue with 2.6 +1817 next = next[0] +1818 elif next and not next[0] == '/' and next[:4] != 'http': +1819 next = self.url(next.replace('[id]', str(form.vars.id))) +1820 redirect(next) +1821 return form +
    1822 +
    1823 - def is_logged_in(self): +
    1824 """ +1825 checks if the user is logged in and returns True/False. +1826 if so user is in auth.user as well as in session.auth.user +1827 """ +1828 +1829 if self.user: +1830 return True +1831 return False +
    1832 +
    1833 - def verify_email( +1834 self, +1835 next=DEFAULT, +1836 onaccept=DEFAULT, +1837 log=DEFAULT, +1838 ): +
    1839 """ +1840 action user to verify the registration email, XXXXXXXXXXXXXXXX +1841 +1842 .. method:: Auth.verify_email([next=DEFAULT [, onvalidation=DEFAULT +1843 [, onaccept=DEFAULT [, log=DEFAULT]]]]) +1844 +1845 """ +1846 +1847 key = current.request.args[-1] +1848 table_user = self.settings.table_user +1849 user = self.db(table_user.registration_key == key).select().first() +1850 if not user: +1851 redirect(self.settings.login_url) +1852 if self.settings.registration_requires_approval: +1853 user.update_record(registration_key = 'pending') +1854 current.session.flash = self.messages.registration_pending +1855 else: +1856 user.update_record(registration_key = '') +1857 current.session.flash = self.messages.email_verified +1858 if log == DEFAULT: +1859 log = self.messages.verify_email_log +1860 if next == DEFAULT: +1861 next = self.settings.verify_email_next +1862 if onaccept == DEFAULT: +1863 onaccept = self.settings.verify_email_onaccept +1864 if log: +1865 self.log_event(log % user) +1866 callback(onaccept,user) +1867 redirect(next) +
    1868 +
    1869 - def retrieve_username( +1870 self, +1871 next=DEFAULT, +1872 onvalidation=DEFAULT, +1873 onaccept=DEFAULT, +1874 log=DEFAULT, +1875 ): +
    1876 """ +1877 returns a form to retrieve the user username +1878 (only if there is a username field) +1879 +1880 .. method:: Auth.retrieve_username([next=DEFAULT +1881 [, onvalidation=DEFAULT [, onaccept=DEFAULT [, log=DEFAULT]]]]) +1882 +1883 """ +1884 +1885 table_user = self.settings.table_user +1886 if not 'username' in table_user.fields: +1887 raise HTTP(404) +1888 request = current.request +1889 response = current.response +1890 session = current.session +1891 captcha = self.settings.retrieve_username_captcha or \ +1892 (self.settings.retrieve_username_captcha!=False and self.settings.captcha) +1893 if not self.settings.mailer: +1894 response.flash = self.messages.function_disabled +1895 return '' +1896 if next == DEFAULT: +1897 next = request.get_vars._next \ +1898 or request.post_vars._next \ +1899 or self.settings.retrieve_username_next +1900 if onvalidation == DEFAULT: +1901 onvalidation = self.settings.retrieve_username_onvalidation +1902 if onaccept == DEFAULT: +1903 onaccept = self.settings.retrieve_username_onaccept +1904 if log == DEFAULT: +1905 log = self.messages.retrieve_username_log +1906 old_requires = table_user.email.requires +1907 table_user.email.requires = [IS_IN_DB(self.db, table_user.email, +1908 error_message=self.messages.invalid_email)] +1909 form = SQLFORM(table_user, +1910 fields=['email'], +1911 hidden=dict(_next=next), +1912 showid=self.settings.showid, +1913 submit_button=self.messages.submit_button, +1914 delete_label=self.messages.delete_label, +1915 formstyle=self.settings.formstyle, +1916 separator=self.settings.label_separator +1917 ) +1918 if captcha: +1919 addrow(form, captcha.label, captcha, captcha.comment,self.settings.formstyle, 'captcha__row') +1920 +1921 if form.accepts(request, session, +1922 formname='retrieve_username', dbio=False, +1923 onvalidation=onvalidation,hideerror=self.settings.hideerror): +1924 user = self.db(table_user.email == form.vars.email).select().first() +1925 if not user: +1926 current.session.flash = \ +1927 self.messages.invalid_email +1928 redirect(self.url(args=request.args)) +1929 username = user.username +1930 self.settings.mailer.send(to=form.vars.email, +1931 subject=self.messages.retrieve_username_subject, +1932 message=self.messages.retrieve_username +1933 % dict(username=username)) +1934 session.flash = self.messages.email_sent +1935 if log: +1936 self.log_event(log % user) +1937 callback(onaccept,form) +1938 if not next: +1939 next = self.url(args = request.args) +1940 elif isinstance(next, (list, tuple)): ### fix issue with 2.6 +1941 next = next[0] +1942 elif next and not next[0] == '/' and next[:4] != 'http': +1943 next = self.url(next.replace('[id]', str(form.vars.id))) +1944 redirect(next) +1945 table_user.email.requires = old_requires +1946 return form +
    1947 +
    1948 - def random_password(self): +
    1949 import string +1950 import random +1951 password = '' +1952 specials=r'!#$*' +1953 for i in range(0,3): +1954 password += random.choice(string.lowercase) +1955 password += random.choice(string.uppercase) +1956 password += random.choice(string.digits) +1957 password += random.choice(specials) +1958 return ''.join(random.sample(password,len(password))) +
    1959 +
    1960 - def reset_password_deprecated( +1961 self, +1962 next=DEFAULT, +1963 onvalidation=DEFAULT, +1964 onaccept=DEFAULT, +1965 log=DEFAULT, +1966 ): +
    1967 """ +1968 returns a form to reset the user password (deprecated) +1969 +1970 .. method:: Auth.reset_password_deprecated([next=DEFAULT +1971 [, onvalidation=DEFAULT [, onaccept=DEFAULT [, log=DEFAULT]]]]) +1972 +1973 """ +1974 +1975 table_user = self.settings.table_user +1976 request = current.request +1977 response = current.response +1978 session = current.session +1979 if not self.settings.mailer: +1980 response.flash = self.messages.function_disabled +1981 return '' +1982 if next == DEFAULT: +1983 next = request.get_vars._next \ +1984 or request.post_vars._next \ +1985 or self.settings.retrieve_password_next +1986 if onvalidation == DEFAULT: +1987 onvalidation = self.settings.retrieve_password_onvalidation +1988 if onaccept == DEFAULT: +1989 onaccept = self.settings.retrieve_password_onaccept +1990 if log == DEFAULT: +1991 log = self.messages.retrieve_password_log +1992 old_requires = table_user.email.requires +1993 table_user.email.requires = [IS_IN_DB(self.db, table_user.email, +1994 error_message=self.messages.invalid_email)] +1995 form = SQLFORM(table_user, +1996 fields=['email'], +1997 hidden=dict(_next=next), +1998 showid=self.settings.showid, +1999 submit_button=self.messages.submit_button, +2000 delete_label=self.messages.delete_label, +2001 formstyle=self.settings.formstyle, +2002 separator=self.settings.label_separator +2003 ) +2004 if form.accepts(request, session, +2005 formname='retrieve_password', dbio=False, +2006 onvalidation=onvalidation,hideerror=self.settings.hideerror): +2007 user = self.db(table_user.email == form.vars.email).select().first() +2008 if not user: +2009 current.session.flash = \ +2010 self.messages.invalid_email +2011 redirect(self.url(args=request.args)) +2012 elif user.registration_key in ('pending','disabled','blocked'): +2013 current.session.flash = \ +2014 self.messages.registration_pending +2015 redirect(self.url(args=request.args)) +2016 password = self.random_password() +2017 passfield = self.settings.password_field +2018 d = {passfield: table_user[passfield].validate(password)[0], +2019 'registration_key': ''} +2020 user.update_record(**d) +2021 if self.settings.mailer and \ +2022 self.settings.mailer.send(to=form.vars.email, +2023 subject=self.messages.retrieve_password_subject, +2024 message=self.messages.retrieve_password \ +2025 % dict(password=password)): +2026 session.flash = self.messages.email_sent +2027 else: +2028 session.flash = self.messages.unable_to_send_email +2029 if log: +2030 self.log_event(log % user) +2031 callback(onaccept,form) +2032 if not next: +2033 next = self.url(args = request.args) +2034 elif isinstance(next, (list, tuple)): ### fix issue with 2.6 +2035 next = next[0] +2036 elif next and not next[0] == '/' and next[:4] != 'http': +2037 next = self.url(next.replace('[id]', str(form.vars.id))) +2038 redirect(next) +2039 table_user.email.requires = old_requires +2040 return form +
    2041 +
    2042 - def reset_password( +2043 self, +2044 next=DEFAULT, +2045 onvalidation=DEFAULT, +2046 onaccept=DEFAULT, +2047 log=DEFAULT, +2048 ): +
    2049 """ +2050 returns a form to reset the user password +2051 +2052 .. method:: Auth.reset_password([next=DEFAULT +2053 [, onvalidation=DEFAULT [, onaccept=DEFAULT [, log=DEFAULT]]]]) +2054 +2055 """ +2056 +2057 table_user = self.settings.table_user +2058 request = current.request +2059 # response = current.response +2060 session = current.session +2061 +2062 if next == DEFAULT: +2063 next = request.get_vars._next \ +2064 or request.post_vars._next \ +2065 or self.settings.reset_password_next +2066 +2067 try: +2068 key = request.vars.key or request.args[-1] +2069 t0 = int(key.split('-')[0]) +2070 if time.time()-t0 > 60*60*24: raise Exception +2071 user = self.db(table_user.reset_password_key == key).select().first() +2072 if not user: raise Exception +2073 except Exception: +2074 session.flash = self.messages.invalid_reset_password +2075 redirect(next) +2076 passfield = self.settings.password_field +2077 form = SQLFORM.factory( +2078 Field('new_password', 'password', +2079 label=self.messages.new_password, +2080 requires=self.settings.table_user[passfield].requires), +2081 Field('new_password2', 'password', +2082 label=self.messages.verify_password, +2083 requires=[IS_EXPR('value==%s' % repr(request.vars.new_password), +2084 self.messages.mismatched_password)]), +2085 submit_button=self.messages.password_reset_button, +2086 formstyle=self.settings.formstyle, +2087 separator=self.settings.label_separator +2088 ) +2089 if form.accepts(request,session,hideerror=self.settings.hideerror): +2090 user.update_record(**{passfield:form.vars.new_password, +2091 'registration_key':'', +2092 'reset_password_key':''}) +2093 session.flash = self.messages.password_changed +2094 redirect(next) +2095 return form +
    2096 +
    2097 - def request_reset_password( +2098 self, +2099 next=DEFAULT, +2100 onvalidation=DEFAULT, +2101 onaccept=DEFAULT, +2102 log=DEFAULT, +2103 ): +
    2104 """ +2105 returns a form to reset the user password +2106 +2107 .. method:: Auth.reset_password([next=DEFAULT +2108 [, onvalidation=DEFAULT [, onaccept=DEFAULT [, log=DEFAULT]]]]) +2109 +2110 """ +2111 +2112 table_user = self.settings.table_user +2113 request = current.request +2114 response = current.response +2115 session = current.session +2116 captcha = self.settings.retrieve_password_captcha or \ +2117 (self.settings.retrieve_password_captcha!=False and self.settings.captcha) +2118 +2119 if next == DEFAULT: +2120 next = request.get_vars._next \ +2121 or request.post_vars._next \ +2122 or self.settings.request_reset_password_next +2123 +2124 if not self.settings.mailer: +2125 response.flash = self.messages.function_disabled +2126 return '' +2127 if onvalidation == DEFAULT: +2128 onvalidation = self.settings.reset_password_onvalidation +2129 if onaccept == DEFAULT: +2130 onaccept = self.settings.reset_password_onaccept +2131 if log == DEFAULT: +2132 log = self.messages.reset_password_log +2133 # old_requires = table_user.email.requires <<< perhaps should be restored +2134 table_user.email.requires = [ +2135 IS_EMAIL(error_message=self.messages.invalid_email), +2136 IS_IN_DB(self.db, table_user.email, +2137 error_message=self.messages.invalid_email)] +2138 form = SQLFORM(table_user, +2139 fields=['email'], +2140 hidden=dict(_next=next), +2141 showid=self.settings.showid, +2142 submit_button=self.messages.password_reset_button, +2143 delete_label=self.messages.delete_label, +2144 formstyle=self.settings.formstyle, +2145 separator=self.settings.label_separator +2146 ) +2147 if captcha: +2148 addrow(form, captcha.label, captcha, captcha.comment, self.settings.formstyle,'captcha__row') +2149 if form.accepts(request, session, +2150 formname='reset_password', dbio=False, +2151 onvalidation=onvalidation, +2152 hideerror=self.settings.hideerror): +2153 user = self.db(table_user.email == form.vars.email).select().first() +2154 if not user: +2155 session.flash = self.messages.invalid_email +2156 redirect(self.url(args=request.args)) +2157 elif user.registration_key in ('pending','disabled','blocked'): +2158 session.flash = self.messages.registration_pending +2159 redirect(self.url(args=request.args)) +2160 reset_password_key = str(int(time.time()))+'-' + web2py_uuid() +2161 +2162 if self.settings.mailer.send(to=form.vars.email, +2163 subject=self.messages.reset_password_subject, +2164 message=self.messages.reset_password % \ +2165 dict(key=reset_password_key)): +2166 session.flash = self.messages.email_sent +2167 user.update_record(reset_password_key=reset_password_key) +2168 else: +2169 session.flash = self.messages.unable_to_send_email +2170 if log: +2171 self.log_event(log % user) +2172 callback(onaccept,form) +2173 if not next: +2174 next = self.url(args = request.args) +2175 elif isinstance(next, (list, tuple)): ### fix issue with 2.6 +2176 next = next[0] +2177 elif next and not next[0] == '/' and next[:4] != 'http': +2178 next = self.url(next.replace('[id]', str(form.vars.id))) +2179 redirect(next) +2180 # old_requires = table_user.email.requires +2181 return form +
    2182 +
    2183 - def retrieve_password( +2184 self, +2185 next=DEFAULT, +2186 onvalidation=DEFAULT, +2187 onaccept=DEFAULT, +2188 log=DEFAULT, +2189 ): +
    2190 if self.settings.reset_password_requires_verification: +2191 return self.request_reset_password(next,onvalidation,onaccept,log) +2192 else: +2193 return self.reset_password_deprecated(next,onvalidation,onaccept,log) +
    2194 +
    2195 - def change_password( +2196 self, +2197 next=DEFAULT, +2198 onvalidation=DEFAULT, +2199 onaccept=DEFAULT, +2200 log=DEFAULT, +2201 ): +
    2202 """ +2203 returns a form that lets the user change password +2204 +2205 .. method:: Auth.change_password([next=DEFAULT[, onvalidation=DEFAULT[, +2206 onaccept=DEFAULT[, log=DEFAULT]]]]) +2207 """ +2208 +2209 if not self.is_logged_in(): +2210 redirect(self.settings.login_url) +2211 db = self.db +2212 table_user = self.settings.table_user +2213 usern = self.settings.table_user_name +2214 s = db(table_user.id == self.user.id) +2215 +2216 request = current.request +2217 session = current.session +2218 if next == DEFAULT: +2219 next = request.get_vars._next \ +2220 or request.post_vars._next \ +2221 or self.settings.change_password_next +2222 if onvalidation == DEFAULT: +2223 onvalidation = self.settings.change_password_onvalidation +2224 if onaccept == DEFAULT: +2225 onaccept = self.settings.change_password_onaccept +2226 if log == DEFAULT: +2227 log = self.messages.change_password_log +2228 passfield = self.settings.password_field +2229 form = SQLFORM.factory( +2230 Field('old_password', 'password', +2231 label=self.messages.old_password, +2232 requires=validators( +2233 table_user[passfield].requires, +2234 IS_IN_DB(s, '%s.%s' % (usern, passfield), +2235 error_message=self.messages.invalid_password))), +2236 Field('new_password', 'password', +2237 label=self.messages.new_password, +2238 requires=table_user[passfield].requires), +2239 Field('new_password2', 'password', +2240 label=self.messages.verify_password, +2241 requires=[IS_EXPR('value==%s' % repr(request.vars.new_password), +2242 self.messages.mismatched_password)]), +2243 submit_button=self.messages.password_change_button, +2244 formstyle = self.settings.formstyle, +2245 separator=self.settings.label_separator +2246 ) +2247 if form.accepts(request, session, +2248 formname='change_password', +2249 onvalidation=onvalidation, +2250 hideerror=self.settings.hideerror): +2251 d = {passfield: form.vars.new_password} +2252 s.update(**d) +2253 session.flash = self.messages.password_changed +2254 if log: +2255 self.log_event(log % self.user) +2256 callback(onaccept,form) +2257 if not next: +2258 next = self.url(args=request.args) +2259 elif isinstance(next, (list, tuple)): ### fix issue with 2.6 +2260 next = next[0] +2261 elif next and not next[0] == '/' and next[:4] != 'http': +2262 next = self.url(next.replace('[id]', str(form.vars.id))) +2263 redirect(next) +2264 return form +
    2265 +
    2266 - def profile( +2267 self, +2268 next=DEFAULT, +2269 onvalidation=DEFAULT, +2270 onaccept=DEFAULT, +2271 log=DEFAULT, +2272 ): +
    2273 """ +2274 returns a form that lets the user change his/her profile +2275 +2276 .. method:: Auth.profile([next=DEFAULT [, onvalidation=DEFAULT +2277 [, onaccept=DEFAULT [, log=DEFAULT]]]]) +2278 +2279 """ +2280 +2281 table_user = self.settings.table_user +2282 if not self.is_logged_in(): +2283 redirect(self.settings.login_url) +2284 passfield = self.settings.password_field +2285 self.settings.table_user[passfield].writable = False +2286 request = current.request +2287 session = current.session +2288 if next == DEFAULT: +2289 next = request.get_vars._next \ +2290 or request.post_vars._next \ +2291 or self.settings.profile_next +2292 if onvalidation == DEFAULT: +2293 onvalidation = self.settings.profile_onvalidation +2294 if onaccept == DEFAULT: +2295 onaccept = self.settings.profile_onaccept +2296 if log == DEFAULT: +2297 log = self.messages.profile_log +2298 form = SQLFORM( +2299 table_user, +2300 self.user.id, +2301 fields = self.settings.profile_fields, +2302 hidden = dict(_next=next), +2303 showid = self.settings.showid, +2304 submit_button = self.messages.profile_save_button, +2305 delete_label = self.messages.delete_label, +2306 upload = self.settings.download_url, +2307 formstyle = self.settings.formstyle, +2308 separator=self.settings.label_separator +2309 ) +2310 if form.accepts(request, session, +2311 formname='profile', +2312 onvalidation=onvalidation,hideerror=self.settings.hideerror): +2313 self.user.update(table_user._filter_fields(form.vars)) +2314 session.flash = self.messages.profile_updated +2315 if log: +2316 self.log_event(log % self.user) +2317 callback(onaccept,form) +2318 if not next: +2319 next = self.url(args=request.args) +2320 elif isinstance(next, (list, tuple)): ### fix issue with 2.6 +2321 next = next[0] +2322 elif next and not next[0] == '/' and next[:4] != 'http': +2323 next = self.url(next.replace('[id]', str(form.vars.id))) +2324 redirect(next) +2325 return form +
    2326 +
    2327 - def is_impersonating(self): +
    2328 return current.session.auth.impersonator +
    2329 +
    2330 - def impersonate(self, user_id=DEFAULT): +
    2331 """ +2332 usage: POST TO http://..../impersonate request.post_vars.user_id=<id> +2333 set request.post_vars.user_id to 0 to restore original user. +2334 +2335 requires impersonator is logged in and +2336 has_permission('impersonate', 'auth_user', user_id) +2337 """ +2338 request = current.request +2339 session = current.session +2340 auth = session.auth +2341 if not self.is_logged_in(): +2342 raise HTTP(401, "Not Authorized") +2343 current_id = auth.user.id +2344 requested_id = user_id +2345 if user_id == DEFAULT: +2346 user_id = current.request.post_vars.user_id +2347 if user_id and user_id != self.user.id and user_id != '0': +2348 if not self.has_permission('impersonate', +2349 self.settings.table_user_name, +2350 user_id): +2351 raise HTTP(403, "Forbidden") +2352 user = self.settings.table_user(user_id) +2353 if not user: +2354 raise HTTP(401, "Not Authorized") +2355 auth.impersonator = cPickle.dumps(session) +2356 auth.user.update( +2357 self.settings.table_user._filter_fields(user, True)) +2358 self.user = auth.user +2359 if self.settings.login_onaccept: +2360 form = Storage(dict(vars=self.user)) +2361 self.settings.login_onaccept(form) +2362 log = self.messages.impersonate_log +2363 if log: +2364 self.log_event(log % dict(id=current_id,other_id=auth.user.id)) +2365 elif user_id in (0, '0') and self.is_impersonating(): +2366 session.clear() +2367 session.update(cPickle.loads(auth.impersonator)) +2368 self.user = session.auth.user +2369 if requested_id == DEFAULT and not request.post_vars: +2370 return SQLFORM.factory(Field('user_id','integer')) +2371 return self.user +
    2372 +
    2373 - def groups(self): +
    2374 """ +2375 displays the groups and their roles for the logged in user +2376 """ +2377 +2378 if not self.is_logged_in(): +2379 redirect(self.settings.login_url) +2380 memberships = self.db(self.settings.table_membership.user_id +2381 == self.user.id).select() +2382 table = TABLE() +2383 for membership in memberships: +2384 groups = self.db(self.settings.table_group.id +2385 == membership.group_id).select() +2386 if groups: +2387 group = groups[0] +2388 table.append(TR(H3(group.role, '(%s)' % group.id))) +2389 table.append(TR(P(group.description))) +2390 if not memberships: +2391 return None +2392 return table +
    2393 +
    2394 - def not_authorized(self): +
    2395 """ +2396 you can change the view for this page to make it look as you like +2397 """ +2398 +2399 return 'ACCESS DENIED' +
    2400 +
    2401 - def requires(self, condition): +
    2402 """ +2403 decorator that prevents access to action if not logged in +2404 """ +2405 +2406 def decorator(action): +2407 +2408 def f(*a, **b): +2409 +2410 if self.settings.allow_basic_login_only and not self.basic(): +2411 if current.request.is_restful: +2412 raise HTTP(403,"Not authorized") +2413 return call_or_redirect(self.settings.on_failed_authorization) +2414 +2415 if not condition: +2416 if current.request.is_restful: +2417 raise HTTP(403,"Not authorized") +2418 if not self.basic() and not self.is_logged_in(): +2419 request = current.request +2420 next = URL(r=request,args=request.args, +2421 vars=request.get_vars) +2422 current.session.flash = current.response.flash +2423 return call_or_redirect( +2424 self.settings.on_failed_authentication, +2425 self.settings.login_url + '?_next='+urllib.quote(next)) +2426 else: +2427 current.session.flash = self.messages.access_denied +2428 return call_or_redirect(self.settings.on_failed_authorization) +2429 return action(*a, **b) +
    2430 f.__doc__ = action.__doc__ +2431 f.__name__ = action.__name__ +2432 f.__dict__.update(action.__dict__) +2433 return f +2434 +2435 return decorator +2436 +
    2437 - def requires_login(self): +
    2438 """ +2439 decorator that prevents access to action if not logged in +2440 """ +2441 +2442 def decorator(action): +2443 +2444 def f(*a, **b): +2445 +2446 if self.settings.allow_basic_login_only and not self.basic(): +2447 if current.request.is_restful: +2448 raise HTTP(403,"Not authorized") +2449 return call_or_redirect(self.settings.on_failed_authorization) +2450 +2451 if not self.basic() and not self.is_logged_in(): +2452 if current.request.is_restful: +2453 raise HTTP(403,"Not authorized") +2454 request = current.request +2455 next = URL(r=request,args=request.args, +2456 vars=request.get_vars) +2457 current.session.flash = current.response.flash +2458 return call_or_redirect( +2459 self.settings.on_failed_authentication, +2460 self.settings.login_url + '?_next='+urllib.quote(next) +2461 ) +2462 return action(*a, **b) +
    2463 f.__doc__ = action.__doc__ +2464 f.__name__ = action.__name__ +2465 f.__dict__.update(action.__dict__) +2466 return f +2467 +2468 return decorator +2469 +
    2470 - def requires_membership(self, role=None, group_id=None): +
    2471 """ +2472 decorator that prevents access to action if not logged in or +2473 if user logged in is not a member of group_id. +2474 If role is provided instead of group_id then the +2475 group_id is calculated. +2476 """ +2477 +2478 def decorator(action): +2479 def f(*a, **b): +2480 if self.settings.allow_basic_login_only and not self.basic(): +2481 if current.request.is_restful: +2482 raise HTTP(403,"Not authorized") +2483 return call_or_redirect(self.settings.on_failed_authorization) +2484 +2485 if not self.basic() and not self.is_logged_in(): +2486 if current.request.is_restful: +2487 raise HTTP(403,"Not authorized") +2488 request = current.request +2489 next = URL(r=request,args=request.args, +2490 vars=request.get_vars) +2491 current.session.flash = current.response.flash +2492 return call_or_redirect( +2493 self.settings.on_failed_authentication, +2494 self.settings.login_url + '?_next='+urllib.quote(next) +2495 ) +2496 if not self.has_membership(group_id=group_id, role=role): +2497 current.session.flash = self.messages.access_denied +2498 return call_or_redirect(self.settings.on_failed_authorization) +2499 return action(*a, **b) +
    2500 f.__doc__ = action.__doc__ +2501 f.__name__ = action.__name__ +2502 f.__dict__.update(action.__dict__) +2503 return f +2504 +2505 return decorator +2506 +2507 +
    2508 - def requires_permission( +2509 self, +2510 name, +2511 table_name='', +2512 record_id=0, +2513 ): +
    2514 """ +2515 decorator that prevents access to action if not logged in or +2516 if user logged in is not a member of any group (role) that +2517 has 'name' access to 'table_name', 'record_id'. +2518 """ +2519 +2520 def decorator(action): +2521 +2522 def f(*a, **b): +2523 if self.settings.allow_basic_login_only and not self.basic(): +2524 if current.request.is_restful: +2525 raise HTTP(403,"Not authorized") +2526 return call_or_redirect(self.settings.on_failed_authorization) +2527 +2528 if not self.basic() and not self.is_logged_in(): +2529 if current.request.is_restful: +2530 raise HTTP(403,"Not authorized") +2531 request = current.request +2532 next = URL(r=request,args=request.args, +2533 vars=request.get_vars) +2534 current.session.flash = current.response.flash +2535 return call_or_redirect( +2536 self.settings.on_failed_authentication, +2537 self.settings.login_url + '?_next='+urllib.quote(next) +2538 ) +2539 if not self.has_permission(name, table_name, record_id): +2540 current.session.flash = self.messages.access_denied +2541 return call_or_redirect(self.settings.on_failed_authorization) +2542 return action(*a, **b) +
    2543 f.__doc__ = action.__doc__ +2544 f.__name__ = action.__name__ +2545 f.__dict__.update(action.__dict__) +2546 return f +2547 +2548 return decorator +2549 +
    2550 - def requires_signature(self): +
    2551 """ +2552 decorator that prevents access to action if not logged in or +2553 if user logged in is not a member of group_id. +2554 If role is provided instead of group_id then the +2555 group_id is calculated. +2556 """ +2557 +2558 def decorator(action): +2559 def f(*a, **b): +2560 if self.settings.allow_basic_login_only and not self.basic(): +2561 if current.request.is_restful: +2562 raise HTTP(403,"Not authorized") +2563 return call_or_redirect(self.settings.on_failed_authorization) +2564 +2565 if not self.basic() and not self.is_logged_in(): +2566 if current.request.is_restful: +2567 raise HTTP(403,"Not authorized") +2568 request = current.request +2569 next = URL(r=request,args=request.args, +2570 vars=request.get_vars) +2571 current.session.flash = current.response.flash +2572 return call_or_redirect( +2573 self.settings.on_failed_authentication, +2574 self.settings.login_url + '?_next='+urllib.quote(next) +2575 ) +2576 if not URL.verify(current.request,user_signature=True): +2577 current.session.flash = self.messages.access_denied +2578 return call_or_redirect(self.settings.on_failed_authorization) +2579 return action(*a, **b) +
    2580 f.__doc__ = action.__doc__ +2581 f.__name__ = action.__name__ +2582 f.__dict__.update(action.__dict__) +2583 return f +2584 +2585 return decorator +2586 +
    2587 - def add_group(self, role, description=''): +
    2588 """ +2589 creates a group associated to a role +2590 """ +2591 +2592 group_id = self.settings.table_group.insert(role=role, +2593 description=description) +2594 log = self.messages.add_group_log +2595 if log: +2596 self.log_event(log % dict(group_id=group_id, role=role)) +2597 return group_id +
    2598 +
    2599 - def del_group(self, group_id): +
    2600 """ +2601 deletes a group +2602 """ +2603 +2604 self.db(self.settings.table_group.id == group_id).delete() +2605 self.db(self.settings.table_membership.group_id +2606 == group_id).delete() +2607 self.db(self.settings.table_permission.group_id +2608 == group_id).delete() +2609 log = self.messages.del_group_log +2610 if log: +2611 self.log_event(log % dict(group_id=group_id)) +
    2612 +
    2613 - def id_group(self, role): +
    2614 """ +2615 returns the group_id of the group specified by the role +2616 """ +2617 rows = self.db(self.settings.table_group.role == role).select() +2618 if not rows: +2619 return None +2620 return rows[0].id +
    2621 +
    2622 - def user_group(self, user_id = None): +
    2623 """ +2624 returns the group_id of the group uniquely associated to this user +2625 i.e. role=user:[user_id] +2626 """ +2627 if not user_id and self.user: +2628 user_id = self.user.id +2629 role = 'user_%s' % user_id +2630 return self.id_group(role) +
    2631 +
    2632 - def has_membership(self, group_id=None, user_id=None, role=None): +
    2633 """ +2634 checks if user is member of group_id or role +2635 """ +2636 +2637 group_id = group_id or self.id_group(role) +2638 try: +2639 group_id = int(group_id) +2640 except: +2641 group_id = self.id_group(group_id) # interpret group_id as a role +2642 if not user_id and self.user: +2643 user_id = self.user.id +2644 membership = self.settings.table_membership +2645 if self.db((membership.user_id == user_id) +2646 & (membership.group_id == group_id)).select(): +2647 r = True +2648 else: +2649 r = False +2650 log = self.messages.has_membership_log +2651 if log: +2652 self.log_event(log % dict(user_id=user_id, +2653 group_id=group_id, check=r)) +2654 return r +
    2655 +
    2656 - def add_membership(self, group_id=None, user_id=None, role=None): +
    2657 """ +2658 gives user_id membership of group_id or role +2659 if user_id==None than user_id is that of current logged in user +2660 """ +2661 +2662 group_id = group_id or self.id_group(role) +2663 try: +2664 group_id = int(group_id) +2665 except: +2666 group_id = self.id_group(group_id) # interpret group_id as a role +2667 if not user_id and self.user: +2668 user_id = self.user.id +2669 membership = self.settings.table_membership +2670 record = membership(user_id = user_id,group_id = group_id) +2671 if record: +2672 return record.id +2673 else: +2674 id = membership.insert(group_id=group_id, user_id=user_id) +2675 log = self.messages.add_membership_log +2676 if log: +2677 self.log_event(log % dict(user_id=user_id, +2678 group_id=group_id)) +2679 return id +
    2680 +
    2681 - def del_membership(self, group_id, user_id=None, role=None): +
    2682 """ +2683 revokes membership from group_id to user_id +2684 if user_id==None than user_id is that of current logged in user +2685 """ +2686 +2687 group_id = group_id or self.id_group(role) +2688 if not user_id and self.user: +2689 user_id = self.user.id +2690 membership = self.settings.table_membership +2691 log = self.messages.del_membership_log +2692 if log: +2693 self.log_event(log % dict(user_id=user_id, +2694 group_id=group_id)) +2695 return self.db(membership.user_id +2696 == user_id)(membership.group_id +2697 == group_id).delete() +
    2698 +
    2699 - def has_permission( +2700 self, +2701 name='any', +2702 table_name='', +2703 record_id=0, +2704 user_id=None, +2705 group_id=None, +2706 ): +
    2707 """ +2708 checks if user_id or current logged in user is member of a group +2709 that has 'name' permission on 'table_name' and 'record_id' +2710 if group_id is passed, it checks whether the group has the permission +2711 """ +2712 +2713 if not user_id and not group_id and self.user: +2714 user_id = self.user.id +2715 if user_id: +2716 membership = self.settings.table_membership +2717 rows = self.db(membership.user_id +2718 == user_id).select(membership.group_id) +2719 groups = set([row.group_id for row in rows]) +2720 if group_id and not group_id in groups: +2721 return False +2722 else: +2723 groups = set([group_id]) +2724 permission = self.settings.table_permission +2725 rows = self.db(permission.name == name)(permission.table_name +2726 == str(table_name))(permission.record_id +2727 == record_id).select(permission.group_id) +2728 groups_required = set([row.group_id for row in rows]) +2729 if record_id: +2730 rows = self.db(permission.name +2731 == name)(permission.table_name +2732 == str(table_name))(permission.record_id +2733 == 0).select(permission.group_id) +2734 groups_required = groups_required.union(set([row.group_id +2735 for row in rows])) +2736 if groups.intersection(groups_required): +2737 r = True +2738 else: +2739 r = False +2740 log = self.messages.has_permission_log +2741 if log and user_id: +2742 self.log_event(log % dict(user_id=user_id, name=name, +2743 table_name=table_name, record_id=record_id)) +2744 return r +
    2745 +
    2746 - def add_permission( +2747 self, +2748 group_id, +2749 name='any', +2750 table_name='', +2751 record_id=0, +2752 ): +
    2753 """ +2754 gives group_id 'name' access to 'table_name' and 'record_id' +2755 """ +2756 +2757 permission = self.settings.table_permission +2758 if group_id == 0: +2759 group_id = self.user_group() +2760 id = permission.insert(group_id=group_id, name=name, +2761 table_name=str(table_name), +2762 record_id=long(record_id)) +2763 log = self.messages.add_permission_log +2764 if log: +2765 self.log_event(log % dict(permission_id=id, group_id=group_id, +2766 name=name, table_name=table_name, +2767 record_id=record_id)) +2768 return id +
    2769 +
    2770 - def del_permission( +2771 self, +2772 group_id, +2773 name='any', +2774 table_name='', +2775 record_id=0, +2776 ): +
    2777 """ +2778 revokes group_id 'name' access to 'table_name' and 'record_id' +2779 """ +2780 +2781 permission = self.settings.table_permission +2782 log = self.messages.del_permission_log +2783 if log: +2784 self.log_event(log % dict(group_id=group_id, name=name, +2785 table_name=table_name, record_id=record_id)) +2786 return self.db(permission.group_id == group_id)(permission.name +2787 == name)(permission.table_name +2788 == str(table_name))(permission.record_id +2789 == long(record_id)).delete() +
    2790 +
    2791 - def accessible_query(self, name, table, user_id=None): +
    2792 """ +2793 returns a query with all accessible records for user_id or +2794 the current logged in user +2795 this method does not work on GAE because uses JOIN and IN +2796 +2797 example:: +2798 +2799 db(auth.accessible_query('read', db.mytable)).select(db.mytable.ALL) +2800 +2801 """ +2802 if not user_id: +2803 user_id = self.user.id +2804 if self.has_permission(name, table, 0, user_id): +2805 return table.id > 0 +2806 db = self.db +2807 membership = self.settings.table_membership +2808 permission = self.settings.table_permission +2809 return table.id.belongs(db(membership.user_id == user_id)\ +2810 (membership.group_id == permission.group_id)\ +2811 (permission.name == name)\ +2812 (permission.table_name == table)\ +2813 ._select(permission.record_id)) +
    2814 +2815 +
    2816 -class Crud(object): +
    2817 +
    2818 - def url(self, f=None, args=[], vars={}): +
    2819 """ +2820 this should point to the controller that exposes +2821 download and crud +2822 """ +2823 return URL(c=self.settings.controller,f=f,args=args,vars=vars) +
    2824 +
    2825 - def __init__(self, environment, db=None, controller='default'): +
    2826 self.db = db +2827 if not db and environment and isinstance(environment,DAL): +2828 self.db = environment +2829 elif not db: +2830 raise SyntaxError, "must pass db as first or second argument" +2831 self.environment = current +2832 settings = self.settings = Settings() +2833 settings.auth = None +2834 settings.logger = None +2835 +2836 settings.create_next = None +2837 settings.update_next = None +2838 settings.controller = controller +2839 settings.delete_next = self.url() +2840 settings.download_url = self.url('download') +2841 settings.create_onvalidation = StorageList() +2842 settings.update_onvalidation = StorageList() +2843 settings.delete_onvalidation = StorageList() +2844 settings.create_onaccept = StorageList() +2845 settings.update_onaccept = StorageList() +2846 settings.update_ondelete = StorageList() +2847 settings.delete_onaccept = StorageList() +2848 settings.update_deletable = True +2849 settings.showid = False +2850 settings.keepvalues = False +2851 settings.create_captcha = None +2852 settings.update_captcha = None +2853 settings.captcha = None +2854 settings.formstyle = 'table3cols' +2855 settings.label_separator = ': ' +2856 settings.hideerror = False +2857 settings.detect_record_change = True +2858 settings.hmac_key = None +2859 settings.lock_keys = True +2860 +2861 messages = self.messages = Messages(current.T) +2862 messages.submit_button = 'Submit' +2863 messages.delete_label = 'Check to delete:' +2864 messages.record_created = 'Record Created' +2865 messages.record_updated = 'Record Updated' +2866 messages.record_deleted = 'Record Deleted' +2867 +2868 messages.update_log = 'Record %(id)s updated' +2869 messages.create_log = 'Record %(id)s created' +2870 messages.read_log = 'Record %(id)s read' +2871 messages.delete_log = 'Record %(id)s deleted' +2872 +2873 messages.lock_keys = True +
    2874 +
    2875 - def __call__(self): +
    2876 args = current.request.args +2877 if len(args) < 1: +2878 raise HTTP(404) +2879 elif args[0] == 'tables': +2880 return self.tables() +2881 elif len(args) > 1 and not args(1) in self.db.tables: +2882 raise HTTP(404) +2883 table = self.db[args(1)] +2884 if args[0] == 'create': +2885 return self.create(table) +2886 elif args[0] == 'select': +2887 return self.select(table,linkto=self.url(args='read')) +2888 elif args[0] == 'search': +2889 form, rows = self.search(table,linkto=self.url(args='read')) +2890 return DIV(form,SQLTABLE(rows)) +2891 elif args[0] == 'read': +2892 return self.read(table, args(2)) +2893 elif args[0] == 'update': +2894 return self.update(table, args(2)) +2895 elif args[0] == 'delete': +2896 return self.delete(table, args(2)) +2897 else: +2898 raise HTTP(404) +
    2899 +
    2900 - def log_event(self, message): +
    2901 if self.settings.logger: +2902 self.settings.logger.log_event(message, 'crud') +
    2903 +
    2904 - def has_permission(self, name, table, record=0): +
    2905 if not self.settings.auth: +2906 return True +2907 try: +2908 record_id = record.id +2909 except: +2910 record_id = record +2911 return self.settings.auth.has_permission(name, str(table), record_id) +
    2912 +
    2913 - def tables(self): +
    2914 return TABLE(*[TR(A(name, +2915 _href=self.url(args=('select',name)))) \ +2916 for name in self.db.tables]) +
    2917 +2918 +2919 @staticmethod +
    2920 - def archive(form,archive_table=None,current_record='current_record'): +
    2921 """ +2922 If you have a table (db.mytable) that needs full revision history you can just do:: +2923 +2924 form=crud.update(db.mytable,myrecord,onaccept=crud.archive) +2925 +2926 crud.archive will define a new table "mytable_archive" and store the +2927 previous record in the newly created table including a reference +2928 to the current record. +2929 +2930 If you want to access such table you need to define it yourself in a model:: +2931 +2932 db.define_table('mytable_archive', +2933 Field('current_record',db.mytable), +2934 db.mytable) +2935 +2936 Notice such table includes all fields of db.mytable plus one: current_record. +2937 crud.archive does not timestamp the stored record unless your original table +2938 has a fields like:: +2939 +2940 db.define_table(..., +2941 Field('saved_on','datetime', +2942 default=request.now,update=request.now,writable=False), +2943 Field('saved_by',auth.user, +2944 default=auth.user_id,update=auth.user_id,writable=False), +2945 +2946 there is nothing special about these fields since they are filled before +2947 the record is archived. +2948 +2949 If you want to change the archive table name and the name of the reference field +2950 you can do, for example:: +2951 +2952 db.define_table('myhistory', +2953 Field('parent_record',db.mytable), +2954 db.mytable) +2955 +2956 and use it as:: +2957 +2958 form=crud.update(db.mytable,myrecord, +2959 onaccept=lambda form:crud.archive(form, +2960 archive_table=db.myhistory, +2961 current_record='parent_record')) +2962 +2963 """ +2964 old_record = form.record +2965 if not old_record: +2966 return None +2967 table = form.table +2968 if not archive_table: +2969 archive_table_name = '%s_archive' % table +2970 if archive_table_name in table._db: +2971 archive_table = table._db[archive_table_name] +2972 else: +2973 archive_table = table._db.define_table(archive_table_name, +2974 Field(current_record,table), +2975 table) +2976 new_record = {current_record:old_record.id} +2977 for fieldname in archive_table.fields: +2978 if not fieldname in ['id',current_record] and fieldname in old_record: +2979 new_record[fieldname]=old_record[fieldname] +2980 id = archive_table.insert(**new_record) +2981 return id +
    2982 +
    2983 - def update( +2984 self, +2985 table, +2986 record, +2987 next=DEFAULT, +2988 onvalidation=DEFAULT, +2989 onaccept=DEFAULT, +2990 ondelete=DEFAULT, +2991 log=DEFAULT, +2992 message=DEFAULT, +2993 deletable=DEFAULT, +2994 formname=DEFAULT, +2995 ): +
    2996 """ +2997 .. method:: Crud.update(table, record, [next=DEFAULT +2998 [, onvalidation=DEFAULT [, onaccept=DEFAULT [, log=DEFAULT +2999 [, message=DEFAULT[, deletable=DEFAULT]]]]]]) +3000 +3001 """ +3002 if not (isinstance(table, self.db.Table) or table in self.db.tables) \ +3003 or (isinstance(record, str) and not str(record).isdigit()): +3004 raise HTTP(404) +3005 if not isinstance(table, self.db.Table): +3006 table = self.db[table] +3007 try: +3008 record_id = record.id +3009 except: +3010 record_id = record or 0 +3011 if record_id and not self.has_permission('update', table, record_id): +3012 redirect(self.settings.auth.settings.on_failed_authorization) +3013 if not record_id \ +3014 and not self.has_permission('create', table, record_id): +3015 redirect(self.settings.auth.settings.on_failed_authorization) +3016 +3017 request = current.request +3018 response = current.response +3019 session = current.session +3020 if request.extension == 'json' and request.vars.json: +3021 request.vars.update(simplejson.loads(request.vars.json)) +3022 if next == DEFAULT: +3023 next = request.get_vars._next \ +3024 or request.post_vars._next \ +3025 or self.settings.update_next +3026 if onvalidation == DEFAULT: +3027 onvalidation = self.settings.update_onvalidation +3028 if onaccept == DEFAULT: +3029 onaccept = self.settings.update_onaccept +3030 if ondelete == DEFAULT: +3031 ondelete = self.settings.update_ondelete +3032 if log == DEFAULT: +3033 log = self.messages.update_log +3034 if deletable == DEFAULT: +3035 deletable = self.settings.update_deletable +3036 if message == DEFAULT: +3037 message = self.messages.record_updated +3038 form = SQLFORM( +3039 table, +3040 record, +3041 hidden=dict(_next=next), +3042 showid=self.settings.showid, +3043 submit_button=self.messages.submit_button, +3044 delete_label=self.messages.delete_label, +3045 deletable=deletable, +3046 upload=self.settings.download_url, +3047 formstyle=self.settings.formstyle, +3048 separator=self.settings.label_separator +3049 ) +3050 self.accepted = False +3051 self.deleted = False +3052 captcha = self.settings.update_captcha or \ +3053 self.settings.captcha +3054 if record and captcha: +3055 addrow(form, captcha.label, captcha, captcha.comment, +3056 self.settings.formstyle,'captcha__row') +3057 captcha = self.settings.create_captcha or \ +3058 self.settings.captcha +3059 if not record and captcha: +3060 addrow(form, captcha.label, captcha, captcha.comment, +3061 self.settings.formstyle,'captcha__row') +3062 if not request.extension in ('html','load'): +3063 (_session, _formname) = (None, None) +3064 else: +3065 (_session, _formname) = \ +3066 (session, '%s/%s' % (table._tablename, form.record_id)) +3067 if formname!=DEFAULT: +3068 _formname = formname +3069 keepvalues = self.settings.keepvalues +3070 if request.vars.delete_this_record: +3071 keepvalues = False +3072 if isinstance(onvalidation,StorageList): +3073 onvalidation=onvalidation.get(table._tablename, []) +3074 if form.accepts(request, _session, formname=_formname, +3075 onvalidation=onvalidation, keepvalues=keepvalues, +3076 hideerror=self.settings.hideerror, +3077 detect_record_change = self.settings.detect_record_change): +3078 self.accepted = True +3079 response.flash = message +3080 if log: +3081 self.log_event(log % form.vars) +3082 if request.vars.delete_this_record: +3083 self.deleted = True +3084 message = self.messages.record_deleted +3085 callback(ondelete,form,table._tablename) +3086 response.flash = message +3087 callback(onaccept,form,table._tablename) +3088 if not request.extension in ('html','load'): +3089 raise HTTP(200, 'RECORD CREATED/UPDATED') +3090 if isinstance(next, (list, tuple)): ### fix issue with 2.6 +3091 next = next[0] +3092 if next: # Only redirect when explicit +3093 if next[0] != '/' and next[:4] != 'http': +3094 next = URL(r=request, +3095 f=next.replace('[id]', str(form.vars.id))) +3096 session.flash = response.flash +3097 redirect(next) +3098 elif not request.extension in ('html','load'): +3099 raise HTTP(401) +3100 return form +
    3101 +
    3102 - def create( +3103 self, +3104 table, +3105 next=DEFAULT, +3106 onvalidation=DEFAULT, +3107 onaccept=DEFAULT, +3108 log=DEFAULT, +3109 message=DEFAULT, +3110 formname=DEFAULT, +3111 ): +
    3112 """ +3113 .. method:: Crud.create(table, [next=DEFAULT [, onvalidation=DEFAULT +3114 [, onaccept=DEFAULT [, log=DEFAULT[, message=DEFAULT]]]]]) +3115 """ +3116 +3117 if next == DEFAULT: +3118 next = self.settings.create_next +3119 if onvalidation == DEFAULT: +3120 onvalidation = self.settings.create_onvalidation +3121 if onaccept == DEFAULT: +3122 onaccept = self.settings.create_onaccept +3123 if log == DEFAULT: +3124 log = self.messages.create_log +3125 if message == DEFAULT: +3126 message = self.messages.record_created +3127 return self.update( +3128 table, +3129 None, +3130 next=next, +3131 onvalidation=onvalidation, +3132 onaccept=onaccept, +3133 log=log, +3134 message=message, +3135 deletable=False, +3136 formname=formname, +3137 ) +
    3138 +
    3139 - def read(self, table, record): +
    3140 if not (isinstance(table, self.db.Table) or table in self.db.tables) \ +3141 or (isinstance(record, str) and not str(record).isdigit()): +3142 raise HTTP(404) +3143 if not isinstance(table, self.db.Table): +3144 table = self.db[table] +3145 if not self.has_permission('read', table, record): +3146 redirect(self.settings.auth.settings.on_failed_authorization) +3147 form = SQLFORM( +3148 table, +3149 record, +3150 readonly=True, +3151 comments=False, +3152 upload=self.settings.download_url, +3153 showid=self.settings.showid, +3154 formstyle=self.settings.formstyle, +3155 separator=self.settings.label_separator +3156 ) +3157 if not current.request.extension in ('html','load'): +3158 return table._filter_fields(form.record, id=True) +3159 return form +
    3160 +
    3161 - def delete( +3162 self, +3163 table, +3164 record_id, +3165 next=DEFAULT, +3166 message=DEFAULT, +3167 ): +
    3168 """ +3169 .. method:: Crud.delete(table, record_id, [next=DEFAULT +3170 [, message=DEFAULT]]) +3171 """ +3172 if not (isinstance(table, self.db.Table) or table in self.db.tables) \ +3173 or not str(record_id).isdigit(): +3174 raise HTTP(404) +3175 if not isinstance(table, self.db.Table): +3176 table = self.db[table] +3177 if not self.has_permission('delete', table, record_id): +3178 redirect(self.settings.auth.settings.on_failed_authorization) +3179 request = current.request +3180 session = current.session +3181 if next == DEFAULT: +3182 next = request.get_vars._next \ +3183 or request.post_vars._next \ +3184 or self.settings.delete_next +3185 if message == DEFAULT: +3186 message = self.messages.record_deleted +3187 record = table[record_id] +3188 if record: +3189 callback(self.settings.delete_onvalidation,record) +3190 del table[record_id] +3191 callback(self.settings.delete_onaccept,record,table._tablename) +3192 session.flash = message +3193 if next: # Only redirect when explicit +3194 redirect(next) +
    3195 +
    3196 - def rows( +3197 self, +3198 table, +3199 query=None, +3200 fields=None, +3201 orderby=None, +3202 limitby=None, +3203 ): +
    3204 request = current.request +3205 if not (isinstance(table, self.db.Table) or table in self.db.tables): +3206 raise HTTP(404) +3207 if not self.has_permission('select', table): +3208 redirect(self.settings.auth.settings.on_failed_authorization) +3209 #if record_id and not self.has_permission('select', table): +3210 # redirect(self.settings.auth.settings.on_failed_authorization) +3211 if not isinstance(table, self.db.Table): +3212 table = self.db[table] +3213 if not query: +3214 query = table.id > 0 +3215 if not fields: +3216 fields = [field for field in table if field.readable] +3217 rows = self.db(query).select(*fields,**dict(orderby=orderby, +3218 limitby=limitby)) +3219 return rows +
    3220 +
    3221 - def select( +3222 self, +3223 table, +3224 query=None, +3225 fields=None, +3226 orderby=None, +3227 limitby=None, +3228 headers={}, +3229 **attr +3230 ): +
    3231 rows = self.rows(table,query,fields,orderby,limitby) +3232 if not rows: +3233 return None # Nicer than an empty table. +3234 if not 'upload' in attr: +3235 attr['upload'] = self.url('download') +3236 if not current.request.extension in ('html','load'): +3237 return rows.as_list() +3238 if not headers: +3239 if isinstance(table,str): +3240 table = self.db[table] +3241 headers = dict((str(k),k.label) for k in table) +3242 return SQLTABLE(rows,headers=headers,**attr) +
    3243 +
    3244 - def get_format(self, field): +
    3245 rtable = field._db[field.type[10:]] +3246 format = rtable.get('_format', None) +3247 if format and isinstance(format, str): +3248 return format[2:-2] +3249 return field.name +
    3250 +
    3251 - def get_query(self, field, op, value, refsearch=False): +
    3252 try: +3253 if refsearch: format = self.get_format(field) +3254 if op == 'equals': +3255 if not refsearch: +3256 return field == value +3257 else: +3258 return lambda row: row[field.name][format] == value +3259 elif op == 'not equal': +3260 if not refsearch: +3261 return field != value +3262 else: +3263 return lambda row: row[field.name][format] != value +3264 elif op == 'greater than': +3265 if not refsearch: +3266 return field > value +3267 else: +3268 return lambda row: row[field.name][format] > value +3269 elif op == 'less than': +3270 if not refsearch: +3271 return field < value +3272 else: +3273 return lambda row: row[field.name][format] < value +3274 elif op == 'starts with': +3275 if not refsearch: +3276 return field.like(value+'%') +3277 else: +3278 return lambda row: str(row[field.name][format]).startswith(value) +3279 elif op == 'ends with': +3280 if not refsearch: +3281 return field.like('%'+value) +3282 else: +3283 return lambda row: str(row[field.name][format]).endswith(value) +3284 elif op == 'contains': +3285 if not refsearch: +3286 return field.like('%'+value+'%') +3287 else: +3288 return lambda row: value in row[field.name][format] +3289 except: +3290 return None +
    3291 +3292 +
    3293 - def search(self, *tables, **args): +
    3294 """ +3295 Creates a search form and its results for a table +3296 Example usage: +3297 form, results = crud.search(db.test, +3298 queries = ['equals', 'not equal', 'contains'], +3299 query_labels={'equals':'Equals', +3300 'not equal':'Not equal'}, +3301 fields = ['id','children'], +3302 field_labels = {'id':'ID','children':'Children'}, +3303 zero='Please choose', +3304 query = (db.test.id > 0)&(db.test.id != 3) ) +3305 """ +3306 table = tables[0] +3307 fields = args.get('fields', table.fields) +3308 request = current.request +3309 db = self.db +3310 if not (isinstance(table, db.Table) or table in db.tables): +3311 raise HTTP(404) +3312 attributes = {} +3313 for key in ('orderby','groupby','left','distinct','limitby','cache'): +3314 if key in args: attributes[key]=args[key] +3315 tbl = TABLE() +3316 selected = []; refsearch = []; results = [] +3317 ops = args.get('queries', []) +3318 zero = args.get('zero', '') +3319 if not ops: +3320 ops = ['equals', 'not equal', 'greater than', +3321 'less than', 'starts with', +3322 'ends with', 'contains'] +3323 ops.insert(0,zero) +3324 query_labels = args.get('query_labels', {}) +3325 query = args.get('query',table.id > 0) +3326 field_labels = args.get('field_labels',{}) +3327 for field in fields: +3328 field = table[field] +3329 if not field.readable: continue +3330 fieldname = field.name +3331 chkval = request.vars.get('chk' + fieldname, None) +3332 txtval = request.vars.get('txt' + fieldname, None) +3333 opval = request.vars.get('op' + fieldname, None) +3334 row = TR(TD(INPUT(_type = "checkbox", _name = "chk" + fieldname, +3335 _disabled = (field.type == 'id'), +3336 value = (field.type == 'id' or chkval == 'on'))), +3337 TD(field_labels.get(fieldname,field.label)), +3338 TD(SELECT([OPTION(query_labels.get(op,op), +3339 _value=op) for op in ops], +3340 _name = "op" + fieldname, +3341 value = opval)), +3342 TD(INPUT(_type = "text", _name = "txt" + fieldname, +3343 _value = txtval, _id='txt' + fieldname, +3344 _class = str(field.type)))) +3345 tbl.append(row) +3346 if request.post_vars and (chkval or field.type=='id'): +3347 if txtval and opval != '': +3348 if field.type[0:10] == 'reference ': +3349 refsearch.append(self.get_query(field, +3350 opval, txtval, refsearch=True)) +3351 else: +3352 value, error = field.validate(txtval) +3353 if not error: +3354 ### TODO deal with 'starts with', 'ends with', 'contains' on GAE +3355 query &= self.get_query(field, opval, value) +3356 else: +3357 row[3].append(DIV(error,_class='error')) +3358 selected.append(field) +3359 form = FORM(tbl,INPUT(_type="submit")) +3360 if selected: +3361 try: +3362 results = db(query).select(*selected,**attributes) +3363 for r in refsearch: +3364 results = results.find(r) +3365 except: # hmmm, we should do better here +3366 results = None +3367 return form, results +
    3368 +3369 +3370 urllib2.install_opener(urllib2.build_opener(urllib2.HTTPCookieProcessor())) +3371 +
    3372 -def fetch(url, data=None, headers={}, +3373 cookie=Cookie.SimpleCookie(), +3374 user_agent='Mozilla/5.0'): +
    3375 if data != None: +3376 data = urllib.urlencode(data) +3377 if user_agent: headers['User-agent'] = user_agent +3378 headers['Cookie'] = ' '.join(['%s=%s;'%(c.key,c.value) for c in cookie.values()]) +3379 try: +3380 from google.appengine.api import urlfetch +3381 except ImportError: +3382 req = urllib2.Request(url, data, headers) +3383 html = urllib2.urlopen(req).read() +3384 else: +3385 method = ((data==None) and urlfetch.GET) or urlfetch.POST +3386 while url is not None: +3387 response = urlfetch.fetch(url=url, payload=data, +3388 method=method, headers=headers, +3389 allow_truncated=False,follow_redirects=False, +3390 deadline=10) +3391 # next request will be a get, so no need to send the data again +3392 data = None +3393 method = urlfetch.GET +3394 # load cookies from the response +3395 cookie.load(response.headers.get('set-cookie', '')) +3396 url = response.headers.get('location') +3397 html = response.content +3398 return html +
    3399 +3400 regex_geocode = \ +3401 re.compile('\<coordinates\>(?P<la>[^,]*),(?P<lo>[^,]*).*?\</coordinates\>') +3402 +3403 +
    3404 -def geocode(address): +
    3405 try: +3406 a = urllib.quote(address) +3407 txt = fetch('http://maps.google.com/maps/geo?q=%s&output=xml' +3408 % a) +3409 item = regex_geocode.search(txt) +3410 (la, lo) = (float(item.group('la')), float(item.group('lo'))) +3411 return (la, lo) +3412 except: +3413 return (0.0, 0.0) +
    3414 +3415 +
    3416 -def universal_caller(f, *a, **b): +
    3417 c = f.func_code.co_argcount +3418 n = f.func_code.co_varnames[:c] +3419 +3420 defaults = f.func_defaults or [] +3421 pos_args = n[0:-len(defaults)] +3422 named_args = n[-len(defaults):] +3423 +3424 arg_dict = {} +3425 +3426 # Fill the arg_dict with name and value for the submitted, positional values +3427 for pos_index, pos_val in enumerate(a[:c]): +3428 arg_dict[n[pos_index]] = pos_val # n[pos_index] is the name of the argument +3429 +3430 # There might be pos_args left, that are sent as named_values. Gather them as well. +3431 # If a argument already is populated with values we simply replaces them. +3432 for arg_name in pos_args[len(arg_dict):]: +3433 if b.has_key(arg_name): +3434 arg_dict[arg_name] = b[arg_name] +3435 +3436 if len(arg_dict) >= len(pos_args): +3437 # All the positional arguments is found. The function may now be called. +3438 # However, we need to update the arg_dict with the values from the named arguments as well. +3439 for arg_name in named_args: +3440 if b.has_key(arg_name): +3441 arg_dict[arg_name] = b[arg_name] +3442 +3443 return f(**arg_dict) +3444 +3445 # Raise an error, the function cannot be called. +3446 raise HTTP(404, "Object does not exist") +
    3447 +3448 +
    3449 -class Service(object): +
    3450 +
    3451 - def __init__(self, environment=None): +
    3452 self.run_procedures = {} +3453 self.csv_procedures = {} +3454 self.xml_procedures = {} +3455 self.rss_procedures = {} +3456 self.json_procedures = {} +3457 self.jsonrpc_procedures = {} +3458 self.xmlrpc_procedures = {} +3459 self.amfrpc_procedures = {} +3460 self.amfrpc3_procedures = {} +3461 self.soap_procedures = {} +
    3462 +
    3463 - def run(self, f): +
    3464 """ +3465 example:: +3466 +3467 service = Service(globals()) +3468 @service.run +3469 def myfunction(a, b): +3470 return a + b +3471 def call(): +3472 return service() +3473 +3474 Then call it with:: +3475 +3476 wget http://..../app/default/call/run/myfunction?a=3&b=4 +3477 +3478 """ +3479 self.run_procedures[f.__name__] = f +3480 return f +
    3481 +
    3482 - def csv(self, f): +
    3483 """ +3484 example:: +3485 +3486 service = Service(globals()) +3487 @service.csv +3488 def myfunction(a, b): +3489 return a + b +3490 def call(): +3491 return service() +3492 +3493 Then call it with:: +3494 +3495 wget http://..../app/default/call/csv/myfunction?a=3&b=4 +3496 +3497 """ +3498 self.run_procedures[f.__name__] = f +3499 return f +
    3500 +
    3501 - def xml(self, f): +
    3502 """ +3503 example:: +3504 +3505 service = Service(globals()) +3506 @service.xml +3507 def myfunction(a, b): +3508 return a + b +3509 def call(): +3510 return service() +3511 +3512 Then call it with:: +3513 +3514 wget http://..../app/default/call/xml/myfunction?a=3&b=4 +3515 +3516 """ +3517 self.run_procedures[f.__name__] = f +3518 return f +
    3519 +
    3520 - def rss(self, f): +
    3521 """ +3522 example:: +3523 +3524 service = Service(globals()) +3525 @service.rss +3526 def myfunction(): +3527 return dict(title=..., link=..., description=..., +3528 created_on=..., entries=[dict(title=..., link=..., +3529 description=..., created_on=...]) +3530 def call(): +3531 return service() +3532 +3533 Then call it with:: +3534 +3535 wget http://..../app/default/call/rss/myfunction +3536 +3537 """ +3538 self.rss_procedures[f.__name__] = f +3539 return f +
    3540 +
    3541 - def json(self, f): +
    3542 """ +3543 example:: +3544 +3545 service = Service(globals()) +3546 @service.json +3547 def myfunction(a, b): +3548 return [{a: b}] +3549 def call(): +3550 return service() +3551 +3552 Then call it with:: +3553 +3554 wget http://..../app/default/call/json/myfunction?a=hello&b=world +3555 +3556 """ +3557 self.json_procedures[f.__name__] = f +3558 return f +
    3559 +
    3560 - def jsonrpc(self, f): +
    3561 """ +3562 example:: +3563 +3564 service = Service(globals()) +3565 @service.jsonrpc +3566 def myfunction(a, b): +3567 return a + b +3568 def call(): +3569 return service() +3570 +3571 Then call it with:: +3572 +3573 wget http://..../app/default/call/jsonrpc/myfunction?a=hello&b=world +3574 +3575 """ +3576 self.jsonrpc_procedures[f.__name__] = f +3577 return f +
    3578 +
    3579 - def xmlrpc(self, f): +
    3580 """ +3581 example:: +3582 +3583 service = Service(globals()) +3584 @service.xmlrpc +3585 def myfunction(a, b): +3586 return a + b +3587 def call(): +3588 return service() +3589 +3590 The call it with:: +3591 +3592 wget http://..../app/default/call/xmlrpc/myfunction?a=hello&b=world +3593 +3594 """ +3595 self.xmlrpc_procedures[f.__name__] = f +3596 return f +
    3597 +
    3598 - def amfrpc(self, f): +
    3599 """ +3600 example:: +3601 +3602 service = Service(globals()) +3603 @service.amfrpc +3604 def myfunction(a, b): +3605 return a + b +3606 def call(): +3607 return service() +3608 +3609 The call it with:: +3610 +3611 wget http://..../app/default/call/amfrpc/myfunction?a=hello&b=world +3612 +3613 """ +3614 self.amfrpc_procedures[f.__name__] = f +3615 return f +
    3616 +
    3617 - def amfrpc3(self, domain='default'): +
    3618 """ +3619 example:: +3620 +3621 service = Service(globals()) +3622 @service.amfrpc3('domain') +3623 def myfunction(a, b): +3624 return a + b +3625 def call(): +3626 return service() +3627 +3628 The call it with:: +3629 +3630 wget http://..../app/default/call/amfrpc3/myfunction?a=hello&b=world +3631 +3632 """ +3633 if not isinstance(domain, str): +3634 raise SyntaxError, "AMF3 requires a domain for function" +3635 +3636 def _amfrpc3(f): +3637 if domain: +3638 self.amfrpc3_procedures[domain+'.'+f.__name__] = f +3639 else: +3640 self.amfrpc3_procedures[f.__name__] = f +3641 return f +
    3642 return _amfrpc3 +
    3643 +
    3644 - def soap(self, name=None, returns=None, args=None,doc=None): +
    3645 """ +3646 example:: +3647 +3648 service = Service(globals()) +3649 @service.soap('MyFunction',returns={'result':int},args={'a':int,'b':int,}) +3650 def myfunction(a, b): +3651 return a + b +3652 def call(): +3653 return service() +3654 +3655 The call it with:: +3656 +3657 from gluon.contrib.pysimplesoap.client import SoapClient +3658 client = SoapClient(wsdl="http://..../app/default/call/soap?WSDL") +3659 response = client.MyFunction(a=1,b=2) +3660 return response['result'] +3661 +3662 Exposes online generated documentation and xml example messages at: +3663 - http://..../app/default/call/soap +3664 """ +3665 +3666 def _soap(f): +3667 self.soap_procedures[name or f.__name__] = f, returns, args, doc +3668 return f +
    3669 return _soap +3670 +
    3671 - def serve_run(self, args=None): +
    3672 request = current.request +3673 if not args: +3674 args = request.args +3675 if args and args[0] in self.run_procedures: +3676 return str(universal_caller(self.run_procedures[args[0]], +3677 *args[1:], **dict(request.vars))) +3678 self.error() +
    3679 +
    3680 - def serve_csv(self, args=None): +
    3681 request = current.request +3682 response = current.response +3683 response.headers['Content-Type'] = 'text/x-csv' +3684 if not args: +3685 args = request.args +3686 +3687 def none_exception(value): +3688 if isinstance(value, unicode): +3689 return value.encode('utf8') +3690 if hasattr(value, 'isoformat'): +3691 return value.isoformat()[:19].replace('T', ' ') +3692 if value == None: +3693 return '<NULL>' +3694 return value +
    3695 if args and args[0] in self.run_procedures: +3696 r = universal_caller(self.run_procedures[args[0]], +3697 *args[1:], **dict(request.vars)) +3698 s = cStringIO.StringIO() +3699 if hasattr(r, 'export_to_csv_file'): +3700 r.export_to_csv_file(s) +3701 elif r and isinstance(r[0], (dict, Storage)): +3702 import csv +3703 writer = csv.writer(s) +3704 writer.writerow(r[0].keys()) +3705 for line in r: +3706 writer.writerow([none_exception(v) \ +3707 for v in line.values()]) +3708 else: +3709 import csv +3710 writer = csv.writer(s) +3711 for line in r: +3712 writer.writerow(line) +3713 return s.getvalue() +3714 self.error() +3715 +
    3716 - def serve_xml(self, args=None): +
    3717 request = current.request +3718 response = current.response +3719 response.headers['Content-Type'] = 'text/xml' +3720 if not args: +3721 args = request.args +3722 if args and args[0] in self.run_procedures: +3723 s = universal_caller(self.run_procedures[args[0]], +3724 *args[1:], **dict(request.vars)) +3725 if hasattr(s, 'as_list'): +3726 s = s.as_list() +3727 return serializers.xml(s) +3728 self.error() +
    3729 +
    3730 - def serve_rss(self, args=None): +
    3731 request = current.request +3732 response = current.response +3733 if not args: +3734 args = request.args +3735 if args and args[0] in self.rss_procedures: +3736 feed = universal_caller(self.rss_procedures[args[0]], +3737 *args[1:], **dict(request.vars)) +3738 else: +3739 self.error() +3740 response.headers['Content-Type'] = 'application/rss+xml' +3741 return serializers.rss(feed) +
    3742 +
    3743 - def serve_json(self, args=None): +
    3744 request = current.request +3745 response = current.response +3746 response.headers['Content-Type'] = 'text/x-json' +3747 if not args: +3748 args = request.args +3749 d = dict(request.vars) +3750 if args and args[0] in self.json_procedures: +3751 s = universal_caller(self.json_procedures[args[0]],*args[1:],**d) +3752 if hasattr(s, 'as_list'): +3753 s = s.as_list() +3754 return response.json(s) +3755 self.error() +
    3756 +
    3757 - class JsonRpcException(Exception): +
    3758 - def __init__(self,code,info): +
    3759 self.code,self.info = code,info +
    3760 +
    3761 - def serve_jsonrpc(self): +
    3762 import contrib.simplejson as simplejson +3763 def return_response(id, result): +3764 return serializers.json({'version': '1.1', +3765 'id': id, 'result': result, 'error': None}) +
    3766 +3767 def return_error(id, code, message): +3768 return serializers.json({'id': id, +3769 'version': '1.1', +3770 'error': {'name': 'JSONRPCError', +3771 'code': code, 'message': message} +3772 }) +3773 +3774 request = current.request +3775 methods = self.jsonrpc_procedures +3776 data = simplejson.loads(request.body.read()) +3777 id, method, params = data['id'], data['method'], data.get('params','') +3778 if not method in methods: +3779 return return_error(id, 100, 'method "%s" does not exist' % method) +3780 try: +3781 s = methods[method](*params) +3782 if hasattr(s, 'as_list'): +3783 s = s.as_list() +3784 return return_response(id, s) +3785 except Service.JsonRpcException, e: +3786 return return_error(id, e.code, e.info) +3787 except BaseException: +3788 etype, eval, etb = sys.exc_info() +3789 return return_error(id, 100, '%s: %s' % (etype.__name__, eval)) +3790 except: +3791 etype, eval, etb = sys.exc_info() +3792 return return_error(id, 100, 'Exception %s: %s' % (etype, eval)) +3793 +
    3794 - def serve_xmlrpc(self): +
    3795 request = current.request +3796 response = current.response +3797 services = self.xmlrpc_procedures.values() +3798 return response.xmlrpc(request, services) +
    3799 +
    3800 - def serve_amfrpc(self, version=0): +
    3801 try: +3802 import pyamf +3803 import pyamf.remoting.gateway +3804 except: +3805 return "pyamf not installed or not in Python sys.path" +3806 request = current.request +3807 response = current.response +3808 if version == 3: +3809 services = self.amfrpc3_procedures +3810 base_gateway = pyamf.remoting.gateway.BaseGateway(services) +3811 pyamf_request = pyamf.remoting.decode(request.body) +3812 else: +3813 services = self.amfrpc_procedures +3814 base_gateway = pyamf.remoting.gateway.BaseGateway(services) +3815 context = pyamf.get_context(pyamf.AMF0) +3816 pyamf_request = pyamf.remoting.decode(request.body, context) +3817 pyamf_response = pyamf.remoting.Envelope(pyamf_request.amfVersion) +3818 for name, message in pyamf_request: +3819 pyamf_response[name] = base_gateway.getProcessor(message)(message) +3820 response.headers['Content-Type'] = pyamf.remoting.CONTENT_TYPE +3821 if version==3: +3822 return pyamf.remoting.encode(pyamf_response).getvalue() +3823 else: +3824 return pyamf.remoting.encode(pyamf_response, context).getvalue() +
    3825 +
    3826 - def serve_soap(self, version="1.1"): +
    3827 try: +3828 from contrib.pysimplesoap.server import SoapDispatcher +3829 except: +3830 return "pysimplesoap not installed in contrib" +3831 request = current.request +3832 response = current.response +3833 procedures = self.soap_procedures +3834 +3835 location = "%s://%s%s" % ( +3836 request.env.wsgi_url_scheme, +3837 request.env.http_host, +3838 URL(r=request,f="call/soap",vars={})) +3839 namespace = 'namespace' in response and response.namespace or location +3840 documentation = response.description or '' +3841 dispatcher = SoapDispatcher( +3842 name = response.title, +3843 location = location, +3844 action = location, # SOAPAction +3845 namespace = namespace, +3846 prefix='pys', +3847 documentation = documentation, +3848 ns = True) +3849 for method, (function, returns, args, doc) in procedures.items(): +3850 dispatcher.register_function(method, function, returns, args, doc) +3851 if request.env.request_method == 'POST': +3852 # Process normal Soap Operation +3853 response.headers['Content-Type'] = 'text/xml' +3854 return dispatcher.dispatch(request.body.read()) +3855 elif 'WSDL' in request.vars: +3856 # Return Web Service Description +3857 response.headers['Content-Type'] = 'text/xml' +3858 return dispatcher.wsdl() +3859 elif 'op' in request.vars: +3860 # Return method help webpage +3861 response.headers['Content-Type'] = 'text/html' +3862 method = request.vars['op'] +3863 sample_req_xml, sample_res_xml, doc = dispatcher.help(method) +3864 body = [H1("Welcome to Web2Py SOAP webservice gateway"), +3865 A("See all webservice operations", +3866 _href=URL(r=request,f="call/soap",vars={})), +3867 H2(method), +3868 P(doc), +3869 UL(LI("Location: %s" % dispatcher.location), +3870 LI("Namespace: %s" % dispatcher.namespace), +3871 LI("SoapAction: %s" % dispatcher.action), +3872 ), +3873 H3("Sample SOAP XML Request Message:"), +3874 CODE(sample_req_xml,language="xml"), +3875 H3("Sample SOAP XML Response Message:"), +3876 CODE(sample_res_xml,language="xml"), +3877 ] +3878 return {'body': body} +3879 else: +3880 # Return general help and method list webpage +3881 response.headers['Content-Type'] = 'text/html' +3882 body = [H1("Welcome to Web2Py SOAP webservice gateway"), +3883 P(response.description), +3884 P("The following operations are available"), +3885 A("See WSDL for webservice description", +3886 _href=URL(r=request,f="call/soap",vars={"WSDL":None})), +3887 UL([LI(A("%s: %s" % (method, doc or ''), +3888 _href=URL(r=request,f="call/soap",vars={'op': method}))) +3889 for method, doc in dispatcher.list_methods()]), +3890 ] +3891 return {'body': body} +
    3892 +
    3893 - def __call__(self): +
    3894 """ +3895 register services with: +3896 service = Service(globals()) +3897 @service.run +3898 @service.rss +3899 @service.json +3900 @service.jsonrpc +3901 @service.xmlrpc +3902 @service.jsonrpc +3903 @service.amfrpc +3904 @service.amfrpc3('domain') +3905 @service.soap('Method', returns={'Result':int}, args={'a':int,'b':int,}) +3906 +3907 expose services with +3908 +3909 def call(): return service() +3910 +3911 call services with +3912 http://..../app/default/call/run?[parameters] +3913 http://..../app/default/call/rss?[parameters] +3914 http://..../app/default/call/json?[parameters] +3915 http://..../app/default/call/jsonrpc +3916 http://..../app/default/call/xmlrpc +3917 http://..../app/default/call/amfrpc +3918 http://..../app/default/call/amfrpc3 +3919 http://..../app/default/call/soap +3920 """ +3921 +3922 request = current.request +3923 if len(request.args) < 1: +3924 raise HTTP(404, "Not Found") +3925 arg0 = request.args(0) +3926 if arg0 == 'run': +3927 return self.serve_run(request.args[1:]) +3928 elif arg0 == 'rss': +3929 return self.serve_rss(request.args[1:]) +3930 elif arg0 == 'csv': +3931 return self.serve_csv(request.args[1:]) +3932 elif arg0 == 'xml': +3933 return self.serve_xml(request.args[1:]) +3934 elif arg0 == 'json': +3935 return self.serve_json(request.args[1:]) +3936 elif arg0 == 'jsonrpc': +3937 return self.serve_jsonrpc() +3938 elif arg0 == 'xmlrpc': +3939 return self.serve_xmlrpc() +3940 elif arg0 == 'amfrpc': +3941 return self.serve_amfrpc() +3942 elif arg0 == 'amfrpc3': +3943 return self.serve_amfrpc(3) +3944 elif arg0 == 'soap': +3945 return self.serve_soap() +3946 else: +3947 self.error() +
    3948 +
    3949 - def error(self): +
    3950 raise HTTP(404, "Object does not exist") +
    3951 +3952 +
    3953 -def completion(callback): +
    3954 """ +3955 Executes a task on completion of the called action. For example: +3956 +3957 from gluon.tools import completion +3958 @completion(lambda d: logging.info(repr(d))) +3959 def index(): +3960 return dict(message='hello') +3961 +3962 It logs the output of the function every time input is called. +3963 The argument of completion is executed in a new thread. +3964 """ +3965 def _completion(f): +3966 def __completion(*a,**b): +3967 d = None +3968 try: +3969 d = f(*a,**b) +3970 return d +3971 finally: +3972 thread.start_new_thread(callback,(d,)) +
    3973 return __completion +3974 return _completion +3975 +
    3976 -def prettydate(d,T=lambda x:x): +
    3977 try: +3978 dt = datetime.datetime.now() - d +3979 except: +3980 return '' +3981 if dt.days >= 2*365: +3982 return T('%d years ago') % int(dt.days / 365) +3983 elif dt.days >= 365: +3984 return T('1 year ago') +3985 elif dt.days >= 60: +3986 return T('%d months ago') % int(dt.days / 30) +3987 elif dt.days > 21: +3988 return T('1 month ago') +3989 elif dt.days >= 14: +3990 return T('%d weeks ago') % int(dt.days / 7) +3991 elif dt.days >= 7: +3992 return T('1 week ago') +3993 elif dt.days > 1: +3994 return T('%d days ago') % dt.days +3995 elif dt.days == 1: +3996 return T('1 day ago') +3997 elif dt.seconds >= 2*60*60: +3998 return T('%d hours ago') % int(dt.seconds / 3600) +3999 elif dt.seconds >= 60*60: +4000 return T('1 hour ago') +4001 elif dt.seconds >= 2*60: +4002 return T('%d minutes ago') % int(dt.seconds / 60) +4003 elif dt.seconds >= 60: +4004 return T('1 minute ago') +4005 elif dt.seconds > 1: +4006 return T('%d seconds ago') % dt.seconds +4007 elif dt.seconds == 1: +4008 return T('1 second ago') +4009 else: +4010 return T('now') +
    4011 +
    4013 def f(): +4014 c=PluginManager() +4015 lock1.acquire() +4016 lock2.acquire() +4017 c.x=7 +4018 lock1.release() +4019 lock2.release() +
    4020 lock1=thread.allocate_lock() +4021 lock2=thread.allocate_lock() +4022 lock1.acquire() +4023 thread.start_new_thread(f,()) +4024 a=PluginManager() +4025 a.x=5 +4026 lock1.release() +4027 lock2.acquire() +4028 return a.x +4029 +
    4030 -class PluginManager(object): +
    4031 """ +4032 +4033 Plugin Manager is similar to a storage object but it is a single level singleton +4034 this means that multiple instances within the same thread share the same attributes +4035 Its constructor is also special. The first argument is the name of the plugin you are defining. +4036 The named arguments are parameters needed by the plugin with default values. +4037 If the parameters were previous defined, the old values are used. +4038 +4039 For example: +4040 +4041 ### in some general configuration file: +4042 >>> plugins = PluginManager() +4043 >>> plugins.me.param1=3 +4044 +4045 ### within the plugin model +4046 >>> _ = PluginManager('me',param1=5,param2=6,param3=7) +4047 +4048 ### where the plugin is used +4049 >>> print plugins.me.param1 +4050 3 +4051 >>> print plugins.me.param2 +4052 6 +4053 >>> plugins.me.param3 = 8 +4054 >>> print plugins.me.param3 +4055 8 +4056 +4057 Here are some tests: +4058 +4059 >>> a=PluginManager() +4060 >>> a.x=6 +4061 >>> b=PluginManager('check') +4062 >>> print b.x +4063 6 +4064 >>> b=PluginManager() # reset settings +4065 >>> print b.x +4066 <Storage {}> +4067 >>> b.x=7 +4068 >>> print a.x +4069 7 +4070 >>> a.y.z=8 +4071 >>> print b.y.z +4072 8 +4073 >>> test_thread_separation() +4074 5 +4075 >>> plugins=PluginManager('me',db='mydb') +4076 >>> print plugins.me.db +4077 mydb +4078 >>> print 'me' in plugins +4079 True +4080 >>> print plugins.me.installed +4081 True +4082 """ +4083 instances = {} +
    4084 - def __new__(cls,*a,**b): +
    4085 id = thread.get_ident() +4086 lock = thread.allocate_lock() +4087 try: +4088 lock.acquire() +4089 try: +4090 return cls.instances[id] +4091 except KeyError: +4092 instance = object.__new__(cls,*a,**b) +4093 cls.instances[id] = instance +4094 return instance +4095 finally: +4096 lock.release() +
    4097 - def __init__(self,plugin=None,**defaults): +
    4098 if not plugin: +4099 self.__dict__.clear() +4100 settings = self.__getattr__(plugin) +4101 settings.installed = True +4102 [settings.update({key:value}) for key,value in defaults.items() if not key in settings] +
    4103 - def __getattr__(self, key): +
    4104 if not key in self.__dict__: +4105 self.__dict__[key] = Storage() +4106 return self.__dict__[key] +
    4107 - def keys(self): +
    4108 return self.__dict__.keys() +
    4109 - def __contains__(self,key): +
    4110 return key in self.__dict__ +
    4111 +4112 if __name__ == '__main__': +4113 import doctest +4114 doctest.testmod() +4115 +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.tools.Auth-class.html Index: applications/examples/static/epydoc/web2py.gluon.tools.Auth-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.tools.Auth-class.html +++ applications/examples/static/epydoc/web2py.gluon.tools.Auth-class.html @@ -0,0 +1,1847 @@ + + + + + web2py.gluon.tools.Auth + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module tools :: + Class Auth + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class Auth

    source code

    +
    +object --+
    +         |
    +        Auth
    +
    + +
    +
    +
    +Class for authentication, authorization, role based access control.
    +
    +Includes:
    +
    +- registration and profile
    +- login and logout
    +- username and password retrieval
    +- event logging
    +- role creation and assignment
    +- user defined group/role based permission
    +
    +Authentication Example::
    +
    +    from contrib.utils import *
    +    mail=Mail()
    +    mail.settings.server='smtp.gmail.com:587'
    +    mail.settings.sender='you@somewhere.com'
    +    mail.settings.login='username:password'
    +    auth=Auth(globals(), db)
    +    auth.settings.mailer=mail
    +    # auth.settings....=...
    +    auth.define_tables()
    +    def authentication():
    +        return dict(form=auth())
    +
    +exposes:
    +
    +- http://.../{application}/{controller}/authentication/login
    +- http://.../{application}/{controller}/authentication/logout
    +- http://.../{application}/{controller}/authentication/register
    +- http://.../{application}/{controller}/authentication/verify_email
    +- http://.../{application}/{controller}/authentication/retrieve_username
    +- http://.../{application}/{controller}/authentication/retrieve_password
    +- http://.../{application}/{controller}/authentication/reset_password
    +- http://.../{application}/{controller}/authentication/profile
    +- http://.../{application}/{controller}/authentication/change_password
    +
    +On registration a group with role=new_user.id is created
    +and user is given membership of this group.
    +
    +You can create a group with::
    +
    +    group_id=auth.add_group('Manager', 'can access the manage action')
    +    auth.add_permission(group_id, 'access to manage')
    +
    +Here "access to manage" is just a user defined string.
    +You can give access to a user::
    +
    +    auth.add_membership(group_id, user_id)
    +
    +If user id is omitted, the logged in user is assumed
    +
    +Then you can decorate any action::
    +
    +    @auth.requires_permission('access to manage')
    +    def manage():
    +        return dict()
    +
    +You can restrict a permission to a specific table::
    +
    +    auth.add_permission(group_id, 'edit', db.sometable)
    +    @auth.requires_permission('edit', db.sometable)
    +
    +Or to a specific record::
    +
    +    auth.add_permission(group_id, 'edit', db.sometable, 45)
    +    @auth.requires_permission('edit', db.sometable, 45)
    +
    +If authorization is not granted calls::
    +
    +    auth.settings.on_failed_authorization
    +
    +Other options::
    +
    +    auth.settings.mailer=None
    +    auth.settings.expiration=3600 # seconds
    +
    +    ...
    +
    +    ### these are messages that can be customized
    +    ...
    +
    +


    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    url(self, + f=1, + args=[], + vars={}) + source code + +
    + +
    +   + + + + + + +
    __init__(self, + environment=1, + db=1, + controller='default', + cas_provider=1)
    + auth=Auth(globals(), db)...
    + source code + +
    + +
    +   + + + + + + +
    _get_user_id(self)
    + accessor for auth.user_id
    + source code + +
    + +
    +   + + + + + + +
    _HTTP(self, + *a, + **b)
    + only used in lambda: self._HTTP(404)
    + source code + +
    + +
    +   + + + + + + +
    __call__(self)
    + usage:
    + source code + +
    + +
    +   + + + + + + +
    navbar(self, + prefix='Welcome', + action=1) + source code + +
    + +
    +   + + + + + + +
    __get_migrate(self, + tablename, + migrate=True) + source code + +
    + +
    +   + + + + + + +
    define_tables(self, + username=True, + migrate=True, + fake_migrate=True)
    + to be called unless tables are defined manually
    + source code + +
    + +
    +   + + + + + + +
    log_event(self, + description, + origin='auth')
    + usage:
    + source code + +
    + +
    +   + + + + + + +
    get_or_create_user(self, + keys)
    + Used for alternate login methods: + If the user exists already then password is updated.
    + source code + +
    + +
    +   + + + + + + +
    basic(self) + source code + +
    + +
    +   + + + + + + +
    login_bare(self, + username, + password)
    + logins user
    + source code + +
    + +
    +   + + + + + + +
    cas_login(self, + next=<function <lambda> at 0x26ba500>, + onvalidation=<function <lambda> at 0x26ba500>, + onaccept=<function <lambda> at 0x26ba500>, + log=<function <lambda> at 0x26ba500>, + version=1) + source code + +
    + +
    +   + + + + + + +
    cas_validate(self, + version=1) + source code + +
    + +
    +   + + + + + + +
    login(self, + next=<function <lambda> at 0x26ba500>, + onvalidation=<function <lambda> at 0x26ba500>, + onaccept=<function <lambda> at 0x26ba500>, + log=<function <lambda> at 0x26ba500>)
    + returns a login form + +..
    + source code + +
    + +
    +   + + + + + + +
    logout(self, + next=<function <lambda> at 0x26ba500>, + onlogout=<function <lambda> at 0x26ba500>, + log=<function <lambda> at 0x26ba500>)
    + logout and redirects to login + +..
    + source code + +
    + +
    +   + + + + + + +
    register(self, + next=<function <lambda> at 0x26ba500>, + onvalidation=<function <lambda> at 0x26ba500>, + onaccept=<function <lambda> at 0x26ba500>, + log=<function <lambda> at 0x26ba500>)
    + returns a registration form + +..
    + source code + +
    + +
    +   + + + + + + +
    is_logged_in(self)
    + checks if the user is logged in and returns True/False.
    + source code + +
    + +
    +   + + + + + + +
    verify_email(self, + next=<function <lambda> at 0x26ba500>, + onaccept=<function <lambda> at 0x26ba500>, + log=<function <lambda> at 0x26ba500>)
    + action user to verify the registration email, XXXXXXXXXXXXXXXX + +..
    + source code + +
    + +
    +   + + + + + + +
    retrieve_username(self, + next=<function <lambda> at 0x26ba500>, + onvalidation=<function <lambda> at 0x26ba500>, + onaccept=<function <lambda> at 0x26ba500>, + log=<function <lambda> at 0x26ba500>)
    + returns a form to retrieve the user username +(only if there is a username field) + +..
    + source code + +
    + +
    +   + + + + + + +
    random_password(self) + source code + +
    + +
    +   + + + + + + +
    reset_password_deprecated(self, + next=<function <lambda> at 0x26ba500>, + onvalidation=<function <lambda> at 0x26ba500>, + onaccept=<function <lambda> at 0x26ba500>, + log=<function <lambda> at 0x26ba500>)
    + returns a form to reset the user password (deprecated) + +..
    + source code + +
    + +
    +   + + + + + + +
    reset_password(self, + next=<function <lambda> at 0x26ba500>, + onvalidation=<function <lambda> at 0x26ba500>, + onaccept=<function <lambda> at 0x26ba500>, + log=<function <lambda> at 0x26ba500>)
    + returns a form to reset the user password + +..
    + source code + +
    + +
    +   + + + + + + +
    request_reset_password(self, + next=<function <lambda> at 0x26ba500>, + onvalidation=<function <lambda> at 0x26ba500>, + onaccept=<function <lambda> at 0x26ba500>, + log=<function <lambda> at 0x26ba500>)
    + returns a form to reset the user password + +..
    + source code + +
    + +
    +   + + + + + + +
    retrieve_password(self, + next=<function <lambda> at 0x26ba500>, + onvalidation=<function <lambda> at 0x26ba500>, + onaccept=<function <lambda> at 0x26ba500>, + log=<function <lambda> at 0x26ba500>) + source code + +
    + +
    +   + + + + + + +
    change_password(self, + next=<function <lambda> at 0x26ba500>, + onvalidation=<function <lambda> at 0x26ba500>, + onaccept=<function <lambda> at 0x26ba500>, + log=<function <lambda> at 0x26ba500>)
    + returns a form that lets the user change password + +..
    + source code + +
    + +
    +   + + + + + + +
    profile(self, + next=<function <lambda> at 0x26ba500>, + onvalidation=<function <lambda> at 0x26ba500>, + onaccept=<function <lambda> at 0x26ba500>, + log=<function <lambda> at 0x26ba500>)
    + returns a form that lets the user change his/her profile + +..
    + source code + +
    + +
    +   + + + + + + +
    is_impersonating(self) + source code + +
    + +
    +   + + + + + + +
    impersonate(self, + user_id=<function <lambda> at 0x26ba500>)
    + usage: POST TO http://..../impersonate + request.post_vars.user_id=<id> set request.post_vars.user_id to + 0 to restore original user.
    + source code + +
    + +
    +   + + + + + + +
    groups(self)
    + displays the groups and their roles for the logged in user
    + source code + +
    + +
    +   + + + + + + +
    not_authorized(self)
    + you can change the view for this page to make it look as you + like
    + source code + +
    + +
    +   + + + + + + +
    requires(self, + condition)
    + decorator that prevents access to action if not logged in
    + source code + +
    + +
    +   + + + + + + +
    requires_login(self)
    + decorator that prevents access to action if not logged in
    + source code + +
    + +
    +   + + + + + + +
    requires_membership(self, + role=1, + group_id=1)
    + decorator that prevents access to action if not logged in or if + user logged in is not a member of group_id.
    + source code + +
    + +
    +   + + + + + + +
    requires_permission(self, + name, + table_name='', + record_id=0)
    + decorator that prevents access to action if not logged in or if + user logged in is not a member of any group (role) that has 'name' + access to 'table_name', 'record_id'.
    + source code + +
    + +
    +   + + + + + + +
    requires_signature(self)
    + decorator that prevents access to action if not logged in or if + user logged in is not a member of group_id.
    + source code + +
    + +
    +   + + + + + + +
    add_group(self, + role, + description='')
    + creates a group associated to a role
    + source code + +
    + +
    +   + + + + + + +
    del_group(self, + group_id)
    + deletes a group
    + source code + +
    + +
    +   + + + + + + +
    id_group(self, + role)
    + returns the group_id of the group specified by the role
    + source code + +
    + +
    +   + + + + + + +
    user_group(self, + user_id=1)
    + returns the group_id of the group uniquely associated to this user + i.e.
    + source code + +
    + +
    +   + + + + + + +
    has_membership(self, + group_id=1, + user_id=1, + role=1)
    + checks if user is member of group_id or role
    + source code + +
    + +
    +   + + + + + + +
    add_membership(self, + group_id=1, + user_id=1, + role=1)
    + gives user_id membership of group_id or role if user_id==None than + user_id is that of current logged in user
    + source code + +
    + +
    +   + + + + + + +
    del_membership(self, + group_id, + user_id=1, + role=1)
    + revokes membership from group_id to user_id if user_id==None than + user_id is that of current logged in user
    + source code + +
    + +
    +   + + + + + + +
    has_permission(self, + name='any', + table_name='', + record_id=0, + user_id=1, + group_id=1)
    + checks if user_id or current logged in user is member of a group + that has 'name' permission on 'table_name' and 'record_id' if + group_id is passed, it checks whether the group has the + permission
    + source code + +
    + +
    +   + + + + + + +
    add_permission(self, + group_id, + name='any', + table_name='', + record_id=0)
    + gives group_id 'name' access to 'table_name' and 'record_id'
    + source code + +
    + +
    +   + + + + + + +
    del_permission(self, + group_id, + name='any', + table_name='', + record_id=0)
    + revokes group_id 'name' access to 'table_name' and 'record_id'
    + source code + +
    + +
    +   + + + + + + +
    accessible_query(self, + name, + table, + user_id=1)
    + returns a query with all accessible records for user_id or the + current logged in user this method does not work on GAE because uses + JOIN and IN
    + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +   + + user_id
    + user.id or None +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + environment=1, + db=1, + controller='default', + cas_provider=1) +
    (Constructor) +

    +
    source code  +
    + +
    +
    +auth=Auth(globals(), db)
    +
    +- environment is there for legacy but unused (awful)
    +- db has to be the database where to create tables for authentication
    +
    +
    +
    +
    Overrides: + object.__init__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    __call__(self) +
    (Call operator) +

    +
    source code  +
    + +

    usage:

    + def authentication(): return dict(form=auth()) +
    +
    +
    +
    + +
    + +
    + + +
    +

    define_tables(self, + username=True, + migrate=True, + fake_migrate=True) +

    +
    source code  +
    + +

    to be called unless tables are defined manually

    + usages: +
    +   # defines all needed tables and table files
    +   # 'myprefix_auth_user.table', ...
    +   auth.define_tables(migrate='myprefix_')
    +
    +   # defines all needed tables without migration/table files
    +   auth.define_tables(migrate=False)
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    log_event(self, + description, + origin='auth') +

    +
    source code  +
    + + usage: +
    +   auth.log_event(description='this happened', origin='auth')
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    get_or_create_user(self, + keys) +

    +
    source code  +
    + +
    +
    +Used for alternate login methods:
    +    If the user exists already then password is updated.
    +    If the user doesn't yet exist, then they are created.
    +
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    login(self, + next=<function <lambda> at 0x26ba500>, + onvalidation=<function <lambda> at 0x26ba500>, + onaccept=<function <lambda> at 0x26ba500>, + log=<function <lambda> at 0x26ba500>) +

    +
    source code  +
    + +
    +
    +returns a login form
    +
    +.. method:: Auth.login([next=DEFAULT [, onvalidation=DEFAULT
    +    [, onaccept=DEFAULT [, log=DEFAULT]]]])
    +
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    logout(self, + next=<function <lambda> at 0x26ba500>, + onlogout=<function <lambda> at 0x26ba500>, + log=<function <lambda> at 0x26ba500>) +

    +
    source code  +
    + +
    +
    +logout and redirects to login
    +
    +.. method:: Auth.logout ([next=DEFAULT[, onlogout=DEFAULT[,
    +    log=DEFAULT]]])
    +
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    register(self, + next=<function <lambda> at 0x26ba500>, + onvalidation=<function <lambda> at 0x26ba500>, + onaccept=<function <lambda> at 0x26ba500>, + log=<function <lambda> at 0x26ba500>) +

    +
    source code  +
    + +
    +
    +returns a registration form
    +
    +.. method:: Auth.register([next=DEFAULT [, onvalidation=DEFAULT
    +    [, onaccept=DEFAULT [, log=DEFAULT]]]])
    +
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    is_logged_in(self) +

    +
    source code  +
    + + checks if the user is logged in and returns True/False. if so user is + in auth.user as well as in session.auth.user +
    +
    +
    +
    + +
    + +
    + + +
    +

    verify_email(self, + next=<function <lambda> at 0x26ba500>, + onaccept=<function <lambda> at 0x26ba500>, + log=<function <lambda> at 0x26ba500>) +

    +
    source code  +
    + +
    +
    +action user to verify the registration email, XXXXXXXXXXXXXXXX
    +
    +.. method:: Auth.verify_email([next=DEFAULT [, onvalidation=DEFAULT
    +    [, onaccept=DEFAULT [, log=DEFAULT]]]])
    +
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    retrieve_username(self, + next=<function <lambda> at 0x26ba500>, + onvalidation=<function <lambda> at 0x26ba500>, + onaccept=<function <lambda> at 0x26ba500>, + log=<function <lambda> at 0x26ba500>) +

    +
    source code  +
    + +
    +
    +returns a form to retrieve the user username
    +(only if there is a username field)
    +
    +.. method:: Auth.retrieve_username([next=DEFAULT
    +    [, onvalidation=DEFAULT [, onaccept=DEFAULT [, log=DEFAULT]]]])
    +
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    reset_password_deprecated(self, + next=<function <lambda> at 0x26ba500>, + onvalidation=<function <lambda> at 0x26ba500>, + onaccept=<function <lambda> at 0x26ba500>, + log=<function <lambda> at 0x26ba500>) +

    +
    source code  +
    + +
    +
    +returns a form to reset the user password (deprecated)
    +
    +.. method:: Auth.reset_password_deprecated([next=DEFAULT
    +    [, onvalidation=DEFAULT [, onaccept=DEFAULT [, log=DEFAULT]]]])
    +
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    reset_password(self, + next=<function <lambda> at 0x26ba500>, + onvalidation=<function <lambda> at 0x26ba500>, + onaccept=<function <lambda> at 0x26ba500>, + log=<function <lambda> at 0x26ba500>) +

    +
    source code  +
    + +
    +
    +returns a form to reset the user password
    +
    +.. method:: Auth.reset_password([next=DEFAULT
    +    [, onvalidation=DEFAULT [, onaccept=DEFAULT [, log=DEFAULT]]]])
    +
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    request_reset_password(self, + next=<function <lambda> at 0x26ba500>, + onvalidation=<function <lambda> at 0x26ba500>, + onaccept=<function <lambda> at 0x26ba500>, + log=<function <lambda> at 0x26ba500>) +

    +
    source code  +
    + +
    +
    +returns a form to reset the user password
    +
    +.. method:: Auth.reset_password([next=DEFAULT
    +    [, onvalidation=DEFAULT [, onaccept=DEFAULT [, log=DEFAULT]]]])
    +
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    change_password(self, + next=<function <lambda> at 0x26ba500>, + onvalidation=<function <lambda> at 0x26ba500>, + onaccept=<function <lambda> at 0x26ba500>, + log=<function <lambda> at 0x26ba500>) +

    +
    source code  +
    + +
    +
    +returns a form that lets the user change password
    +
    +.. method:: Auth.change_password([next=DEFAULT[, onvalidation=DEFAULT[,
    +    onaccept=DEFAULT[, log=DEFAULT]]]])
    +
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    profile(self, + next=<function <lambda> at 0x26ba500>, + onvalidation=<function <lambda> at 0x26ba500>, + onaccept=<function <lambda> at 0x26ba500>, + log=<function <lambda> at 0x26ba500>) +

    +
    source code  +
    + +
    +
    +returns a form that lets the user change his/her profile
    +
    +.. method:: Auth.profile([next=DEFAULT [, onvalidation=DEFAULT
    +    [, onaccept=DEFAULT [, log=DEFAULT]]]])
    +
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    impersonate(self, + user_id=<function <lambda> at 0x26ba500>) +

    +
    source code  +
    + +

    usage: POST TO http://..../impersonate + request.post_vars.user_id=<id> set request.post_vars.user_id to 0 + to restore original user.

    + requires impersonator is logged in and has_permission('impersonate', + 'auth_user', user_id) +
    +
    +
    +
    + +
    + +
    + + +
    +

    requires_membership(self, + role=1, + group_id=1) +

    +
    source code  +
    + + decorator that prevents access to action if not logged in or if user + logged in is not a member of group_id. If role is provided instead of + group_id then the group_id is calculated. +
    +
    +
    +
    + +
    + +
    + + +
    +

    requires_signature(self) +

    +
    source code  +
    + + decorator that prevents access to action if not logged in or if user + logged in is not a member of group_id. If role is provided instead of + group_id then the group_id is calculated. +
    +
    +
    +
    + +
    + +
    + + +
    +

    user_group(self, + user_id=1) +

    +
    source code  +
    + + returns the group_id of the group uniquely associated to this user + i.e. role=user:[user_id] +
    +
    +
    +
    + +
    + +
    + + +
    +

    accessible_query(self, + name, + table, + user_id=1) +

    +
    source code  +
    + +

    returns a query with all accessible records for user_id or the current + logged in user this method does not work on GAE because uses JOIN and + IN

    + example: +
    +  db(auth.accessible_query('read', db.mytable)).select(db.mytable.ALL)
    +
    +
    +
    +
    +
    +
    + + + + + + +
    + + + + + +
    Property Details[hide private]
    +
    + +
    + +
    +

    user_id

    + user.id or None +
    +
    Get Method:
    +
    web2py.gluon.tools.Auth._get_user_id(self) + - accessor for auth.user_id +
    +
    Set Method:
    +
    +1
    +
    + + - PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    +
    Delete Method:
    +
    +1
    +
    + + - PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.tools.Crud-class.html Index: applications/examples/static/epydoc/web2py.gluon.tools.Crud-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.tools.Crud-class.html +++ applications/examples/static/epydoc/web2py.gluon.tools.Crud-class.html @@ -0,0 +1,756 @@ + + + + + web2py.gluon.tools.Crud + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module tools :: + Class Crud + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class Crud

    source code

    +
    +object --+
    +         |
    +        Crud
    +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    url(self, + f=1, + args=[], + vars={})
    + this should point to the controller that exposes download and + crud
    + source code + +
    + +
    +   + + + + + + +
    __init__(self, + environment, + db=1, + controller='default')
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    __call__(self) + source code + +
    + +
    +   + + + + + + +
    log_event(self, + message) + source code + +
    + +
    +   + + + + + + +
    has_permission(self, + name, + table, + record=0) + source code + +
    + +
    +   + + + + + + +
    tables(self) + source code + +
    + +
    +   + + + + + + +
    update(self, + table, + record, + next=<function <lambda> at 0x26ba500>, + onvalidation=<function <lambda> at 0x26ba500>, + onaccept=<function <lambda> at 0x26ba500>, + ondelete=<function <lambda> at 0x26ba500>, + log=<function <lambda> at 0x26ba500>, + message=<function <lambda> at 0x26ba500>, + deletable=<function <lambda> at 0x26ba500>, + formname=<function <lambda> at 0x26ba500>)
    + ..
    + source code + +
    + +
    +   + + + + + + +
    create(self, + table, + next=<function <lambda> at 0x26ba500>, + onvalidation=<function <lambda> at 0x26ba500>, + onaccept=<function <lambda> at 0x26ba500>, + log=<function <lambda> at 0x26ba500>, + message=<function <lambda> at 0x26ba500>, + formname=<function <lambda> at 0x26ba500>)
    + ..
    + source code + +
    + +
    +   + + + + + + +
    read(self, + table, + record) + source code + +
    + +
    +   + + + + + + +
    delete(self, + table, + record_id, + next=<function <lambda> at 0x26ba500>, + message=<function <lambda> at 0x26ba500>)
    + ..
    + source code + +
    + +
    +   + + + + + + +
    rows(self, + table, + query=1, + fields=1, + orderby=1, + limitby=1) + source code + +
    + +
    +   + + + + + + +
    select(self, + table, + query=1, + fields=1, + orderby=1, + limitby=1, + headers={}, + **attr) + source code + +
    + +
    +   + + + + + + +
    get_format(self, + field) + source code + +
    + +
    +   + + + + + + +
    get_query(self, + field, + op, + value, + refsearch=True) + source code + +
    + +
    +   + + + + + + +
    search(self, + *tables, + **args)
    + Creates a search form and its results for a table...
    + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +   + + + + + + +
    archive(form, + archive_table=1, + current_record='current_record')
    + If you have a table (db.mytable) that needs full revision history + you can just do:
    + source code + +
    + +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + environment, + db=1, + controller='default') +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    archive(form, + archive_table=1, + current_record='current_record') +
    Static Method +

    +
    source code  +
    + + If you have a table (db.mytable) that needs full revision history you + can just do: +
    +   form=crud.update(db.mytable,myrecord,onaccept=crud.archive)
    +
    +

    crud.archive will define a new table "mytable_archive" and + store the previous record in the newly created table including a + reference to the current record.

    + If you want to access such table you need to define it yourself in a + model: +
    +   db.define_table('mytable_archive',
    +       Field('current_record',db.mytable),
    +       db.mytable)
    +
    + Notice such table includes all fields of db.mytable plus one: + current_record. crud.archive does not timestamp the stored record unless + your original table has a fields like: +
    +   db.define_table(...,
    +       Field('saved_on','datetime',
    +            default=request.now,update=request.now,writable=False),
    +       Field('saved_by',auth.user,
    +            default=auth.user_id,update=auth.user_id,writable=False),
    +
    +

    there is nothing special about these fields since they are filled + before the record is archived.

    + If you want to change the archive table name and the name of the + reference field you can do, for example: +
    +   db.define_table('myhistory',
    +       Field('parent_record',db.mytable),
    +       db.mytable)
    +
    + and use it as: +
    +   form=crud.update(db.mytable,myrecord,
    +                    onaccept=lambda form:crud.archive(form,
    +                    archive_table=db.myhistory,
    +                    current_record='parent_record'))
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    update(self, + table, + record, + next=<function <lambda> at 0x26ba500>, + onvalidation=<function <lambda> at 0x26ba500>, + onaccept=<function <lambda> at 0x26ba500>, + ondelete=<function <lambda> at 0x26ba500>, + log=<function <lambda> at 0x26ba500>, + message=<function <lambda> at 0x26ba500>, + deletable=<function <lambda> at 0x26ba500>, + formname=<function <lambda> at 0x26ba500>) +

    +
    source code  +
    + +
    +
    +.. method:: Crud.update(table, record, [next=DEFAULT
    +    [, onvalidation=DEFAULT [, onaccept=DEFAULT [, log=DEFAULT
    +    [, message=DEFAULT[, deletable=DEFAULT]]]]]])
    +
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    create(self, + table, + next=<function <lambda> at 0x26ba500>, + onvalidation=<function <lambda> at 0x26ba500>, + onaccept=<function <lambda> at 0x26ba500>, + log=<function <lambda> at 0x26ba500>, + message=<function <lambda> at 0x26ba500>, + formname=<function <lambda> at 0x26ba500>) +

    +
    source code  +
    + +
    +
    +.. method:: Crud.create(table, [next=DEFAULT [, onvalidation=DEFAULT
    +    [, onaccept=DEFAULT [, log=DEFAULT[, message=DEFAULT]]]]])
    +
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    delete(self, + table, + record_id, + next=<function <lambda> at 0x26ba500>, + message=<function <lambda> at 0x26ba500>) +

    +
    source code  +
    + +
    +
    +.. method:: Crud.delete(table, record_id, [next=DEFAULT
    +    [, message=DEFAULT]])
    +
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    search(self, + *tables, + **args) +

    +
    source code  +
    + +
    +
    +Creates a search form and its results for a table
    +Example usage:
    +form, results = crud.search(db.test,
    +                       queries = ['equals', 'not equal', 'contains'],
    +                       query_labels={'equals':'Equals',
    +                                     'not equal':'Not equal'},
    +                       fields = ['id','children'],
    +                       field_labels = {'id':'ID','children':'Children'},
    +                       zero='Please choose',
    +                       query = (db.test.id > 0)&(db.test.id != 3) )
    +
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.tools.Mail-class.html Index: applications/examples/static/epydoc/web2py.gluon.tools.Mail-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.tools.Mail-class.html +++ applications/examples/static/epydoc/web2py.gluon.tools.Mail-class.html @@ -0,0 +1,529 @@ + + + + + web2py.gluon.tools.Mail + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module tools :: + Class Mail + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class Mail

    source code

    +
    +object --+
    +         |
    +        Mail
    +
    + +
    +

    Class for configuring and sending emails with alternative text / html + body, multiple attachments and encryption support

    + Works with SMTP and Google App Engine.

    + + + + + + + + + + +
    + + + + + +
    Nested Classes[hide private]
    +
    +   + + Attachment
    + Email attachment +
    + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + server=1, + sender=1, + login=1, + tls=True)
    + Main Mail object + +Arguments:: + + server: SMTP server address in address:port notation + sender: sender email address + login: sender login name and password in login:password notation + or None if no authentication is required + tls: enables/disables encryption (True by default) + +In Google App Engine use:: + + server='gae' + +For sake of backward compatibility all fields are optional and default +to None, however, to be able to send emails at least server and sender +must be specified.
    + source code + +
    + +
    +   + + + + + + +
    send(self, + to, + subject='None', + message='None', + attachments=1, + cc=1, + bcc=1, + reply_to=1, + encoding='utf-8')
    + Sends an email using data specified in constructor + +Arguments:: + + to: list or tuple of receiver addresses; will also accept single + object + subject: subject of the email + message: email body text; depends on type of passed object: + if 2-list or 2-tuple is passed: first element will be + source of plain text while second of html text; + otherwise: object will be the only source of plain text + and html source will be set to None; + If text or html source is: + None: content part will be ignored, + string: content part will be set to it, + file-like object: content part will be fetched from + it using it's read() method + attachments: list or tuple of Mail.Attachment objects; will also + accept single object + cc: list or tuple of carbon copy receiver addresses; will also + accept single object + bcc: list or tuple of blind carbon copy receiver addresses; will + also accept single object + reply_to: address to which reply should be composed + encoding: encoding of all strings passed to this method (including + message bodies) + +Examples:: + + #Send plain text message to single address: + mail.send('you@example.com', + 'Message subject', + 'Plain text body of the message') + + #Send html message to single address: + mail.send('you@example.com', + 'Message subject', + '<html>Plain text body of the message</html>') + + #Send text and html message to three addresses (two in cc): + mail.send('you@example.com', + 'Message subject', + ('Plain text body', '<html>html body</html>'), + cc=['other1@example.com', 'other2@example.com']) + + #Send html only message with image attachment available from + the message by 'photo' content id: + mail.send('you@example.com', + 'Message subject', + (None, '<html><img src="cid:photo" /></html>'), + Mail.Attachment('/path/to/photo.jpg' + content_id='photo')) + + #Send email with two attachments and no body text + mail.send('you@example.com, + 'Message subject', + None, + [Mail.Attachment('/path/to/fist.file'), + Mail.Attachment('/path/to/second.file')]) + +Returns True on success, False on failure.
    + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + server=1, + sender=1, + login=1, + tls=True) +
    (Constructor) +

    +
    source code  +
    + +
    +
    +Main Mail object
    +
    +Arguments::
    +
    +    server: SMTP server address in address:port notation
    +    sender: sender email address
    +    login: sender login name and password in login:password notation
    +           or None if no authentication is required
    +    tls: enables/disables encryption (True by default)
    +
    +In Google App Engine use::
    +
    +    server='gae'
    +
    +For sake of backward compatibility all fields are optional and default
    +to None, however, to be able to send emails at least server and sender
    +must be specified. They are available under following fields:
    +
    +    mail.settings.server
    +    mail.settings.sender
    +    mail.settings.login
    +
    +When server is 'logging', email is logged but not sent (debug mode)
    +
    +Optionally you can use PGP encryption or X509:
    +
    +    mail.settings.cipher_type = None
    +    mail.settings.sign = True
    +    mail.settings.sign_passphrase = None
    +    mail.settings.encrypt = True
    +    mail.settings.x509_sign_keyfile = None
    +    mail.settings.x509_sign_certfile = None
    +    mail.settings.x509_crypt_certfiles = None
    +
    +    cipher_type       : None
    +                        gpg - need a python-pyme package and gpgme lib
    +                        x509 - smime
    +    sign              : sign the message (True or False)
    +    sign_passphrase   : passphrase for key signing
    +    encrypt           : encrypt the message
    +                     ... x509 only ...
    +    x509_sign_keyfile : the signers private key filename (PEM format)
    +    x509_sign_certfile: the signers certificate filename (PEM format)
    +    x509_crypt_certfiles: the certificates file to encrypt the messages
    +                          with can be a file name or a list of
    +                          file names (PEM format)
    +
    +Examples::
    +
    +    #Create Mail object with authentication data for remote server:
    +    mail = Mail('example.com:25', 'me@example.com', 'me:password')
    +
    +
    +
    +
    Overrides: + object.__init__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    send(self, + to, + subject='None', + message='None', + attachments=1, + cc=1, + bcc=1, + reply_to=1, + encoding='utf-8') +

    +
    source code  +
    + +
    +
    +Sends an email using data specified in constructor
    +
    +Arguments::
    +
    +    to: list or tuple of receiver addresses; will also accept single
    +        object
    +    subject: subject of the email
    +    message: email body text; depends on type of passed object:
    +             if 2-list or 2-tuple is passed: first element will be
    +             source of plain text while second of html text;
    +             otherwise: object will be the only source of plain text
    +             and html source will be set to None;
    +             If text or html source is:
    +             None: content part will be ignored,
    +             string: content part will be set to it,
    +             file-like object: content part will be fetched from
    +                               it using it's read() method
    +    attachments: list or tuple of Mail.Attachment objects; will also
    +                 accept single object
    +    cc: list or tuple of carbon copy receiver addresses; will also
    +        accept single object
    +    bcc: list or tuple of blind carbon copy receiver addresses; will
    +        also accept single object
    +    reply_to: address to which reply should be composed
    +    encoding: encoding of all strings passed to this method (including
    +              message bodies)
    +
    +Examples::
    +
    +    #Send plain text message to single address:
    +    mail.send('you@example.com',
    +              'Message subject',
    +              'Plain text body of the message')
    +
    +    #Send html message to single address:
    +    mail.send('you@example.com',
    +              'Message subject',
    +              '<html>Plain text body of the message</html>')
    +
    +    #Send text and html message to three addresses (two in cc):
    +    mail.send('you@example.com',
    +              'Message subject',
    +              ('Plain text body', '<html>html body</html>'),
    +              cc=['other1@example.com', 'other2@example.com'])
    +
    +    #Send html only message with image attachment available from
    +    the message by 'photo' content id:
    +    mail.send('you@example.com',
    +              'Message subject',
    +              (None, '<html><img src="cid:photo" /></html>'),
    +              Mail.Attachment('/path/to/photo.jpg'
    +                              content_id='photo'))
    +
    +    #Send email with two attachments and no body text
    +    mail.send('you@example.com,
    +              'Message subject',
    +              None,
    +              [Mail.Attachment('/path/to/fist.file'),
    +               Mail.Attachment('/path/to/second.file')])
    +
    +Returns True on success, False on failure.
    +
    +Before return, method updates two object's fields:
    +self.result: return value of smtplib.SMTP.sendmail() or GAE's
    +             mail.send_mail() method
    +self.error: Exception message or None if above was successful
    +
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.tools.Mail.Attachment-class.html Index: applications/examples/static/epydoc/web2py.gluon.tools.Mail.Attachment-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.tools.Mail.Attachment-class.html +++ applications/examples/static/epydoc/web2py.gluon.tools.Mail.Attachment-class.html @@ -0,0 +1,311 @@ + + + + + web2py.gluon.tools.Mail.Attachment + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module tools :: + Class Mail :: + Class Attachment + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class Attachment

    source code

    +
    +email.message.Message --+    
    +                        |    
    + email.mime.base.MIMEBase --+
    +                            |
    +                           Mail.Attachment
    +
    + +
    +

    Email attachment

    + Arguments: +
    +   payload: path to file or file-like object with read() method
    +   filename: name of the attachment stored in message; if set to
    +             None, it will be fetched from payload path; file-like
    +             object payload must have explicit filename specified
    +   content_id: id of the attachment; automatically contained within
    +               < and >
    +   content_type: content type of the attachment; if set to None,
    +                 it will be fetched from filename using gluon.contenttype
    +                 module
    +   encoding: encoding of all strings passed to this function (except
    +             attachment body)
    +
    +

    Content ID is used to identify attachments within the html body; in + example, attached image with content ID 'photo' may be used in html + message as a source of img tag <img src="cid:photo" + />.

    + Examples: +
    +   #Create attachment from text file:
    +   attachment = Mail.Attachment('/path/to/file.txt')
    +
    +   Content-Type: text/plain
    +   MIME-Version: 1.0
    +   Content-Disposition: attachment; filename="file.txt"
    +   Content-Transfer-Encoding: base64
    +
    +   SOMEBASE64CONTENT=
    +
    +   #Create attachment from image file with custom filename and cid:
    +   attachment = Mail.Attachment('/path/to/file.png',
    +                                    filename='photo.png',
    +                                    content_id='photo')
    +
    +   Content-Type: image/png
    +   MIME-Version: 1.0
    +   Content-Disposition: attachment; filename="photo.png"
    +   Content-Id: <photo>
    +   Content-Transfer-Encoding: base64
    +
    +   SOMEOTHERBASE64CONTENT=
    +


    + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + payload, + filename=1, + content_id=1, + content_type=1, + encoding='utf-8')
    + This constructor adds a Content-Type: and a MIME-Version: + header.
    + source code + +
    + +
    +

    Inherited from email.message.Message: + __contains__, + __delitem__, + __getitem__, + __len__, + __setitem__, + __str__, + add_header, + as_string, + attach, + del_param, + get, + get_all, + get_boundary, + get_charset, + get_charsets, + get_content_charset, + get_content_maintype, + get_content_subtype, + get_content_type, + get_default_type, + get_filename, + get_param, + get_params, + get_payload, + get_unixfrom, + has_key, + is_multipart, + items, + keys, + replace_header, + set_boundary, + set_charset, + set_default_type, + set_param, + set_payload, + set_type, + set_unixfrom, + values, + walk +

    +

    Inherited from email.message.Message (private): + _get_params_preserve +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + payload, + filename=1, + content_id=1, + content_type=1, + encoding='utf-8') +
    (Constructor) +

    +
    source code  +
    + +

    This constructor adds a Content-Type: and a MIME-Version: header.

    + The Content-Type: header is taken from the _maintype and _subtype + arguments. Additional parameters for this header are taken from the + keyword arguments. +
    +
    Overrides: + email.mime.base.MIMEBase.__init__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.tools.PluginManager-class.html Index: applications/examples/static/epydoc/web2py.gluon.tools.PluginManager-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.tools.PluginManager-class.html +++ applications/examples/static/epydoc/web2py.gluon.tools.PluginManager-class.html @@ -0,0 +1,430 @@ + + + + + web2py.gluon.tools.PluginManager + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module tools :: + Class PluginManager + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class PluginManager

    source code

    +
    +object --+
    +         |
    +        PluginManager
    +
    + +
    +

    Plugin Manager is similar to a storage object but it is a single level + singleton this means that multiple instances within the same thread share + the same attributes Its constructor is also special. The first argument + is the name of the plugin you are defining. The named arguments are + parameters needed by the plugin with default values. If the parameters + were previous defined, the old values are used.

    +

    For example:

    +

    ### in some general configuration file: >>> plugins = + PluginManager() >>> plugins.me.param1=3

    +

    ### within the plugin model >>> _ = + PluginManager('me',param1=5,param2=6,param3=7)

    +

    ### where the plugin is used >>> print plugins.me.param1 3 + >>> print plugins.me.param2 6 >>> plugins.me.param3 = 8 + >>> print plugins.me.param3 8

    + Here are some tests: +
    +>>> a=PluginManager()
    +>>> a.x=6
    +>>> b=PluginManager('check')
    +>>> print b.x
    +6
    +>>> b=PluginManager() # reset settings
    +>>> print b.x
    +<Storage {}>
    +>>> b.x=7
    +>>> print a.x
    +7
    +>>> a.y.z=8
    +>>> print b.y.z
    +8
    +>>> test_thread_separation()
    +5
    +>>> plugins=PluginManager('me',db='mydb')
    +>>> print plugins.me.db
    +mydb
    +>>> print 'me' in plugins
    +True
    +>>> print plugins.me.installed
    +True


    + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + plugin=1, + **defaults)
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    __getattr__(self, + key) + source code + +
    + +
    +   + + + + + + +
    keys(self) + source code + +
    + +
    +   + + + + + + +
    __contains__(self, + key) + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +   + + + + + + +
    __new__(cls, + *a, + **b)
    + Returns: +a new object with type S, a subtype of T
    + source code + +
    + +
    + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + instances = {} +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __new__(cls, + *a, + **b) +
    Static Method +

    +
    source code  +
    + + +
    +
    Returns:
    +
    +a new object with type S, a subtype of T
    +
    +
    +
    Overrides: + object.__new__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    __init__(self, + plugin=1, + **defaults) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.tools.Recaptcha-class.html Index: applications/examples/static/epydoc/web2py.gluon.tools.Recaptcha-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.tools.Recaptcha-class.html +++ applications/examples/static/epydoc/web2py.gluon.tools.Recaptcha-class.html @@ -0,0 +1,419 @@ + + + + + web2py.gluon.tools.Recaptcha + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module tools :: + Class Recaptcha + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class Recaptcha

    source code

    +
    +             object --+        
    +                      |        
    +gluon.html.XmlComponent --+    
    +                          |    
    +             gluon.html.DIV --+
    +                              |
    +                             Recaptcha
    +
    + +
    + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + request, + public_key='', + private_key='', + use_ssl=True, + error=1, + error_message='invalid', + label='Verify:', + options='')
    + :param *components: any components that should be nested in this + element :param **attributes: any attributes you want to give to this + element
    + source code + +
    + +
    +   + + + + + + +
    _validate(self)
    + nothing to validate yet.
    + source code + +
    + +
    +   + + + + + + +
    xml(self)
    + generates the xml for this component.
    + source code + +
    + +
    +

    Inherited from gluon.html.DIV: + __delitem__, + __getitem__, + __len__, + __nonzero__, + __setitem__, + __str__, + append, + element, + elements, + flatten, + insert, + sibling, + siblings, + update +

    +

    Inherited from gluon.html.DIV (private): + _fixup, + _postprocessing, + _setnode, + _traverse, + _wrap_components, + _xml +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__ +

    +
    + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + API_SSL_SERVER = 'https://www.google.com/recaptcha/api' +
    +   + + API_SERVER = 'http://www.google.com/recaptcha/api' +
    +   + + VERIFY_SERVER = 'http://www.google.com/recaptcha/api/verify' +
    +

    Inherited from gluon.html.DIV: + regex_attr, + regex_class, + regex_id, + regex_tag, + tag +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + request, + public_key='', + private_key='', + use_ssl=True, + error=1, + error_message='invalid', + label='Verify:', + options='') +
    (Constructor) +

    +
    source code  +
    + +

    :param *components: any components that should be nested in this + element :param **attributes: any attributes you want to give to this + element

    + :raises SyntaxError: when a stand alone tag receives components +
    +
    Overrides: + gluon.html.DIV.__init__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    _validate(self) +

    +
    source code  +
    + + nothing to validate yet. May be overridden by subclasses +
    +
    Overrides: + gluon.html.DIV._validate +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    xml(self) +

    +
    source code  +
    + + generates the xml for this component. +
    +
    Overrides: + gluon.html.DIV.xml +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.tools.Service-class.html Index: applications/examples/static/epydoc/web2py.gluon.tools.Service-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.tools.Service-class.html +++ applications/examples/static/epydoc/web2py.gluon.tools.Service-class.html @@ -0,0 +1,1007 @@ + + + + + web2py.gluon.tools.Service + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module tools :: + Class Service + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class Service

    source code

    +
    +object --+
    +         |
    +        Service
    +
    + +
    + + + + + + + + + +
    + + + + + +
    Nested Classes[hide private]
    +
    +   + + JsonRpcException +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + environment=1)
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    run(self, + f)
    + example:
    + source code + +
    + +
    +   + + + + + + +
    csv(self, + f)
    + example:
    + source code + +
    + +
    +   + + + + + + +
    xml(self, + f)
    + example:
    + source code + +
    + +
    +   + + + + + + +
    rss(self, + f)
    + example:
    + source code + +
    + +
    +   + + + + + + +
    json(self, + f)
    + example:
    + source code + +
    + +
    +   + + + + + + +
    jsonrpc(self, + f)
    + example:
    + source code + +
    + +
    +   + + + + + + +
    xmlrpc(self, + f)
    + example:
    + source code + +
    + +
    +   + + + + + + +
    amfrpc(self, + f)
    + example:
    + source code + +
    + +
    +   + + + + + + +
    amfrpc3(self, + domain='default')
    + example:
    + source code + +
    + +
    +   + + + + + + +
    soap(self, + name=1, + returns=1, + args=1, + doc=1)
    + example::...
    + source code + +
    + +
    +   + + + + + + +
    serve_run(self, + args=1) + source code + +
    + +
    +   + + + + + + +
    serve_csv(self, + args=1) + source code + +
    + +
    +   + + + + + + +
    serve_xml(self, + args=1) + source code + +
    + +
    +   + + + + + + +
    serve_rss(self, + args=1) + source code + +
    + +
    +   + + + + + + +
    serve_json(self, + args=1) + source code + +
    + +
    +   + + + + + + +
    serve_jsonrpc(self) + source code + +
    + +
    +   + + + + + + +
    serve_xmlrpc(self) + source code + +
    + +
    +   + + + + + + +
    serve_amfrpc(self, + version=0) + source code + +
    + +
    +   + + + + + + +
    serve_soap(self, + version='1.1') + source code + +
    + +
    +   + + + + + + +
    __call__(self)
    + register services with: service = Service(globals()) @service.run + @service.rss @service.json @service.jsonrpc @service.xmlrpc + @service.jsonrpc @service.amfrpc @service.amfrpc3('domain') + @service.soap('Method', returns={'Result':int}, + args={'a':int,'b':int,})
    + source code + +
    + +
    +   + + + + + + +
    error(self) + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + environment=1) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    run(self, + f) +

    +
    source code  +
    + + example: +
    +   service = Service(globals())
    +   @service.run
    +   def myfunction(a, b):
    +       return a + b
    +   def call():
    +       return service()
    +
    + Then call it with: +
    +   wget http://..../app/default/call/run/myfunction?a=3&b=4
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    csv(self, + f) +

    +
    source code  +
    + + example: +
    +   service = Service(globals())
    +   @service.csv
    +   def myfunction(a, b):
    +       return a + b
    +   def call():
    +       return service()
    +
    + Then call it with: +
    +   wget http://..../app/default/call/csv/myfunction?a=3&b=4
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    xml(self, + f) +

    +
    source code  +
    + + example: +
    +   service = Service(globals())
    +   @service.xml
    +   def myfunction(a, b):
    +       return a + b
    +   def call():
    +       return service()
    +
    + Then call it with: +
    +   wget http://..../app/default/call/xml/myfunction?a=3&b=4
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    rss(self, + f) +

    +
    source code  +
    + + example: +
    +   service = Service(globals())
    +   @service.rss
    +   def myfunction():
    +       return dict(title=..., link=..., description=...,
    +           created_on=..., entries=[dict(title=..., link=...,
    +               description=..., created_on=...])
    +   def call():
    +       return service()
    +
    + Then call it with: +
    +   wget http://..../app/default/call/rss/myfunction
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    json(self, + f) +

    +
    source code  +
    + + example: +
    +   service = Service(globals())
    +   @service.json
    +   def myfunction(a, b):
    +       return [{a: b}]
    +   def call():
    +       return service()
    +
    + Then call it with: +
    +   wget http://..../app/default/call/json/myfunction?a=hello&b=world
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    jsonrpc(self, + f) +

    +
    source code  +
    + + example: +
    +   service = Service(globals())
    +   @service.jsonrpc
    +   def myfunction(a, b):
    +       return a + b
    +   def call():
    +       return service()
    +
    + Then call it with: +
    +   wget http://..../app/default/call/jsonrpc/myfunction?a=hello&b=world
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    xmlrpc(self, + f) +

    +
    source code  +
    + + example: +
    +   service = Service(globals())
    +   @service.xmlrpc
    +   def myfunction(a, b):
    +       return a + b
    +   def call():
    +       return service()
    +
    + The call it with: +
    +   wget http://..../app/default/call/xmlrpc/myfunction?a=hello&b=world
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    amfrpc(self, + f) +

    +
    source code  +
    + + example: +
    +   service = Service(globals())
    +   @service.amfrpc
    +   def myfunction(a, b):
    +       return a + b
    +   def call():
    +       return service()
    +
    + The call it with: +
    +   wget http://..../app/default/call/amfrpc/myfunction?a=hello&b=world
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    amfrpc3(self, + domain='default') +

    +
    source code  +
    + + example: +
    +   service = Service(globals())
    +   @service.amfrpc3('domain')
    +   def myfunction(a, b):
    +       return a + b
    +   def call():
    +       return service()
    +
    + The call it with: +
    +   wget http://..../app/default/call/amfrpc3/myfunction?a=hello&b=world
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    soap(self, + name=1, + returns=1, + args=1, + doc=1) +

    +
    source code  +
    + +
    +
    +example::
    +
    +    service = Service(globals())
    +    @service.soap('MyFunction',returns={'result':int},args={'a':int,'b':int,})
    +    def myfunction(a, b):
    +        return a + b
    +    def call():
    +        return service()
    +
    +The call it with::
    +
    +    from gluon.contrib.pysimplesoap.client import SoapClient
    +    client = SoapClient(wsdl="http://..../app/default/call/soap?WSDL")
    +    response = client.MyFunction(a=1,b=2)
    +    return response['result']
    +
    +Exposes online generated documentation and xml example messages at:
    +- http://..../app/default/call/soap
    +
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    __call__(self) +
    (Call operator) +

    +
    source code  +
    + +

    register services with: service = Service(globals()) @service.run + @service.rss @service.json @service.jsonrpc @service.xmlrpc + @service.jsonrpc @service.amfrpc @service.amfrpc3('domain') + @service.soap('Method', returns={'Result':int}, + args={'a':int,'b':int,})

    +

    expose services with

    +

    def call(): return service()

    + call services with http://..../app/default/call/run?[parameters] + http://..../app/default/call/rss?[parameters] + http://..../app/default/call/json?[parameters] + http://..../app/default/call/jsonrpc http://..../app/default/call/xmlrpc + http://..../app/default/call/amfrpc http://..../app/default/call/amfrpc3 + http://..../app/default/call/soap +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.tools.Service.JsonRpcException-class.html Index: applications/examples/static/epydoc/web2py.gluon.tools.Service.JsonRpcException-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.tools.Service.JsonRpcException-class.html +++ applications/examples/static/epydoc/web2py.gluon.tools.Service.JsonRpcException-class.html @@ -0,0 +1,263 @@ + + + + + web2py.gluon.tools.Service.JsonRpcException + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module tools :: + Class Service :: + Class JsonRpcException + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class JsonRpcException

    source code

    +
    +              object --+        
    +                       |        
    +exceptions.BaseException --+    
    +                           |    
    +        exceptions.Exception --+
    +                               |
    +                              Service.JsonRpcException
    +
    + +
    + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + code, + info)
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +

    Inherited from exceptions.Exception: + __new__ +

    +

    Inherited from exceptions.BaseException: + __delattr__, + __getattribute__, + __getitem__, + __getslice__, + __reduce__, + __repr__, + __setattr__, + __setstate__, + __str__ +

    +

    Inherited from object: + __hash__, + __reduce_ex__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from exceptions.BaseException: + args, + message +

    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + code, + info) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + exceptions.Exception.__init__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.utils-module.html Index: applications/examples/static/epydoc/web2py.gluon.utils-module.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.utils-module.html +++ applications/examples/static/epydoc/web2py.gluon.utils-module.html @@ -0,0 +1,402 @@ + + + + + web2py.gluon.utils + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module utils + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Module utils

    source code

    +

    This file is part of the web2py Web Framework Copyrighted by Massimo + Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)

    + This file specifically includes utilities for security.

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Functions[hide private]
    +
    +   + + + + + + +
    md5_hash(text)
    + Generate a md5 hash with the given text
    + source code + +
    + +
    +   + + + + + + +
    simple_hash(text, + digest_alg='md5')
    + Generates hash with the given text using the specified digest + hashing algorithm
    + source code + +
    + +
    +   + + + + + + +
    get_digest(value)
    + Returns a hashlib digest algorithm from a string
    + source code + +
    + +
    +   + + + + + + +
    hmac_hash(value, + key, + digest_alg='md5', + salt=1) + source code + +
    + +
    +   + + + + + + +
    initialize_urandom()
    + This function and the web2py_uuid follow from the following + discussion: + http://groups.google.com/group/web2py-developers/browse_thread/thread/7fd5789a7da3f09
    + source code + +
    + +
    +   + + + + + + +
    web2py_uuid()
    + This function follows from the following discussion: + http://groups.google.com/group/web2py-developers/browse_thread/thread/7fd5789a7da3f09
    + source code + +
    + +
    + + + + + + + + + + + + +
    + + + + + +
    Variables[hide private]
    +
    +   + + logger = logging.getLogger("web2py") +
    +   + + ctokens = [176, 173, 75, 184, 184, 169, 176, 173, 75, 184, 184... +
    + + + + + + +
    + + + + + +
    Function Details[hide private]
    +
    + +
    + +
    + + +
    +

    initialize_urandom() +

    +
    source code  +
    + +

    This function and the web2py_uuid follow from the following + discussion: + http://groups.google.com/group/web2py-developers/browse_thread/thread/7fd5789a7da3f09

    +

    At startup web2py compute a unique ID that identifies the machine by + adding uuid.getnode() + int(time.time() * 1e3)

    +

    This is a 48-bit number. It converts the number into 16 8-bit tokens. + It uses this value to initialize the entropy source ('/dev/urandom') and + to seed random.

    + If os.random() is not supported, it falls back to using random and + issues a warning. +
    +
    +
    +
    + +
    + +
    + + +
    +

    web2py_uuid() +

    +
    source code  +
    + +

    This function follows from the following discussion: + http://groups.google.com/group/web2py-developers/browse_thread/thread/7fd5789a7da3f09

    + It works like uuid.uuid4 except that tries to use os.urandom() if + possible and it XORs the output with the tokens uniquely associated with + this machine. +
    +
    +
    +
    +
    + + + + + + +
    + + + + + +
    Variables Details[hide private]
    +
    + +
    + +
    +

    ctokens

    + +
    +
    +
    +
    Value:
    +
    +[176,
    + 173,
    + 75,
    + 184,
    + 184,
    + 169,
    + 176,
    + 173,
    +...
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.utils-pysrc.html Index: applications/examples/static/epydoc/web2py.gluon.utils-pysrc.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.utils-pysrc.html +++ applications/examples/static/epydoc/web2py.gluon.utils-pysrc.html @@ -0,0 +1,293 @@ + + + + + web2py.gluon.utils + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module utils + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Module web2py.gluon.utils

    +
    +  1  #!/usr/bin/env python 
    +  2  # -*- coding: utf-8 -*- 
    +  3   
    +  4  """ 
    +  5  This file is part of the web2py Web Framework 
    +  6  Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> 
    +  7  License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) 
    +  8   
    +  9  This file specifically includes utilities for security. 
    + 10  """ 
    + 11   
    + 12  import hashlib 
    + 13  import hmac 
    + 14  import uuid 
    + 15  import random 
    + 16  import time 
    + 17  import os 
    + 18  import logging 
    + 19   
    + 20  logger = logging.getLogger("web2py") 
    + 21   
    +
    22 -def md5_hash(text): +
    23 """ Generate a md5 hash with the given text """ + 24 return hashlib.md5(text).hexdigest() +
    25 +
    26 -def simple_hash(text, digest_alg = 'md5'): +
    27 """ + 28 Generates hash with the given text using the specified + 29 digest hashing algorithm + 30 """ + 31 if not digest_alg: + 32 raise RuntimeError, "simple_hash with digest_alg=None" + 33 elif not isinstance(digest_alg,str): + 34 h = digest_alg(text) + 35 else: + 36 h = hashlib.new(digest_alg) + 37 h.update(text) + 38 return h.hexdigest() +
    39 +
    40 -def get_digest(value): +
    41 """ + 42 Returns a hashlib digest algorithm from a string + 43 """ + 44 if not isinstance(value,str): + 45 return value + 46 value = value.lower() + 47 if value == "md5": + 48 return hashlib.md5 + 49 elif value == "sha1": + 50 return hashlib.sha1 + 51 elif value == "sha224": + 52 return hashlib.sha224 + 53 elif value == "sha256": + 54 return hashlib.sha256 + 55 elif value == "sha384": + 56 return hashlib.sha384 + 57 elif value == "sha512": + 58 return hashlib.sha512 + 59 else: + 60 raise ValueError("Invalid digest algorithm") +
    61 +
    62 -def hmac_hash(value, key, digest_alg='md5', salt=None): +
    63 if ':' in key: + 64 digest_alg, key = key.split(':') + 65 digest_alg = get_digest(digest_alg) + 66 d = hmac.new(key,value,digest_alg) + 67 if salt: + 68 d.update(str(salt)) + 69 return d.hexdigest() +
    70 + 71 + 72 ### compute constant ctokens +
    73 -def initialize_urandom(): +
    74 """ + 75 This function and the web2py_uuid follow from the following discussion: + 76 http://groups.google.com/group/web2py-developers/browse_thread/thread/7fd5789a7da3f09 + 77 + 78 At startup web2py compute a unique ID that identifies the machine by adding + 79 uuid.getnode() + int(time.time() * 1e3) + 80 + 81 This is a 48-bit number. It converts the number into 16 8-bit tokens. + 82 It uses this value to initialize the entropy source ('/dev/urandom') and to seed random. + 83 + 84 If os.random() is not supported, it falls back to using random and issues a warning. + 85 """ + 86 node_id = uuid.getnode() + 87 microseconds = int(time.time() * 1e6) + 88 ctokens = [((node_id + microseconds) >> ((i%6)*8)) % 256 for i in range(16)] + 89 random.seed(node_id + microseconds) + 90 try: + 91 os.urandom(1) + 92 try: + 93 # try to add process-specific entropy + 94 frandom = open('/dev/urandom','wb') + 95 try: + 96 frandom.write(''.join(chr(t) for t in ctokens)) + 97 finally: + 98 frandom.close() + 99 except IOError: +100 # works anyway +101 pass +102 except NotImplementedError: +103 logger.warning( +104 """Cryptographically secure session management is not possible on your system because +105 your system does not provide a cryptographically secure entropy source. +106 This is not specific to web2py; consider deploying on a different operating system.""") +107 return ctokens +
    108 ctokens = initialize_urandom() +109 +
    110 -def web2py_uuid(): +
    111 """ +112 This function follows from the following discussion: +113 http://groups.google.com/group/web2py-developers/browse_thread/thread/7fd5789a7da3f09 +114 +115 It works like uuid.uuid4 except that tries to use os.urandom() if possible +116 and it XORs the output with the tokens uniquely associated with this machine. +117 """ +118 bytes = [random.randrange(256) for i in range(16)] +119 try: +120 ubytes = [ord(c) for c in os.urandom(16)] # use /dev/urandom if possible +121 bytes = [bytes[i] ^ ubytes[i] for i in range(16)] +122 except NotImplementedError: +123 pass +124 ## xor bytes with constant ctokens +125 bytes = ''.join(chr(c ^ ctokens[i]) for i,c in enumerate(bytes)) +126 return str(uuid.UUID(bytes=bytes, version=4)) +
    127 +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.validators-module.html Index: applications/examples/static/epydoc/web2py.gluon.validators-module.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.validators-module.html +++ applications/examples/static/epydoc/web2py.gluon.validators-module.html @@ -0,0 +1,1034 @@ + + + + + web2py.gluon.validators + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module validators + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Module validators

    source code

    +

    This file is part of the web2py Web Framework Copyrighted by Massimo + Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)

    + Thanks to ga2arch for help with IS_IN_DB and IS_NOT_IN_DB on GAE

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Classes[hide private]
    +
    +   + + Validator
    + Root for all validators, mainly for documentation purposes. +
    +   + + IS_MATCH
    + example: +
    +   + + IS_EQUAL_TO
    + example: +
    +   + + IS_EXPR
    + example: +
    +   + + IS_LENGTH
    + Checks if length of field's value fits between given + boundaries. +
    +   + + IS_IN_SET
    + example: +
    +   + + IS_IN_DB
    + example: +
    +   + + IS_NOT_IN_DB
    + example: +
    +   + + IS_INT_IN_RANGE
    + Determine that the argument is (or can be represented as) an + int, and that it falls within the specified range. +
    +   + + IS_FLOAT_IN_RANGE
    + Determine that the argument is (or can be represented as) a + float, and that it falls within the specified inclusive range. +
    +   + + IS_DECIMAL_IN_RANGE
    + Determine that the argument is (or can be represented as) a + Python Decimal, and that it falls within the specified inclusive + range. +
    +   + + IS_NOT_EMPTY
    + example: +
    +   + + IS_ALPHANUMERIC
    + example: +
    +   + + IS_EMAIL
    + Checks if field's value is a valid email address. +
    +   + + IS_GENERIC_URL
    + Rejects a URL string if any of the following is true: + * The string is empty or None + * The string uses characters that are not allowed in a URL + * The URL scheme specified (if one is specified) is not valid + +Based on RFC 2396: http://www.faqs.org/rfcs/rfc2396.html + +This function only checks the URL's syntax. +
    +   + + IS_HTTP_URL
    + Rejects a URL string if any of the following is true: + * The string is empty or None + * The string uses characters that are not allowed in a URL + * The string breaks any of the HTTP syntactic rules + * The URL scheme specified (if one is specified) is not 'http' or 'https' + * The top-level domain (if a host name is specified) does not exist + +Based on RFC 2616: http://www.faqs.org/rfcs/rfc2616.html + +This function only checks the URL's syntax. +
    +   + + IS_URL
    + Rejects a URL string if any of the following is true: + * The string is empty or None + * The string uses characters that are not allowed in a URL + * The string breaks any of the HTTP syntactic rules + * The URL scheme specified (if one is specified) is not 'http' or 'https' + * The top-level domain (if a host name is specified) does not exist + +(These rules are based on RFC 2616: http://www.faqs.org/rfcs/rfc2616.html) + +This function only checks the URL's syntax. +
    +   + + IS_TIME
    + example: +
    +   + + IS_DATE
    + example: +
    +   + + IS_DATETIME
    + example: +
    +   + + IS_DATE_IN_RANGE
    + example: +
    +   + + IS_DATETIME_IN_RANGE
    + example: +
    +   + + IS_LIST_OF +
    +   + + IS_LOWER
    + convert to lower case +
    +   + + IS_UPPER
    + convert to upper case +
    +   + + IS_SLUG
    + convert arbitrary text string to a slug +
    +   + + IS_EMPTY_OR
    + dummy class for testing IS_EMPTY_OR +
    +   + + IS_NULL_OR
    + dummy class for testing IS_EMPTY_OR +
    +   + + CLEANUP
    + example: +
    +   + + CRYPT
    + example: +
    +   + + IS_STRONG
    + example: +
    +   + + IS_IN_SUBSET +
    +   + + IS_IMAGE
    + Checks if file uploaded through file input was saved in one of + selected image formats and has dimensions (width and height) within + given boundaries. +
    +   + + IS_UPLOAD_FILENAME
    + Checks if name and extension of file uploaded through file input matches +given criteria. +
    +   + + IS_IPV4
    + Checks if field's value is an IP version 4 address in decimal form. +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Functions[hide private]
    +
    +   + + + + + + +
    translate(text) + source code + +
    + +
    +   + + + + + + +
    options_sorter(x, + y) + source code + +
    + +
    +   + + + + + + +
    is_empty(value, + empty_regex=1)
    + test empty field
    + source code + +
    + +
    +   + + + + + + +
    escape_unicode(string)
    + Converts a unicode string into US-ASCII, using a simple conversion scheme.
    + source code + +
    + +
    +   + + + + + + +
    unicode_to_ascii_authority(authority)
    + Follows the steps in RFC 3490, Section 4 to convert a unicode authority +string into its ASCII equivalent.
    + source code + +
    + +
    +   + + + + + + +
    unicode_to_ascii_url(url, + prepend_scheme)
    + Converts the inputed unicode url into a US-ASCII equivalent.
    + source code + +
    + +
    +   + + + + + + +
    urlify(value, + maxlen=80, + keep_underscores=True)
    + Convert incoming string to a simplified ASCII subset.
    + source code + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Variables[hide private]
    +
    +   + + regex1 = re.compile(r'[\w_]+\.[\w_]+') +
    +   + + regex2 = re.compile(r'%\((?P<name>[^\)]+)\)s') +
    +   + + official_url_schemes = ['aaa', 'aaas', 'acap', 'cap', 'cid', '... +
    +   + + unofficial_url_schemes = ['about', 'adiumxtra', 'aim', 'afp', ... +
    +   + + all_url_schemes = [None, 'aaa', 'aaas', 'acap', 'cap', 'cid', ... +
    +   + + http_schemes = [None, 'http', 'https'] +
    +   + + url_split_regex = re.compile(r'^(([^:/\?#]+):)?(//([^/\?#]*))?... +
    +   + + label_split_regex = re.compile(r'[\.\u3002\uff0e\uff61]') +
    +   + + official_top_level_domains = ['ac', 'ad', 'ae', 'aero', 'af', ... +
    +   + + regex_time = re.compile(r'((?P<h>[0-9]+))([^0-9 ]+(?P<m>[0-9 ]... +
    + + + + + + +
    + + + + + +
    Function Details[hide private]
    +
    + +
    + +
    + + +
    +

    escape_unicode(string) +

    +
    source code  +
    + +
    +
    +Converts a unicode string into US-ASCII, using a simple conversion scheme.
    +Each unicode character that does not have a US-ASCII equivalent is
    +converted into a URL escaped form based on its hexadecimal value.
    +For example, the unicode character '\u4e86' will become the string '%4e%86'
    +
    +:param string: unicode string, the unicode string to convert into an
    +    escaped US-ASCII form
    +:returns: the US-ASCII escaped form of the inputted string
    +:rtype: string
    +
    +@author: Jonathan Benn
    +
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    unicode_to_ascii_authority(authority) +

    +
    source code  +
    + +
    +
    +Follows the steps in RFC 3490, Section 4 to convert a unicode authority
    +string into its ASCII equivalent.
    +For example, u'www.Alliancefrançaise.nu' will be converted into
    +'www.xn--alliancefranaise-npb.nu'
    +
    +:param authority: unicode string, the URL authority component to convert,
    +                  e.g. u'www.Alliancefrançaise.nu'
    +:returns: the US-ASCII character equivalent to the inputed authority,
    +         e.g. 'www.xn--alliancefranaise-npb.nu'
    +:rtype: string
    +:raises Exception: if the function is not able to convert the inputed
    +    authority
    +
    +@author: Jonathan Benn
    +
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    unicode_to_ascii_url(url, + prepend_scheme) +

    +
    source code  +
    + +
    +
    +Converts the inputed unicode url into a US-ASCII equivalent. This function
    +goes a little beyond RFC 3490, which is limited in scope to the domain name
    +(authority) only. Here, the functionality is expanded to what was observed
    +on Wikipedia on 2009-Jan-22:
    +
    +   Component    Can Use Unicode?
    +   ---------    ----------------
    +   scheme       No
    +   authority    Yes
    +   path         Yes
    +   query        Yes
    +   fragment     No
    +
    +The authority component gets converted to punycode, but occurrences of
    +unicode in other components get converted into a pair of URI escapes (we
    +assume 4-byte unicode). E.g. the unicode character U+4E2D will be
    +converted into '%4E%2D'. Testing with Firefox v3.0.5 has shown that it can
    +understand this kind of URI encoding.
    +
    +:param url: unicode string, the URL to convert from unicode into US-ASCII
    +:param prepend_scheme: string, a protocol scheme to prepend to the URL if
    +    we're having trouble parsing it.
    +    e.g. "http". Input None to disable this functionality
    +:returns: a US-ASCII equivalent of the inputed url
    +:rtype: string
    +
    +@author: Jonathan Benn
    +
    +
    +
    +
    +
    +
    + +
    + +
    + + +
    +

    urlify(value, + maxlen=80, + keep_underscores=True) +

    +
    source code  +
    + + Convert incoming string to a simplified ASCII subset. if + (keep_underscores): underscores are retained in the string else: + underscores are translated to hyphens (default) +
    +
    +
    +
    +
    + + + + + + +
    + + + + + +
    Variables Details[hide private]
    +
    + +
    + +
    +

    official_url_schemes

    + +
    +
    +
    +
    Value:
    +
    +['aaa',
    + 'aaas',
    + 'acap',
    + 'cap',
    + 'cid',
    + 'crid',
    + 'data',
    + 'dav',
    +...
    +
    +
    +
    +
    +
    + +
    + +
    +

    unofficial_url_schemes

    + +
    +
    +
    +
    Value:
    +
    +['about',
    + 'adiumxtra',
    + 'aim',
    + 'afp',
    + 'aw',
    + 'callto',
    + 'chrome',
    + 'cvs',
    +...
    +
    +
    +
    +
    +
    + +
    + +
    +

    all_url_schemes

    + +
    +
    +
    +
    Value:
    +
    +[None,
    + 'aaa',
    + 'aaas',
    + 'acap',
    + 'cap',
    + 'cid',
    + 'crid',
    + 'data',
    +...
    +
    +
    +
    +
    +
    + +
    + +
    +

    url_split_regex

    + +
    +
    +
    +
    Value:
    +
    +re.compile(r'^(([^:/\?#]+):)?(//([^/\?#]*))?([^\?#]*)(\?([^#]*))?(#(.*\
    +))?')
    +
    +
    +
    +
    +
    + +
    + +
    +

    official_top_level_domains

    + +
    +
    +
    +
    Value:
    +
    +['ac',
    + 'ad',
    + 'ae',
    + 'aero',
    + 'af',
    + 'ag',
    + 'ai',
    + 'al',
    +...
    +
    +
    +
    +
    +
    + +
    + +
    +

    regex_time

    + +
    +
    +
    +
    Value:
    +
    +re.compile(r'((?P<h>[0-9]+))([^0-9 ]+(?P<m>[0-9 ]+))?([^0-9ap ]+(?P<s>\
    +[0-9]*))?((?P<d>[ap]m))?')
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.validators-pysrc.html Index: applications/examples/static/epydoc/web2py.gluon.validators-pysrc.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.validators-pysrc.html +++ applications/examples/static/epydoc/web2py.gluon.validators-pysrc.html @@ -0,0 +1,4108 @@ + + + + + web2py.gluon.validators + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module validators + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Module web2py.gluon.validators

    +
    +   1  #!/bin/env python 
    +   2  # -*- coding: utf-8 -*- 
    +   3   
    +   4  """ 
    +   5  This file is part of the web2py Web Framework 
    +   6  Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> 
    +   7  License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) 
    +   8   
    +   9  Thanks to ga2arch for help with IS_IN_DB and IS_NOT_IN_DB on GAE 
    +  10  """ 
    +  11   
    +  12  import os 
    +  13  import re 
    +  14  import datetime 
    +  15  import time 
    +  16  import cgi 
    +  17  import urllib 
    +  18  import struct 
    +  19  import decimal 
    +  20  import unicodedata 
    +  21  from cStringIO import StringIO 
    +  22  from utils import simple_hash, hmac_hash 
    +  23   
    +  24  __all__ = [ 
    +  25      'CLEANUP', 
    +  26      'CRYPT', 
    +  27      'IS_ALPHANUMERIC', 
    +  28      'IS_DATE_IN_RANGE', 
    +  29      'IS_DATE', 
    +  30      'IS_DATETIME_IN_RANGE', 
    +  31      'IS_DATETIME', 
    +  32      'IS_DECIMAL_IN_RANGE', 
    +  33      'IS_EMAIL', 
    +  34      'IS_EMPTY_OR', 
    +  35      'IS_EXPR', 
    +  36      'IS_FLOAT_IN_RANGE', 
    +  37      'IS_IMAGE', 
    +  38      'IS_IN_DB', 
    +  39      'IS_IN_SET', 
    +  40      'IS_INT_IN_RANGE', 
    +  41      'IS_IPV4', 
    +  42      'IS_LENGTH', 
    +  43      'IS_LIST_OF', 
    +  44      'IS_LOWER', 
    +  45      'IS_MATCH', 
    +  46      'IS_EQUAL_TO', 
    +  47      'IS_NOT_EMPTY', 
    +  48      'IS_NOT_IN_DB', 
    +  49      'IS_NULL_OR', 
    +  50      'IS_SLUG', 
    +  51      'IS_STRONG', 
    +  52      'IS_TIME', 
    +  53      'IS_UPLOAD_FILENAME', 
    +  54      'IS_UPPER', 
    +  55      'IS_URL', 
    +  56      ] 
    +  57   
    +
    58 -def translate(text): +
    59 if isinstance(text,(str,unicode)): + 60 from globals import current + 61 if hasattr(current,'T'): + 62 return current.T(text) + 63 return text +
    64 +
    65 -def options_sorter(x,y): +
    66 return (str(x[1]).upper()>str(y[1]).upper() and 1) or -1 +
    67 +
    68 -class Validator(object): +
    69 """ + 70 Root for all validators, mainly for documentation purposes. + 71 + 72 Validators are classes used to validate input fields (including forms + 73 generated from database tables). + 74 + 75 Here is an example of using a validator with a FORM:: + 76 + 77 INPUT(_name='a', requires=IS_INT_IN_RANGE(0, 10)) + 78 + 79 Here is an example of how to require a validator for a table field:: + 80 + 81 db.define_table('person', SQLField('name')) + 82 db.person.name.requires=IS_NOT_EMPTY() + 83 + 84 Validators are always assigned using the requires attribute of a field. A + 85 field can have a single validator or multiple validators. Multiple + 86 validators are made part of a list:: + 87 + 88 db.person.name.requires=[IS_NOT_EMPTY(), IS_NOT_IN_DB(db, 'person.id')] + 89 + 90 Validators are called by the function accepts on a FORM or other HTML + 91 helper object that contains a form. They are always called in the order in + 92 which they are listed. + 93 + 94 Built-in validators have constructors that take the optional argument error + 95 message which allows you to change the default error message. + 96 Here is an example of a validator on a database table:: + 97 + 98 db.person.name.requires=IS_NOT_EMPTY(error_message=T('fill this')) + 99 + 100 where we have used the translation operator T to allow for + 101 internationalization. + 102 + 103 Notice that default error messages are not translated. + 104 """ + 105 +
    106 - def formatter(self, value): +
    107 """ + 108 For some validators returns a formatted version (matching the validator) + 109 of value. Otherwise just returns the value. + 110 """ + 111 return value +
    112 + 113 +
    114 -class IS_MATCH(Validator): +
    115 """ + 116 example:: + 117 + 118 INPUT(_type='text', _name='name', requires=IS_MATCH('.+')) + 119 + 120 the argument of IS_MATCH is a regular expression:: + 121 + 122 >>> IS_MATCH('.+')('hello') + 123 ('hello', None) + 124 + 125 >>> IS_MATCH('.+')('') + 126 ('', 'invalid expression') + 127 """ + 128 +
    129 - def __init__(self, expression, error_message='invalid expression', strict=True): +
    130 if strict: + 131 if not expression.endswith('$'): + 132 expression = '(%s)$' % expression + 133 self.regex = re.compile(expression) + 134 self.error_message = error_message +
    135 +
    136 - def __call__(self, value): +
    137 match = self.regex.match(value) + 138 if match: + 139 return (match.group(), None) + 140 return (value, translate(self.error_message)) +
    141 + 142 +
    143 -class IS_EQUAL_TO(Validator): +
    144 """ + 145 example:: + 146 + 147 INPUT(_type='text', _name='password') + 148 INPUT(_type='text', _name='password2', + 149 requires=IS_EQUAL_TO(request.vars.password)) + 150 + 151 the argument of IS_EQUAL_TO is a string + 152 + 153 >>> IS_EQUAL_TO('aaa')('aaa') + 154 ('aaa', None) + 155 + 156 >>> IS_EQUAL_TO('aaa')('aab') + 157 ('aab', 'no match') + 158 """ + 159 +
    160 - def __init__(self, expression, error_message='no match'): +
    161 self.expression = expression + 162 self.error_message = error_message +
    163 +
    164 - def __call__(self, value): +
    165 if value == self.expression: + 166 return (value, None) + 167 return (value, translate(self.error_message)) +
    168 + 169 +
    170 -class IS_EXPR(Validator): +
    171 """ + 172 example:: + 173 + 174 INPUT(_type='text', _name='name', + 175 requires=IS_EXPR('5 < int(value) < 10')) + 176 + 177 the argument of IS_EXPR must be python condition:: + 178 + 179 >>> IS_EXPR('int(value) < 2')('1') + 180 ('1', None) + 181 + 182 >>> IS_EXPR('int(value) < 2')('2') + 183 ('2', 'invalid expression') + 184 """ + 185 +
    186 - def __init__(self, expression, error_message='invalid expression'): +
    187 self.expression = expression + 188 self.error_message = error_message +
    189 +
    190 - def __call__(self, value): +
    191 environment = {'value': value} + 192 exec '__ret__=' + self.expression in environment + 193 if environment['__ret__']: + 194 return (value, None) + 195 return (value, translate(self.error_message)) +
    196 + 197 +
    198 -class IS_LENGTH(Validator): +
    199 """ + 200 Checks if length of field's value fits between given boundaries. Works + 201 for both text and file inputs. + 202 + 203 Arguments: + 204 + 205 maxsize: maximum allowed length / size + 206 minsize: minimum allowed length / size + 207 + 208 Examples:: + 209 + 210 #Check if text string is shorter than 33 characters: + 211 INPUT(_type='text', _name='name', requires=IS_LENGTH(32)) + 212 + 213 #Check if password string is longer than 5 characters: + 214 INPUT(_type='password', _name='name', requires=IS_LENGTH(minsize=6)) + 215 + 216 #Check if uploaded file has size between 1KB and 1MB: + 217 INPUT(_type='file', _name='name', requires=IS_LENGTH(1048576, 1024)) + 218 + 219 >>> IS_LENGTH()('') + 220 ('', None) + 221 >>> IS_LENGTH()('1234567890') + 222 ('1234567890', None) + 223 >>> IS_LENGTH(maxsize=5, minsize=0)('1234567890') # too long + 224 ('1234567890', 'enter from 0 to 5 characters') + 225 >>> IS_LENGTH(maxsize=50, minsize=20)('1234567890') # too short + 226 ('1234567890', 'enter from 20 to 50 characters') + 227 """ + 228 +
    229 - def __init__(self, maxsize=255, minsize=0, + 230 error_message='enter from %(min)g to %(max)g characters'): +
    231 self.maxsize = maxsize + 232 self.minsize = minsize + 233 self.error_message = error_message +
    234 +
    235 - def __call__(self, value): +
    236 if isinstance(value, cgi.FieldStorage): + 237 if value.file: + 238 value.file.seek(0, os.SEEK_END) + 239 length = value.file.tell() + 240 value.file.seek(0, os.SEEK_SET) + 241 else: + 242 val = value.value + 243 if val: + 244 length = len(val) + 245 else: + 246 length = 0 + 247 if self.minsize <= length <= self.maxsize: + 248 return (value, None) + 249 elif isinstance(value, (str, unicode, list)): + 250 if self.minsize <= len(value) <= self.maxsize: + 251 return (value, None) + 252 elif self.minsize <= len(str(value)) <= self.maxsize: + 253 try: + 254 value.decode('utf8') + 255 return (value, None) + 256 except: + 257 pass + 258 return (value, translate(self.error_message) \ + 259 % dict(min=self.minsize, max=self.maxsize)) +
    260 + 261 +
    262 -class IS_IN_SET(Validator): +
    263 """ + 264 example:: + 265 + 266 INPUT(_type='text', _name='name', + 267 requires=IS_IN_SET(['max', 'john'],zero='')) + 268 + 269 the argument of IS_IN_SET must be a list or set + 270 + 271 >>> IS_IN_SET(['max', 'john'])('max') + 272 ('max', None) + 273 >>> IS_IN_SET(['max', 'john'])('massimo') + 274 ('massimo', 'value not allowed') + 275 >>> IS_IN_SET(['max', 'john'], multiple=True)(('max', 'john')) + 276 (('max', 'john'), None) + 277 >>> IS_IN_SET(['max', 'john'], multiple=True)(('bill', 'john')) + 278 (('bill', 'john'), 'value not allowed') + 279 >>> IS_IN_SET(('id1','id2'), ['first label','second label'])('id1') # Traditional way + 280 ('id1', None) + 281 >>> IS_IN_SET({'id1':'first label', 'id2':'second label'})('id1') + 282 ('id1', None) + 283 >>> import itertools + 284 >>> IS_IN_SET(itertools.chain(['1','3','5'],['2','4','6']))('1') + 285 ('1', None) + 286 >>> IS_IN_SET([('id1','first label'), ('id2','second label')])('id1') # Redundant way + 287 ('id1', None) + 288 """ + 289 +
    290 - def __init__( + 291 self, + 292 theset, + 293 labels=None, + 294 error_message='value not allowed', + 295 multiple=False, + 296 zero='', + 297 sort=False, + 298 ): +
    299 self.multiple = multiple + 300 if isinstance(theset, dict): + 301 self.theset = [str(item) for item in theset] + 302 self.labels = theset.values() + 303 elif theset and isinstance(theset, (tuple,list)) \ + 304 and isinstance(theset[0], (tuple,list)) and len(theset[0])==2: + 305 self.theset = [str(item) for item,label in theset] + 306 self.labels = [str(label) for item,label in theset] + 307 else: + 308 self.theset = [str(item) for item in theset] + 309 self.labels = labels + 310 self.error_message = error_message + 311 self.zero = zero + 312 self.sort = sort +
    313 +
    314 - def options(self,zero=True): +
    315 if not self.labels: + 316 items = [(k, k) for (i, k) in enumerate(self.theset)] + 317 else: + 318 items = [(k, self.labels[i]) for (i, k) in enumerate(self.theset)] + 319 if self.sort: + 320 items.sort(options_sorter) + 321 if zero and self.zero != None and not self.multiple: + 322 items.insert(0,('',self.zero)) + 323 return items +
    324 +
    325 - def __call__(self, value): +
    326 if self.multiple: + 327 ### if below was values = re.compile("[\w\-:]+").findall(str(value)) + 328 if isinstance(value, (str,unicode)): + 329 values = [value] + 330 elif isinstance(value, (tuple, list)): + 331 values = value + 332 elif not value: + 333 values = [] + 334 else: + 335 values = [value] + 336 failures = [x for x in values if not x in self.theset] + 337 if failures and self.theset: + 338 if self.multiple and (value == None or value == ''): + 339 return ([], None) + 340 return (value, translate(self.error_message)) + 341 if self.multiple: + 342 if isinstance(self.multiple,(tuple,list)) and \ + 343 not self.multiple[0]<=len(values)<self.multiple[1]: + 344 return (values, translate(self.error_message)) + 345 return (values, None) + 346 return (value, None) +
    347 + 348 + 349 regex1 = re.compile('[\w_]+\.[\w_]+') + 350 regex2 = re.compile('%\((?P<name>[^\)]+)\)s') + 351 + 352 +
    353 -class IS_IN_DB(Validator): +
    354 """ + 355 example:: + 356 + 357 INPUT(_type='text', _name='name', + 358 requires=IS_IN_DB(db, db.mytable.myfield, zero='')) + 359 + 360 used for reference fields, rendered as a dropbox + 361 """ + 362 +
    363 - def __init__( + 364 self, + 365 dbset, + 366 field, + 367 label=None, + 368 error_message='value not in database', + 369 orderby=None, + 370 groupby=None, + 371 cache=None, + 372 multiple=False, + 373 zero='', + 374 sort=False, + 375 _and=None, + 376 ): +
    377 from dal import Table + 378 if isinstance(field,Table): field = field._id + 379 + 380 if hasattr(dbset, 'define_table'): + 381 self.dbset = dbset() + 382 else: + 383 self.dbset = dbset + 384 self.field = field + 385 (ktable, kfield) = str(self.field).split('.') + 386 if not label: + 387 label = '%%(%s)s' % kfield + 388 if isinstance(label,str): + 389 if regex1.match(str(label)): + 390 label = '%%(%s)s' % str(label).split('.')[-1] + 391 ks = regex2.findall(label) + 392 if not kfield in ks: + 393 ks += [kfield] + 394 fields = ks + 395 else: + 396 ks = [kfield] + 397 fields = 'all' + 398 self.fields = fields + 399 self.label = label + 400 self.ktable = ktable + 401 self.kfield = kfield + 402 self.ks = ks + 403 self.error_message = error_message + 404 self.theset = None + 405 self.orderby = orderby + 406 self.groupby = groupby + 407 self.cache = cache + 408 self.multiple = multiple + 409 self.zero = zero + 410 self.sort = sort + 411 self._and = _and +
    412 +
    413 - def set_self_id(self, id): +
    414 if self._and: + 415 self._and.record_id = id +
    416 +
    417 - def build_set(self): +
    418 if self.fields == 'all': + 419 fields = [f for f in self.dbset.db[self.ktable]] + 420 else: + 421 fields = [self.dbset.db[self.ktable][k] for k in self.fields] + 422 if self.dbset.db._dbname != 'gae': + 423 orderby = self.orderby or reduce(lambda a,b:a|b,fields) + 424 groupby = self.groupby + 425 dd = dict(orderby=orderby, groupby=groupby, cache=self.cache) + 426 records = self.dbset.select(*fields, **dd) + 427 else: + 428 orderby = self.orderby or reduce(lambda a,b:a|b,(f for f in fields if not f.name=='id')) + 429 dd = dict(orderby=orderby, cache=self.cache) + 430 records = self.dbset.select(self.dbset.db[self.ktable].ALL, **dd) + 431 self.theset = [str(r[self.kfield]) for r in records] + 432 if isinstance(self.label,str): + 433 self.labels = [self.label % dict(r) for r in records] + 434 else: + 435 self.labels = [self.label(r) for r in records] +
    436 +
    437 - def options(self, zero=True): +
    438 self.build_set() + 439 items = [(k, self.labels[i]) for (i, k) in enumerate(self.theset)] + 440 if self.sort: + 441 items.sort(options_sorter) + 442 if zero and self.zero != None and not self.multiple: + 443 items.insert(0,('',self.zero)) + 444 return items +
    445 +
    446 - def __call__(self, value): +
    447 if self.multiple: + 448 if isinstance(value,list): + 449 values=value + 450 elif value: + 451 values = [value] + 452 else: + 453 values = [] + 454 if isinstance(self.multiple,(tuple,list)) and \ + 455 not self.multiple[0]<=len(values)<self.multiple[1]: + 456 return (values, translate(self.error_message)) + 457 if not [x for x in values if not x in self.theset]: + 458 return (values, None) + 459 elif self.theset: + 460 if value in self.theset: + 461 if self._and: + 462 return self._and(value) + 463 else: + 464 return (value, None) + 465 else: + 466 (ktable, kfield) = str(self.field).split('.') + 467 field = self.dbset.db[ktable][kfield] + 468 if self.dbset(field == value).count(): + 469 if self._and: + 470 return self._and(value) + 471 else: + 472 return (value, None) + 473 return (value, translate(self.error_message)) +
    474 + 475 +
    476 -class IS_NOT_IN_DB(Validator): +
    477 """ + 478 example:: + 479 + 480 INPUT(_type='text', _name='name', requires=IS_NOT_IN_DB(db, db.table)) + 481 + 482 makes the field unique + 483 """ + 484 +
    485 - def __init__( + 486 self, + 487 dbset, + 488 field, + 489 error_message='value already in database or empty', + 490 allowed_override=[], + 491 ): +
    492 + 493 from dal import Table + 494 if isinstance(field,Table): field = field._id + 495 + 496 if hasattr(dbset, 'define_table'): + 497 self.dbset = dbset() + 498 else: + 499 self.dbset = dbset + 500 self.field = field + 501 self.error_message = error_message + 502 self.record_id = 0 + 503 self.allowed_override = allowed_override +
    504 +
    505 - def set_self_id(self, id): +
    506 self.record_id = id +
    507 +
    508 - def __call__(self, value): +
    509 value=str(value) + 510 if not value.strip(): + 511 return (value, translate(self.error_message)) + 512 if value in self.allowed_override: + 513 return (value, None) + 514 (tablename, fieldname) = str(self.field).split('.') + 515 field = self.dbset.db[tablename][fieldname] + 516 rows = self.dbset(field == value).select(limitby=(0, 1)) + 517 if len(rows) > 0: + 518 if isinstance(self.record_id, dict): + 519 for f in self.record_id: + 520 if str(getattr(rows[0], f)) != str(self.record_id[f]): + 521 return (value, translate(self.error_message)) + 522 elif str(rows[0].id) != str(self.record_id): + 523 return (value, translate(self.error_message)) + 524 return (value, None) +
    525 + 526 +
    527 -class IS_INT_IN_RANGE(Validator): +
    528 """ + 529 Determine that the argument is (or can be represented as) an int, + 530 and that it falls within the specified range. The range is interpreted + 531 in the Pythonic way, so the test is: min <= value < max. + 532 + 533 The minimum and maximum limits can be None, meaning no lower or upper limit, + 534 respectively. + 535 + 536 example:: + 537 + 538 INPUT(_type='text', _name='name', requires=IS_INT_IN_RANGE(0, 10)) + 539 + 540 >>> IS_INT_IN_RANGE(1,5)('4') + 541 (4, None) + 542 >>> IS_INT_IN_RANGE(1,5)(4) + 543 (4, None) + 544 >>> IS_INT_IN_RANGE(1,5)(1) + 545 (1, None) + 546 >>> IS_INT_IN_RANGE(1,5)(5) + 547 (5, 'enter an integer between 1 and 4') + 548 >>> IS_INT_IN_RANGE(1,5)(5) + 549 (5, 'enter an integer between 1 and 4') + 550 >>> IS_INT_IN_RANGE(1,5)(3.5) + 551 (3, 'enter an integer between 1 and 4') + 552 >>> IS_INT_IN_RANGE(None,5)('4') + 553 (4, None) + 554 >>> IS_INT_IN_RANGE(None,5)('6') + 555 (6, 'enter an integer less than or equal to 4') + 556 >>> IS_INT_IN_RANGE(1,None)('4') + 557 (4, None) + 558 >>> IS_INT_IN_RANGE(1,None)('0') + 559 (0, 'enter an integer greater than or equal to 1') + 560 >>> IS_INT_IN_RANGE()(6) + 561 (6, None) + 562 >>> IS_INT_IN_RANGE()('abc') + 563 ('abc', 'enter an integer') + 564 """ + 565 +
    566 - def __init__( + 567 self, + 568 minimum=None, + 569 maximum=None, + 570 error_message=None, + 571 ): +
    572 self.minimum = self.maximum = None + 573 if minimum is None: + 574 if maximum is None: + 575 self.error_message = error_message or 'enter an integer' + 576 else: + 577 self.maximum = int(maximum) + 578 if error_message is None: + 579 error_message = 'enter an integer less than or equal to %(max)g' + 580 self.error_message = translate(error_message) % dict(max=self.maximum-1) + 581 elif maximum is None: + 582 self.minimum = int(minimum) + 583 if error_message is None: + 584 error_message = 'enter an integer greater than or equal to %(min)g' + 585 self.error_message = translate(error_message) % dict(min=self.minimum) + 586 else: + 587 self.minimum = int(minimum) + 588 self.maximum = int(maximum) + 589 if error_message is None: + 590 error_message = 'enter an integer between %(min)g and %(max)g' + 591 self.error_message = translate(error_message) \ + 592 % dict(min=self.minimum, max=self.maximum-1) +
    593 +
    594 - def __call__(self, value): +
    595 try: + 596 fvalue = float(value) + 597 value = int(value) + 598 if value != fvalue: + 599 return (value, self.error_message) + 600 if self.minimum is None: + 601 if self.maximum is None or value < self.maximum: + 602 return (value, None) + 603 elif self.maximum is None: + 604 if value >= self.minimum: + 605 return (value, None) + 606 elif self.minimum <= value < self.maximum: + 607 return (value, None) + 608 except ValueError: + 609 pass + 610 return (value, self.error_message) +
    611 + 612 +
    613 -class IS_FLOAT_IN_RANGE(Validator): +
    614 """ + 615 Determine that the argument is (or can be represented as) a float, + 616 and that it falls within the specified inclusive range. + 617 The comparison is made with native arithmetic. + 618 + 619 The minimum and maximum limits can be None, meaning no lower or upper limit, + 620 respectively. + 621 + 622 example:: + 623 + 624 INPUT(_type='text', _name='name', requires=IS_FLOAT_IN_RANGE(0, 10)) + 625 + 626 >>> IS_FLOAT_IN_RANGE(1,5)('4') + 627 (4.0, None) + 628 >>> IS_FLOAT_IN_RANGE(1,5)(4) + 629 (4.0, None) + 630 >>> IS_FLOAT_IN_RANGE(1,5)(1) + 631 (1.0, None) + 632 >>> IS_FLOAT_IN_RANGE(1,5)(5.25) + 633 (5.25, 'enter a number between 1 and 5') + 634 >>> IS_FLOAT_IN_RANGE(1,5)(6.0) + 635 (6.0, 'enter a number between 1 and 5') + 636 >>> IS_FLOAT_IN_RANGE(1,5)(3.5) + 637 (3.5, None) + 638 >>> IS_FLOAT_IN_RANGE(1,None)(3.5) + 639 (3.5, None) + 640 >>> IS_FLOAT_IN_RANGE(None,5)(3.5) + 641 (3.5, None) + 642 >>> IS_FLOAT_IN_RANGE(1,None)(0.5) + 643 (0.5, 'enter a number greater than or equal to 1') + 644 >>> IS_FLOAT_IN_RANGE(None,5)(6.5) + 645 (6.5, 'enter a number less than or equal to 5') + 646 >>> IS_FLOAT_IN_RANGE()(6.5) + 647 (6.5, None) + 648 >>> IS_FLOAT_IN_RANGE()('abc') + 649 ('abc', 'enter a number') + 650 """ + 651 +
    652 - def __init__( + 653 self, + 654 minimum=None, + 655 maximum=None, + 656 error_message=None, + 657 dot='.' + 658 ): +
    659 self.minimum = self.maximum = None + 660 self.dot = dot + 661 if minimum is None: + 662 if maximum is None: + 663 if error_message is None: + 664 error_message = 'enter a number' + 665 else: + 666 self.maximum = float(maximum) + 667 if error_message is None: + 668 error_message = 'enter a number less than or equal to %(max)g' + 669 elif maximum is None: + 670 self.minimum = float(minimum) + 671 if error_message is None: + 672 error_message = 'enter a number greater than or equal to %(min)g' + 673 else: + 674 self.minimum = float(minimum) + 675 self.maximum = float(maximum) + 676 if error_message is None: + 677 error_message = 'enter a number between %(min)g and %(max)g' + 678 self.error_message = translate(error_message) \ + 679 % dict(min=self.minimum, max=self.maximum) +
    680 +
    681 - def __call__(self, value): +
    682 try: + 683 if self.dot=='.': + 684 fvalue = float(value) + 685 else: + 686 fvalue = float(str(value).replace(self.dot,'.')) + 687 if self.minimum is None: + 688 if self.maximum is None or fvalue <= self.maximum: + 689 return (fvalue, None) + 690 elif self.maximum is None: + 691 if fvalue >= self.minimum: + 692 return (fvalue, None) + 693 elif self.minimum <= fvalue <= self.maximum: + 694 return (fvalue, None) + 695 except (ValueError, TypeError): + 696 pass + 697 return (value, self.error_message) +
    698 +
    699 - def formatter(self,value): +
    700 if self.dot=='.': + 701 return str(value) + 702 else: + 703 return str(value).replace('.',self.dot) +
    704 + 705 +
    706 -class IS_DECIMAL_IN_RANGE(Validator): +
    707 """ + 708 Determine that the argument is (or can be represented as) a Python Decimal, + 709 and that it falls within the specified inclusive range. + 710 The comparison is made with Python Decimal arithmetic. + 711 + 712 The minimum and maximum limits can be None, meaning no lower or upper limit, + 713 respectively. + 714 + 715 example:: + 716 + 717 INPUT(_type='text', _name='name', requires=IS_DECIMAL_IN_RANGE(0, 10)) + 718 + 719 >>> IS_DECIMAL_IN_RANGE(1,5)('4') + 720 (Decimal('4'), None) + 721 >>> IS_DECIMAL_IN_RANGE(1,5)(4) + 722 (Decimal('4'), None) + 723 >>> IS_DECIMAL_IN_RANGE(1,5)(1) + 724 (Decimal('1'), None) + 725 >>> IS_DECIMAL_IN_RANGE(1,5)(5.25) + 726 (5.25, 'enter a number between 1 and 5') + 727 >>> IS_DECIMAL_IN_RANGE(5.25,6)(5.25) + 728 (Decimal('5.25'), None) + 729 >>> IS_DECIMAL_IN_RANGE(5.25,6)('5.25') + 730 (Decimal('5.25'), None) + 731 >>> IS_DECIMAL_IN_RANGE(1,5)(6.0) + 732 (6.0, 'enter a number between 1 and 5') + 733 >>> IS_DECIMAL_IN_RANGE(1,5)(3.5) + 734 (Decimal('3.5'), None) + 735 >>> IS_DECIMAL_IN_RANGE(1.5,5.5)(3.5) + 736 (Decimal('3.5'), None) + 737 >>> IS_DECIMAL_IN_RANGE(1.5,5.5)(6.5) + 738 (6.5, 'enter a number between 1.5 and 5.5') + 739 >>> IS_DECIMAL_IN_RANGE(1.5,None)(6.5) + 740 (Decimal('6.5'), None) + 741 >>> IS_DECIMAL_IN_RANGE(1.5,None)(0.5) + 742 (0.5, 'enter a number greater than or equal to 1.5') + 743 >>> IS_DECIMAL_IN_RANGE(None,5.5)(4.5) + 744 (Decimal('4.5'), None) + 745 >>> IS_DECIMAL_IN_RANGE(None,5.5)(6.5) + 746 (6.5, 'enter a number less than or equal to 5.5') + 747 >>> IS_DECIMAL_IN_RANGE()(6.5) + 748 (Decimal('6.5'), None) + 749 >>> IS_DECIMAL_IN_RANGE(0,99)(123.123) + 750 (123.123, 'enter a number between 0 and 99') + 751 >>> IS_DECIMAL_IN_RANGE(0,99)('123.123') + 752 ('123.123', 'enter a number between 0 and 99') + 753 >>> IS_DECIMAL_IN_RANGE(0,99)('12.34') + 754 (Decimal('12.34'), None) + 755 >>> IS_DECIMAL_IN_RANGE()('abc') + 756 ('abc', 'enter a decimal number') + 757 """ + 758 +
    759 - def __init__( + 760 self, + 761 minimum=None, + 762 maximum=None, + 763 error_message=None, + 764 dot='.' + 765 ): +
    766 self.minimum = self.maximum = None + 767 self.dot = dot + 768 if minimum is None: + 769 if maximum is None: + 770 if error_message is None: + 771 error_message = 'enter a decimal number' + 772 else: + 773 self.maximum = decimal.Decimal(str(maximum)) + 774 if error_message is None: + 775 error_message = 'enter a number less than or equal to %(max)g' + 776 elif maximum is None: + 777 self.minimum = decimal.Decimal(str(minimum)) + 778 if error_message is None: + 779 error_message = 'enter a number greater than or equal to %(min)g' + 780 else: + 781 self.minimum = decimal.Decimal(str(minimum)) + 782 self.maximum = decimal.Decimal(str(maximum)) + 783 if error_message is None: + 784 error_message = 'enter a number between %(min)g and %(max)g' + 785 self.error_message = translate(error_message) \ + 786 % dict(min=self.minimum, max=self.maximum) +
    787 +
    788 - def __call__(self, value): +
    789 try: + 790 if isinstance(value,decimal.Decimal): + 791 v = value + 792 else: + 793 v = decimal.Decimal(str(value).replace(self.dot,'.')) + 794 if self.minimum is None: + 795 if self.maximum is None or v <= self.maximum: + 796 return (v, None) + 797 elif self.maximum is None: + 798 if v >= self.minimum: + 799 return (v, None) + 800 elif self.minimum <= v <= self.maximum: + 801 return (v, None) + 802 except (ValueError, TypeError, decimal.InvalidOperation): + 803 pass + 804 return (value, self.error_message) +
    805 +
    806 - def formatter(self, value): +
    807 return str(value).replace('.',self.dot) +
    808 +
    809 -def is_empty(value, empty_regex=None): +
    810 "test empty field" + 811 if isinstance(value, (str, unicode)): + 812 value = value.strip() + 813 if empty_regex is not None and empty_regex.match(value): + 814 value = '' + 815 if value == None or value == '' or value == []: + 816 return (value, True) + 817 return (value, False) +
    818 +
    819 -class IS_NOT_EMPTY(Validator): +
    820 """ + 821 example:: + 822 + 823 INPUT(_type='text', _name='name', requires=IS_NOT_EMPTY()) + 824 + 825 >>> IS_NOT_EMPTY()(1) + 826 (1, None) + 827 >>> IS_NOT_EMPTY()(0) + 828 (0, None) + 829 >>> IS_NOT_EMPTY()('x') + 830 ('x', None) + 831 >>> IS_NOT_EMPTY()(' x ') + 832 ('x', None) + 833 >>> IS_NOT_EMPTY()(None) + 834 (None, 'enter a value') + 835 >>> IS_NOT_EMPTY()('') + 836 ('', 'enter a value') + 837 >>> IS_NOT_EMPTY()(' ') + 838 ('', 'enter a value') + 839 >>> IS_NOT_EMPTY()(' \\n\\t') + 840 ('', 'enter a value') + 841 >>> IS_NOT_EMPTY()([]) + 842 ([], 'enter a value') + 843 >>> IS_NOT_EMPTY(empty_regex='def')('def') + 844 ('', 'enter a value') + 845 >>> IS_NOT_EMPTY(empty_regex='de[fg]')('deg') + 846 ('', 'enter a value') + 847 >>> IS_NOT_EMPTY(empty_regex='def')('abc') + 848 ('abc', None) + 849 """ + 850 +
    851 - def __init__(self, error_message='enter a value', empty_regex=None): +
    852 self.error_message = error_message + 853 if empty_regex is not None: + 854 self.empty_regex = re.compile(empty_regex) + 855 else: + 856 self.empty_regex = None +
    857 +
    858 - def __call__(self, value): +
    859 value, empty = is_empty(value, empty_regex=self.empty_regex) + 860 if empty: + 861 return (value, translate(self.error_message)) + 862 return (value, None) +
    863 + 864 +
    865 -class IS_ALPHANUMERIC(IS_MATCH): +
    866 """ + 867 example:: + 868 + 869 INPUT(_type='text', _name='name', requires=IS_ALPHANUMERIC()) + 870 + 871 >>> IS_ALPHANUMERIC()('1') + 872 ('1', None) + 873 >>> IS_ALPHANUMERIC()('') + 874 ('', None) + 875 >>> IS_ALPHANUMERIC()('A_a') + 876 ('A_a', None) + 877 >>> IS_ALPHANUMERIC()('!') + 878 ('!', 'enter only letters, numbers, and underscore') + 879 """ + 880 +
    881 - def __init__(self, error_message='enter only letters, numbers, and underscore'): +
    882 IS_MATCH.__init__(self, '^[\w]*$', error_message) +
    883 + 884 +
    885 -class IS_EMAIL(Validator): +
    886 """ + 887 Checks if field's value is a valid email address. Can be set to disallow + 888 or force addresses from certain domain(s). + 889 + 890 Email regex adapted from + 891 http://haacked.com/archive/2007/08/21/i-knew-how-to-validate-an-email-address-until-i.aspx, + 892 generally following the RFCs, except that we disallow quoted strings + 893 and permit underscores and leading numerics in subdomain labels + 894 + 895 Arguments: + 896 + 897 - banned: regex text for disallowed address domains + 898 - forced: regex text for required address domains + 899 + 900 Both arguments can also be custom objects with a match(value) method. + 901 + 902 Examples:: + 903 + 904 #Check for valid email address: + 905 INPUT(_type='text', _name='name', + 906 requires=IS_EMAIL()) + 907 + 908 #Check for valid email address that can't be from a .com domain: + 909 INPUT(_type='text', _name='name', + 910 requires=IS_EMAIL(banned='^.*\.com(|\..*)$')) + 911 + 912 #Check for valid email address that must be from a .edu domain: + 913 INPUT(_type='text', _name='name', + 914 requires=IS_EMAIL(forced='^.*\.edu(|\..*)$')) + 915 + 916 >>> IS_EMAIL()('a@b.com') + 917 ('a@b.com', None) + 918 >>> IS_EMAIL()('abc@def.com') + 919 ('abc@def.com', None) + 920 >>> IS_EMAIL()('abc@3def.com') + 921 ('abc@3def.com', None) + 922 >>> IS_EMAIL()('abc@def.us') + 923 ('abc@def.us', None) + 924 >>> IS_EMAIL()('abc@d_-f.us') + 925 ('abc@d_-f.us', None) + 926 >>> IS_EMAIL()('@def.com') # missing name + 927 ('@def.com', 'enter a valid email address') + 928 >>> IS_EMAIL()('"abc@def".com') # quoted name + 929 ('"abc@def".com', 'enter a valid email address') + 930 >>> IS_EMAIL()('abc+def.com') # no @ + 931 ('abc+def.com', 'enter a valid email address') + 932 >>> IS_EMAIL()('abc@def.x') # one-char TLD + 933 ('abc@def.x', 'enter a valid email address') + 934 >>> IS_EMAIL()('abc@def.12') # numeric TLD + 935 ('abc@def.12', 'enter a valid email address') + 936 >>> IS_EMAIL()('abc@def..com') # double-dot in domain + 937 ('abc@def..com', 'enter a valid email address') + 938 >>> IS_EMAIL()('abc@.def.com') # dot starts domain + 939 ('abc@.def.com', 'enter a valid email address') + 940 >>> IS_EMAIL()('abc@def.c_m') # underscore in TLD + 941 ('abc@def.c_m', 'enter a valid email address') + 942 >>> IS_EMAIL()('NotAnEmail') # missing @ + 943 ('NotAnEmail', 'enter a valid email address') + 944 >>> IS_EMAIL()('abc@NotAnEmail') # missing TLD + 945 ('abc@NotAnEmail', 'enter a valid email address') + 946 >>> IS_EMAIL()('customer/department@example.com') + 947 ('customer/department@example.com', None) + 948 >>> IS_EMAIL()('$A12345@example.com') + 949 ('$A12345@example.com', None) + 950 >>> IS_EMAIL()('!def!xyz%abc@example.com') + 951 ('!def!xyz%abc@example.com', None) + 952 >>> IS_EMAIL()('_Yosemite.Sam@example.com') + 953 ('_Yosemite.Sam@example.com', None) + 954 >>> IS_EMAIL()('~@example.com') + 955 ('~@example.com', None) + 956 >>> IS_EMAIL()('.wooly@example.com') # dot starts name + 957 ('.wooly@example.com', 'enter a valid email address') + 958 >>> IS_EMAIL()('wo..oly@example.com') # adjacent dots in name + 959 ('wo..oly@example.com', 'enter a valid email address') + 960 >>> IS_EMAIL()('pootietang.@example.com') # dot ends name + 961 ('pootietang.@example.com', 'enter a valid email address') + 962 >>> IS_EMAIL()('.@example.com') # name is bare dot + 963 ('.@example.com', 'enter a valid email address') + 964 >>> IS_EMAIL()('Ima.Fool@example.com') + 965 ('Ima.Fool@example.com', None) + 966 >>> IS_EMAIL()('Ima Fool@example.com') # space in name + 967 ('Ima Fool@example.com', 'enter a valid email address') + 968 >>> IS_EMAIL()('localguy@localhost') # localhost as domain + 969 ('localguy@localhost', None) + 970 + 971 """ + 972 + 973 regex = re.compile(''' + 974 ^(?!\.) # name may not begin with a dot + 975 ( + 976 [-a-z0-9!\#$%&'*+/=?^_`{|}~] # all legal characters except dot + 977 | + 978 (?<!\.)\. # single dots only + 979 )+ + 980 (?<!\.) # name may not end with a dot + 981 @ + 982 ( + 983 localhost + 984 | + 985 ( + 986 [a-z0-9] # [sub]domain begins with alphanumeric + 987 ( + 988 [-\w]* # alphanumeric, underscore, dot, hyphen + 989 [a-z0-9] # ending alphanumeric + 990 )? + 991 \. # ending dot + 992 )+ + 993 [a-z]{2,} # TLD alpha-only + 994 )$ + 995 ''', re.VERBOSE|re.IGNORECASE) + 996 + 997 regex_proposed_but_failed = re.compile('^([\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+\.)*[\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+@((((([a-z0-9]{1}[a-z0-9\-]{0,62}[a-z0-9]{1})|[a-z])\.)+[a-z]{2,6})|(\d{1,3}\.){3}\d{1,3}(\:\d{1,5})?)$',re.VERBOSE|re.IGNORECASE) + 998 +
    999 - def __init__(self, +1000 banned=None, +1001 forced=None, +1002 error_message='enter a valid email address'): +
    1003 if isinstance(banned, str): +1004 banned = re.compile(banned) +1005 if isinstance(forced, str): +1006 forced = re.compile(forced) +1007 self.banned = banned +1008 self.forced = forced +1009 self.error_message = error_message +
    1010 +
    1011 - def __call__(self, value): +
    1012 match = self.regex.match(value) +1013 if match: +1014 domain = value.split('@')[1] +1015 if (not self.banned or not self.banned.match(domain)) \ +1016 and (not self.forced or self.forced.match(domain)): +1017 return (value, None) +1018 return (value, translate(self.error_message)) +
    1019 +1020 +1021 # URL scheme source: +1022 # <http://en.wikipedia.org/wiki/URI_scheme> obtained on 2008-Nov-10 +1023 +1024 official_url_schemes = [ +1025 'aaa', +1026 'aaas', +1027 'acap', +1028 'cap', +1029 'cid', +1030 'crid', +1031 'data', +1032 'dav', +1033 'dict', +1034 'dns', +1035 'fax', +1036 'file', +1037 'ftp', +1038 'go', +1039 'gopher', +1040 'h323', +1041 'http', +1042 'https', +1043 'icap', +1044 'im', +1045 'imap', +1046 'info', +1047 'ipp', +1048 'iris', +1049 'iris.beep', +1050 'iris.xpc', +1051 'iris.xpcs', +1052 'iris.lws', +1053 'ldap', +1054 'mailto', +1055 'mid', +1056 'modem', +1057 'msrp', +1058 'msrps', +1059 'mtqp', +1060 'mupdate', +1061 'news', +1062 'nfs', +1063 'nntp', +1064 'opaquelocktoken', +1065 'pop', +1066 'pres', +1067 'prospero', +1068 'rtsp', +1069 'service', +1070 'shttp', +1071 'sip', +1072 'sips', +1073 'snmp', +1074 'soap.beep', +1075 'soap.beeps', +1076 'tag', +1077 'tel', +1078 'telnet', +1079 'tftp', +1080 'thismessage', +1081 'tip', +1082 'tv', +1083 'urn', +1084 'vemmi', +1085 'wais', +1086 'xmlrpc.beep', +1087 'xmlrpc.beep', +1088 'xmpp', +1089 'z39.50r', +1090 'z39.50s', +1091 ] +1092 unofficial_url_schemes = [ +1093 'about', +1094 'adiumxtra', +1095 'aim', +1096 'afp', +1097 'aw', +1098 'callto', +1099 'chrome', +1100 'cvs', +1101 'ed2k', +1102 'feed', +1103 'fish', +1104 'gg', +1105 'gizmoproject', +1106 'iax2', +1107 'irc', +1108 'ircs', +1109 'itms', +1110 'jar', +1111 'javascript', +1112 'keyparc', +1113 'lastfm', +1114 'ldaps', +1115 'magnet', +1116 'mms', +1117 'msnim', +1118 'mvn', +1119 'notes', +1120 'nsfw', +1121 'psyc', +1122 'paparazzi:http', +1123 'rmi', +1124 'rsync', +1125 'secondlife', +1126 'sgn', +1127 'skype', +1128 'ssh', +1129 'sftp', +1130 'smb', +1131 'sms', +1132 'soldat', +1133 'steam', +1134 'svn', +1135 'teamspeak', +1136 'unreal', +1137 'ut2004', +1138 'ventrilo', +1139 'view-source', +1140 'webcal', +1141 'wyciwyg', +1142 'xfire', +1143 'xri', +1144 'ymsgr', +1145 ] +1146 all_url_schemes = [None] + official_url_schemes + unofficial_url_schemes +1147 http_schemes = [None, 'http', 'https'] +1148 +1149 +1150 # This regex comes from RFC 2396, Appendix B. It's used to split a URL into +1151 # its component parts +1152 # Here are the regex groups that it extracts: +1153 # scheme = group(2) +1154 # authority = group(4) +1155 # path = group(5) +1156 # query = group(7) +1157 # fragment = group(9) +1158 +1159 url_split_regex = \ +1160 re.compile('^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?') +1161 +1162 # Defined in RFC 3490, Section 3.1, Requirement #1 +1163 # Use this regex to split the authority component of a unicode URL into +1164 # its component labels +1165 label_split_regex = re.compile(u'[\u002e\u3002\uff0e\uff61]') +1166 +1167 +
    1168 -def escape_unicode(string): +
    1169 ''' +1170 Converts a unicode string into US-ASCII, using a simple conversion scheme. +1171 Each unicode character that does not have a US-ASCII equivalent is +1172 converted into a URL escaped form based on its hexadecimal value. +1173 For example, the unicode character '\u4e86' will become the string '%4e%86' +1174 +1175 :param string: unicode string, the unicode string to convert into an +1176 escaped US-ASCII form +1177 :returns: the US-ASCII escaped form of the inputted string +1178 :rtype: string +1179 +1180 @author: Jonathan Benn +1181 ''' +1182 returnValue = StringIO() +1183 +1184 for character in string: +1185 code = ord(character) +1186 if code > 0x7F: +1187 hexCode = hex(code) +1188 returnValue.write('%' + hexCode[2:4] + '%' + hexCode[4:6]) +1189 else: +1190 returnValue.write(character) +1191 +1192 return returnValue.getvalue() +
    1193 +1194 +
    1195 -def unicode_to_ascii_authority(authority): +
    1196 ''' +1197 Follows the steps in RFC 3490, Section 4 to convert a unicode authority +1198 string into its ASCII equivalent. +1199 For example, u'www.Alliancefran\xe7aise.nu' will be converted into +1200 'www.xn--alliancefranaise-npb.nu' +1201 +1202 :param authority: unicode string, the URL authority component to convert, +1203 e.g. u'www.Alliancefran\xe7aise.nu' +1204 :returns: the US-ASCII character equivalent to the inputed authority, +1205 e.g. 'www.xn--alliancefranaise-npb.nu' +1206 :rtype: string +1207 :raises Exception: if the function is not able to convert the inputed +1208 authority +1209 +1210 @author: Jonathan Benn +1211 ''' +1212 #RFC 3490, Section 4, Step 1 +1213 #The encodings.idna Python module assumes that AllowUnassigned == True +1214 +1215 #RFC 3490, Section 4, Step 2 +1216 labels = label_split_regex.split(authority) +1217 +1218 #RFC 3490, Section 4, Step 3 +1219 #The encodings.idna Python module assumes that UseSTD3ASCIIRules == False +1220 +1221 #RFC 3490, Section 4, Step 4 +1222 #We use the ToASCII operation because we are about to put the authority +1223 #into an IDN-unaware slot +1224 asciiLabels = [] +1225 try: +1226 import encodings.idna +1227 for label in labels: +1228 if label: +1229 asciiLabels.append(encodings.idna.ToASCII(label)) +1230 else: +1231 #encodings.idna.ToASCII does not accept an empty string, but +1232 #it is necessary for us to allow for empty labels so that we +1233 #don't modify the URL +1234 asciiLabels.append('') +1235 except: +1236 asciiLabels=[str(label) for label in labels] +1237 #RFC 3490, Section 4, Step 5 +1238 return str(reduce(lambda x, y: x + unichr(0x002E) + y, asciiLabels)) +
    1239 +1240 +
    1241 -def unicode_to_ascii_url(url, prepend_scheme): +
    1242 ''' +1243 Converts the inputed unicode url into a US-ASCII equivalent. This function +1244 goes a little beyond RFC 3490, which is limited in scope to the domain name +1245 (authority) only. Here, the functionality is expanded to what was observed +1246 on Wikipedia on 2009-Jan-22: +1247 +1248 Component Can Use Unicode? +1249 --------- ---------------- +1250 scheme No +1251 authority Yes +1252 path Yes +1253 query Yes +1254 fragment No +1255 +1256 The authority component gets converted to punycode, but occurrences of +1257 unicode in other components get converted into a pair of URI escapes (we +1258 assume 4-byte unicode). E.g. the unicode character U+4E2D will be +1259 converted into '%4E%2D'. Testing with Firefox v3.0.5 has shown that it can +1260 understand this kind of URI encoding. +1261 +1262 :param url: unicode string, the URL to convert from unicode into US-ASCII +1263 :param prepend_scheme: string, a protocol scheme to prepend to the URL if +1264 we're having trouble parsing it. +1265 e.g. "http". Input None to disable this functionality +1266 :returns: a US-ASCII equivalent of the inputed url +1267 :rtype: string +1268 +1269 @author: Jonathan Benn +1270 ''' +1271 #convert the authority component of the URL into an ASCII punycode string, +1272 #but encode the rest using the regular URI character encoding +1273 +1274 groups = url_split_regex.match(url).groups() +1275 #If no authority was found +1276 if not groups[3]: +1277 #Try appending a scheme to see if that fixes the problem +1278 scheme_to_prepend = prepend_scheme or 'http' +1279 groups = url_split_regex.match( +1280 unicode(scheme_to_prepend) + u'://' + url).groups() +1281 #if we still can't find the authority +1282 if not groups[3]: +1283 raise Exception('No authority component found, '+ \ +1284 'could not decode unicode to US-ASCII') +1285 +1286 #We're here if we found an authority, let's rebuild the URL +1287 scheme = groups[1] +1288 authority = groups[3] +1289 path = groups[4] or '' +1290 query = groups[5] or '' +1291 fragment = groups[7] or '' +1292 +1293 if prepend_scheme: +1294 scheme = str(scheme) + '://' +1295 else: +1296 scheme = '' +1297 return scheme + unicode_to_ascii_authority(authority) +\ +1298 escape_unicode(path) + escape_unicode(query) + str(fragment) +
    1299 +1300 +
    1301 -class IS_GENERIC_URL(Validator): +
    1302 """ +1303 Rejects a URL string if any of the following is true: +1304 * The string is empty or None +1305 * The string uses characters that are not allowed in a URL +1306 * The URL scheme specified (if one is specified) is not valid +1307 +1308 Based on RFC 2396: http://www.faqs.org/rfcs/rfc2396.html +1309 +1310 This function only checks the URL's syntax. It does not check that the URL +1311 points to a real document, for example, or that it otherwise makes sense +1312 semantically. This function does automatically prepend 'http://' in front +1313 of a URL if and only if that's necessary to successfully parse the URL. +1314 Please note that a scheme will be prepended only for rare cases +1315 (e.g. 'google.ca:80') +1316 +1317 The list of allowed schemes is customizable with the allowed_schemes +1318 parameter. If you exclude None from the list, then abbreviated URLs +1319 (lacking a scheme such as 'http') will be rejected. +1320 +1321 The default prepended scheme is customizable with the prepend_scheme +1322 parameter. If you set prepend_scheme to None then prepending will be +1323 disabled. URLs that require prepending to parse will still be accepted, +1324 but the return value will not be modified. +1325 +1326 @author: Jonathan Benn +1327 +1328 >>> IS_GENERIC_URL()('http://user@abc.com') +1329 ('http://user@abc.com', None) +1330 +1331 """ +1332 +
    1333 - def __init__( +1334 self, +1335 error_message='enter a valid URL', +1336 allowed_schemes=None, +1337 prepend_scheme=None, +1338 ): +
    1339 """ +1340 :param error_message: a string, the error message to give the end user +1341 if the URL does not validate +1342 :param allowed_schemes: a list containing strings or None. Each element +1343 is a scheme the inputed URL is allowed to use +1344 :param prepend_scheme: a string, this scheme is prepended if it's +1345 necessary to make the URL valid +1346 """ +1347 +1348 self.error_message = error_message +1349 if allowed_schemes == None: +1350 self.allowed_schemes = all_url_schemes +1351 else: +1352 self.allowed_schemes = allowed_schemes +1353 self.prepend_scheme = prepend_scheme +1354 if self.prepend_scheme not in self.allowed_schemes: +1355 raise SyntaxError, \ +1356 "prepend_scheme='%s' is not in allowed_schemes=%s" \ +1357 % (self.prepend_scheme, self.allowed_schemes) +
    1358 +
    1359 - def __call__(self, value): +
    1360 """ +1361 :param value: a string, the URL to validate +1362 :returns: a tuple, where tuple[0] is the inputed value (possible +1363 prepended with prepend_scheme), and tuple[1] is either +1364 None (success!) or the string error_message +1365 """ +1366 try: +1367 # if the URL does not misuse the '%' character +1368 if not re.compile( +1369 r"%[^0-9A-Fa-f]{2}|%[^0-9A-Fa-f][0-9A-Fa-f]|%[0-9A-Fa-f][^0-9A-Fa-f]|%$|%[0-9A-Fa-f]$|%[^0-9A-Fa-f]$" +1370 ).search(value): +1371 # if the URL is only composed of valid characters +1372 if re.compile( +1373 r"[A-Za-z0-9;/?:@&=+$,\-_\.!~*'\(\)%#]+$").match(value): +1374 # Then split up the URL into its components and check on +1375 # the scheme +1376 scheme = url_split_regex.match(value).group(2) +1377 # Clean up the scheme before we check it +1378 if scheme != None: +1379 scheme = urllib.unquote(scheme).lower() +1380 # If the scheme really exists +1381 if scheme in self.allowed_schemes: +1382 # Then the URL is valid +1383 return (value, None) +1384 else: +1385 # else, for the possible case of abbreviated URLs with +1386 # ports, check to see if adding a valid scheme fixes +1387 # the problem (but only do this if it doesn't have +1388 # one already!) +1389 if not re.compile('://').search(value) and None\ +1390 in self.allowed_schemes: +1391 schemeToUse = self.prepend_scheme or 'http' +1392 prependTest = self.__call__(schemeToUse +1393 + '://' + value) +1394 # if the prepend test succeeded +1395 if prependTest[1] == None: +1396 # if prepending in the output is enabled +1397 if self.prepend_scheme: +1398 return prependTest +1399 else: +1400 # else return the original, +1401 # non-prepended value +1402 return (value, None) +1403 except: +1404 pass +1405 # else the URL is not valid +1406 return (value, translate(self.error_message)) +
    1407 +1408 # Sources (obtained 2008-Nov-11): +1409 # http://en.wikipedia.org/wiki/Top-level_domain +1410 # http://www.iana.org/domains/root/db/ +1411 +1412 official_top_level_domains = [ +1413 'ac', +1414 'ad', +1415 'ae', +1416 'aero', +1417 'af', +1418 'ag', +1419 'ai', +1420 'al', +1421 'am', +1422 'an', +1423 'ao', +1424 'aq', +1425 'ar', +1426 'arpa', +1427 'as', +1428 'asia', +1429 'at', +1430 'au', +1431 'aw', +1432 'ax', +1433 'az', +1434 'ba', +1435 'bb', +1436 'bd', +1437 'be', +1438 'bf', +1439 'bg', +1440 'bh', +1441 'bi', +1442 'biz', +1443 'bj', +1444 'bl', +1445 'bm', +1446 'bn', +1447 'bo', +1448 'br', +1449 'bs', +1450 'bt', +1451 'bv', +1452 'bw', +1453 'by', +1454 'bz', +1455 'ca', +1456 'cat', +1457 'cc', +1458 'cd', +1459 'cf', +1460 'cg', +1461 'ch', +1462 'ci', +1463 'ck', +1464 'cl', +1465 'cm', +1466 'cn', +1467 'co', +1468 'com', +1469 'coop', +1470 'cr', +1471 'cu', +1472 'cv', +1473 'cx', +1474 'cy', +1475 'cz', +1476 'de', +1477 'dj', +1478 'dk', +1479 'dm', +1480 'do', +1481 'dz', +1482 'ec', +1483 'edu', +1484 'ee', +1485 'eg', +1486 'eh', +1487 'er', +1488 'es', +1489 'et', +1490 'eu', +1491 'example', +1492 'fi', +1493 'fj', +1494 'fk', +1495 'fm', +1496 'fo', +1497 'fr', +1498 'ga', +1499 'gb', +1500 'gd', +1501 'ge', +1502 'gf', +1503 'gg', +1504 'gh', +1505 'gi', +1506 'gl', +1507 'gm', +1508 'gn', +1509 'gov', +1510 'gp', +1511 'gq', +1512 'gr', +1513 'gs', +1514 'gt', +1515 'gu', +1516 'gw', +1517 'gy', +1518 'hk', +1519 'hm', +1520 'hn', +1521 'hr', +1522 'ht', +1523 'hu', +1524 'id', +1525 'ie', +1526 'il', +1527 'im', +1528 'in', +1529 'info', +1530 'int', +1531 'invalid', +1532 'io', +1533 'iq', +1534 'ir', +1535 'is', +1536 'it', +1537 'je', +1538 'jm', +1539 'jo', +1540 'jobs', +1541 'jp', +1542 'ke', +1543 'kg', +1544 'kh', +1545 'ki', +1546 'km', +1547 'kn', +1548 'kp', +1549 'kr', +1550 'kw', +1551 'ky', +1552 'kz', +1553 'la', +1554 'lb', +1555 'lc', +1556 'li', +1557 'lk', +1558 'localhost', +1559 'lr', +1560 'ls', +1561 'lt', +1562 'lu', +1563 'lv', +1564 'ly', +1565 'ma', +1566 'mc', +1567 'md', +1568 'me', +1569 'mf', +1570 'mg', +1571 'mh', +1572 'mil', +1573 'mk', +1574 'ml', +1575 'mm', +1576 'mn', +1577 'mo', +1578 'mobi', +1579 'mp', +1580 'mq', +1581 'mr', +1582 'ms', +1583 'mt', +1584 'mu', +1585 'museum', +1586 'mv', +1587 'mw', +1588 'mx', +1589 'my', +1590 'mz', +1591 'na', +1592 'name', +1593 'nc', +1594 'ne', +1595 'net', +1596 'nf', +1597 'ng', +1598 'ni', +1599 'nl', +1600 'no', +1601 'np', +1602 'nr', +1603 'nu', +1604 'nz', +1605 'om', +1606 'org', +1607 'pa', +1608 'pe', +1609 'pf', +1610 'pg', +1611 'ph', +1612 'pk', +1613 'pl', +1614 'pm', +1615 'pn', +1616 'pr', +1617 'pro', +1618 'ps', +1619 'pt', +1620 'pw', +1621 'py', +1622 'qa', +1623 're', +1624 'ro', +1625 'rs', +1626 'ru', +1627 'rw', +1628 'sa', +1629 'sb', +1630 'sc', +1631 'sd', +1632 'se', +1633 'sg', +1634 'sh', +1635 'si', +1636 'sj', +1637 'sk', +1638 'sl', +1639 'sm', +1640 'sn', +1641 'so', +1642 'sr', +1643 'st', +1644 'su', +1645 'sv', +1646 'sy', +1647 'sz', +1648 'tc', +1649 'td', +1650 'tel', +1651 'test', +1652 'tf', +1653 'tg', +1654 'th', +1655 'tj', +1656 'tk', +1657 'tl', +1658 'tm', +1659 'tn', +1660 'to', +1661 'tp', +1662 'tr', +1663 'travel', +1664 'tt', +1665 'tv', +1666 'tw', +1667 'tz', +1668 'ua', +1669 'ug', +1670 'uk', +1671 'um', +1672 'us', +1673 'uy', +1674 'uz', +1675 'va', +1676 'vc', +1677 've', +1678 'vg', +1679 'vi', +1680 'vn', +1681 'vu', +1682 'wf', +1683 'ws', +1684 'xn--0zwm56d', +1685 'xn--11b5bs3a9aj6g', +1686 'xn--80akhbyknj4f', +1687 'xn--9t4b11yi5a', +1688 'xn--deba0ad', +1689 'xn--g6w251d', +1690 'xn--hgbk6aj7f53bba', +1691 'xn--hlcj6aya9esc7a', +1692 'xn--jxalpdlp', +1693 'xn--kgbechtv', +1694 'xn--zckzah', +1695 'ye', +1696 'yt', +1697 'yu', +1698 'za', +1699 'zm', +1700 'zw', +1701 ] +1702 +1703 +
    1704 -class IS_HTTP_URL(Validator): +
    1705 """ +1706 Rejects a URL string if any of the following is true: +1707 * The string is empty or None +1708 * The string uses characters that are not allowed in a URL +1709 * The string breaks any of the HTTP syntactic rules +1710 * The URL scheme specified (if one is specified) is not 'http' or 'https' +1711 * The top-level domain (if a host name is specified) does not exist +1712 +1713 Based on RFC 2616: http://www.faqs.org/rfcs/rfc2616.html +1714 +1715 This function only checks the URL's syntax. It does not check that the URL +1716 points to a real document, for example, or that it otherwise makes sense +1717 semantically. This function does automatically prepend 'http://' in front +1718 of a URL in the case of an abbreviated URL (e.g. 'google.ca'). +1719 +1720 The list of allowed schemes is customizable with the allowed_schemes +1721 parameter. If you exclude None from the list, then abbreviated URLs +1722 (lacking a scheme such as 'http') will be rejected. +1723 +1724 The default prepended scheme is customizable with the prepend_scheme +1725 parameter. If you set prepend_scheme to None then prepending will be +1726 disabled. URLs that require prepending to parse will still be accepted, +1727 but the return value will not be modified. +1728 +1729 @author: Jonathan Benn +1730 +1731 >>> IS_HTTP_URL()('http://1.2.3.4') +1732 ('http://1.2.3.4', None) +1733 >>> IS_HTTP_URL()('http://abc.com') +1734 ('http://abc.com', None) +1735 >>> IS_HTTP_URL()('https://abc.com') +1736 ('https://abc.com', None) +1737 >>> IS_HTTP_URL()('httpx://abc.com') +1738 ('httpx://abc.com', 'enter a valid URL') +1739 >>> IS_HTTP_URL()('http://abc.com:80') +1740 ('http://abc.com:80', None) +1741 >>> IS_HTTP_URL()('http://user@abc.com') +1742 ('http://user@abc.com', None) +1743 >>> IS_HTTP_URL()('http://user@1.2.3.4') +1744 ('http://user@1.2.3.4', None) +1745 +1746 """ +1747 +
    1748 - def __init__( +1749 self, +1750 error_message='enter a valid URL', +1751 allowed_schemes=None, +1752 prepend_scheme='http', +1753 ): +
    1754 """ +1755 :param error_message: a string, the error message to give the end user +1756 if the URL does not validate +1757 :param allowed_schemes: a list containing strings or None. Each element +1758 is a scheme the inputed URL is allowed to use +1759 :param prepend_scheme: a string, this scheme is prepended if it's +1760 necessary to make the URL valid +1761 """ +1762 +1763 self.error_message = error_message +1764 if allowed_schemes == None: +1765 self.allowed_schemes = http_schemes +1766 else: +1767 self.allowed_schemes = allowed_schemes +1768 self.prepend_scheme = prepend_scheme +1769 +1770 for i in self.allowed_schemes: +1771 if i not in http_schemes: +1772 raise SyntaxError, \ +1773 "allowed_scheme value '%s' is not in %s" % \ +1774 (i, http_schemes) +1775 +1776 if self.prepend_scheme not in self.allowed_schemes: +1777 raise SyntaxError, \ +1778 "prepend_scheme='%s' is not in allowed_schemes=%s" % \ +1779 (self.prepend_scheme, self.allowed_schemes) +
    1780 +
    1781 - def __call__(self, value): +
    1782 """ +1783 :param value: a string, the URL to validate +1784 :returns: a tuple, where tuple[0] is the inputed value +1785 (possible prepended with prepend_scheme), and tuple[1] is either +1786 None (success!) or the string error_message +1787 """ +1788 +1789 try: +1790 # if the URL passes generic validation +1791 x = IS_GENERIC_URL(error_message=self.error_message, +1792 allowed_schemes=self.allowed_schemes, +1793 prepend_scheme=self.prepend_scheme) +1794 if x(value)[1] == None: +1795 componentsMatch = url_split_regex.match(value) +1796 authority = componentsMatch.group(4) +1797 # if there is an authority component +1798 if authority: +1799 # if authority is a valid IP address +1800 if re.compile( +1801 "([\w.!~*'|;:&=+$,-]+@)?\d+\.\d+\.\d+\.\d+(:\d*)*$").match(authority): +1802 # Then this HTTP URL is valid +1803 return (value, None) +1804 else: +1805 # else if authority is a valid domain name +1806 domainMatch = \ +1807 re.compile( +1808 "([\w.!~*'|;:&=+$,-]+@)?(([A-Za-z0-9]+[A-Za-z0-9\-]*[A-Za-z0-9]+\.)*([A-Za-z0-9]+\.)*)*([A-Za-z]+[A-Za-z0-9\-]*[A-Za-z0-9]+)\.?(:\d*)*$" +1809 ).match(authority) +1810 if domainMatch: +1811 # if the top-level domain really exists +1812 if domainMatch.group(5).lower()\ +1813 in official_top_level_domains: +1814 # Then this HTTP URL is valid +1815 return (value, None) +1816 else: +1817 # else this is a relative/abbreviated URL, which will parse +1818 # into the URL's path component +1819 path = componentsMatch.group(5) +1820 # relative case: if this is a valid path (if it starts with +1821 # a slash) +1822 if re.compile('/').match(path): +1823 # Then this HTTP URL is valid +1824 return (value, None) +1825 else: +1826 # abbreviated case: if we haven't already, prepend a +1827 # scheme and see if it fixes the problem +1828 if not re.compile('://').search(value): +1829 schemeToUse = self.prepend_scheme or 'http' +1830 prependTest = self.__call__(schemeToUse +1831 + '://' + value) +1832 # if the prepend test succeeded +1833 if prependTest[1] == None: +1834 # if prepending in the output is enabled +1835 if self.prepend_scheme: +1836 return prependTest +1837 else: +1838 # else return the original, non-prepended +1839 # value +1840 return (value, None) +1841 except: +1842 pass +1843 # else the HTTP URL is not valid +1844 return (value, translate(self.error_message)) +
    1845 +1846 +
    1847 -class IS_URL(Validator): +
    1848 """ +1849 Rejects a URL string if any of the following is true: +1850 * The string is empty or None +1851 * The string uses characters that are not allowed in a URL +1852 * The string breaks any of the HTTP syntactic rules +1853 * The URL scheme specified (if one is specified) is not 'http' or 'https' +1854 * The top-level domain (if a host name is specified) does not exist +1855 +1856 (These rules are based on RFC 2616: http://www.faqs.org/rfcs/rfc2616.html) +1857 +1858 This function only checks the URL's syntax. It does not check that the URL +1859 points to a real document, for example, or that it otherwise makes sense +1860 semantically. This function does automatically prepend 'http://' in front +1861 of a URL in the case of an abbreviated URL (e.g. 'google.ca'). +1862 +1863 If the parameter mode='generic' is used, then this function's behavior +1864 changes. It then rejects a URL string if any of the following is true: +1865 * The string is empty or None +1866 * The string uses characters that are not allowed in a URL +1867 * The URL scheme specified (if one is specified) is not valid +1868 +1869 (These rules are based on RFC 2396: http://www.faqs.org/rfcs/rfc2396.html) +1870 +1871 The list of allowed schemes is customizable with the allowed_schemes +1872 parameter. If you exclude None from the list, then abbreviated URLs +1873 (lacking a scheme such as 'http') will be rejected. +1874 +1875 The default prepended scheme is customizable with the prepend_scheme +1876 parameter. If you set prepend_scheme to None then prepending will be +1877 disabled. URLs that require prepending to parse will still be accepted, +1878 but the return value will not be modified. +1879 +1880 IS_URL is compatible with the Internationalized Domain Name (IDN) standard +1881 specified in RFC 3490 (http://tools.ietf.org/html/rfc3490). As a result, +1882 URLs can be regular strings or unicode strings. +1883 If the URL's domain component (e.g. google.ca) contains non-US-ASCII +1884 letters, then the domain will be converted into Punycode (defined in +1885 RFC 3492, http://tools.ietf.org/html/rfc3492). IS_URL goes a bit beyond +1886 the standards, and allows non-US-ASCII characters to be present in the path +1887 and query components of the URL as well. These non-US-ASCII characters will +1888 be escaped using the standard '%20' type syntax. e.g. the unicode +1889 character with hex code 0x4e86 will become '%4e%86' +1890 +1891 Code Examples:: +1892 +1893 INPUT(_type='text', _name='name', requires=IS_URL()) +1894 >>> IS_URL()('abc.com') +1895 ('http://abc.com', None) +1896 +1897 INPUT(_type='text', _name='name', requires=IS_URL(mode='generic')) +1898 >>> IS_URL(mode='generic')('abc.com') +1899 ('abc.com', None) +1900 +1901 INPUT(_type='text', _name='name', +1902 requires=IS_URL(allowed_schemes=['https'], prepend_scheme='https')) +1903 >>> IS_URL(allowed_schemes=['https'], prepend_scheme='https')('https://abc.com') +1904 ('https://abc.com', None) +1905 +1906 INPUT(_type='text', _name='name', +1907 requires=IS_URL(prepend_scheme='https')) +1908 >>> IS_URL(prepend_scheme='https')('abc.com') +1909 ('https://abc.com', None) +1910 +1911 INPUT(_type='text', _name='name', +1912 requires=IS_URL(mode='generic', allowed_schemes=['ftps', 'https'], +1913 prepend_scheme='https')) +1914 >>> IS_URL(mode='generic', allowed_schemes=['ftps', 'https'], prepend_scheme='https')('https://abc.com') +1915 ('https://abc.com', None) +1916 >>> IS_URL(mode='generic', allowed_schemes=['ftps', 'https', None], prepend_scheme='https')('abc.com') +1917 ('abc.com', None) +1918 +1919 @author: Jonathan Benn +1920 """ +1921 +
    1922 - def __init__( +1923 self, +1924 error_message='enter a valid URL', +1925 mode='http', +1926 allowed_schemes=None, +1927 prepend_scheme='http', +1928 ): +
    1929 """ +1930 :param error_message: a string, the error message to give the end user +1931 if the URL does not validate +1932 :param allowed_schemes: a list containing strings or None. Each element +1933 is a scheme the inputed URL is allowed to use +1934 :param prepend_scheme: a string, this scheme is prepended if it's +1935 necessary to make the URL valid +1936 """ +1937 +1938 self.error_message = error_message +1939 self.mode = mode.lower() +1940 if not self.mode in ['generic', 'http']: +1941 raise SyntaxError, "invalid mode '%s' in IS_URL" % self.mode +1942 self.allowed_schemes = allowed_schemes +1943 +1944 if self.allowed_schemes: +1945 if prepend_scheme not in self.allowed_schemes: +1946 raise SyntaxError, \ +1947 "prepend_scheme='%s' is not in allowed_schemes=%s" \ +1948 % (prepend_scheme, self.allowed_schemes) +1949 +1950 # if allowed_schemes is None, then we will defer testing +1951 # prepend_scheme's validity to a sub-method +1952 +1953 self.prepend_scheme = prepend_scheme +
    1954 +
    1955 - def __call__(self, value): +
    1956 """ +1957 :param value: a unicode or regular string, the URL to validate +1958 :returns: a (string, string) tuple, where tuple[0] is the modified +1959 input value and tuple[1] is either None (success!) or the +1960 string error_message. The input value will never be modified in the +1961 case of an error. However, if there is success then the input URL +1962 may be modified to (1) prepend a scheme, and/or (2) convert a +1963 non-compliant unicode URL into a compliant US-ASCII version. +1964 """ +1965 +1966 if self.mode == 'generic': +1967 subMethod = IS_GENERIC_URL(error_message=self.error_message, +1968 allowed_schemes=self.allowed_schemes, +1969 prepend_scheme=self.prepend_scheme) +1970 elif self.mode == 'http': +1971 subMethod = IS_HTTP_URL(error_message=self.error_message, +1972 allowed_schemes=self.allowed_schemes, +1973 prepend_scheme=self.prepend_scheme) +1974 else: +1975 raise SyntaxError, "invalid mode '%s' in IS_URL" % self.mode +1976 +1977 if type(value) != unicode: +1978 return subMethod(value) +1979 else: +1980 try: +1981 asciiValue = unicode_to_ascii_url(value, self.prepend_scheme) +1982 except Exception: +1983 #If we are not able to convert the unicode url into a +1984 # US-ASCII URL, then the URL is not valid +1985 return (value, translate(self.error_message)) +1986 +1987 methodResult = subMethod(asciiValue) +1988 #if the validation of the US-ASCII version of the value failed +1989 if methodResult[1] != None: +1990 # then return the original input value, not the US-ASCII version +1991 return (value, methodResult[1]) +1992 else: +1993 return methodResult +
    1994 +1995 +1996 regex_time = re.compile( +1997 '((?P<h>[0-9]+))([^0-9 ]+(?P<m>[0-9 ]+))?([^0-9ap ]+(?P<s>[0-9]*))?((?P<d>[ap]m))?') +1998 +1999 +
    2000 -class IS_TIME(Validator): +
    2001 """ +2002 example:: +2003 +2004 INPUT(_type='text', _name='name', requires=IS_TIME()) +2005 +2006 understands the following formats +2007 hh:mm:ss [am/pm] +2008 hh:mm [am/pm] +2009 hh [am/pm] +2010 +2011 [am/pm] is optional, ':' can be replaced by any other non-space non-digit +2012 +2013 >>> IS_TIME()('21:30') +2014 (datetime.time(21, 30), None) +2015 >>> IS_TIME()('21-30') +2016 (datetime.time(21, 30), None) +2017 >>> IS_TIME()('21.30') +2018 (datetime.time(21, 30), None) +2019 >>> IS_TIME()('21:30:59') +2020 (datetime.time(21, 30, 59), None) +2021 >>> IS_TIME()('5:30') +2022 (datetime.time(5, 30), None) +2023 >>> IS_TIME()('5:30 am') +2024 (datetime.time(5, 30), None) +2025 >>> IS_TIME()('5:30 pm') +2026 (datetime.time(17, 30), None) +2027 >>> IS_TIME()('5:30 whatever') +2028 ('5:30 whatever', 'enter time as hh:mm:ss (seconds, am, pm optional)') +2029 >>> IS_TIME()('5:30 20') +2030 ('5:30 20', 'enter time as hh:mm:ss (seconds, am, pm optional)') +2031 >>> IS_TIME()('24:30') +2032 ('24:30', 'enter time as hh:mm:ss (seconds, am, pm optional)') +2033 >>> IS_TIME()('21:60') +2034 ('21:60', 'enter time as hh:mm:ss (seconds, am, pm optional)') +2035 >>> IS_TIME()('21:30::') +2036 ('21:30::', 'enter time as hh:mm:ss (seconds, am, pm optional)') +2037 >>> IS_TIME()('') +2038 ('', 'enter time as hh:mm:ss (seconds, am, pm optional)') +2039 """ +2040 +
    2041 - def __init__(self, error_message='enter time as hh:mm:ss (seconds, am, pm optional)'): +
    2042 self.error_message = error_message +
    2043 +
    2044 - def __call__(self, value): +
    2045 try: +2046 ivalue = value +2047 value = regex_time.match(value.lower()) +2048 (h, m, s) = (int(value.group('h')), 0, 0) +2049 if value.group('m') != None: +2050 m = int(value.group('m')) +2051 if value.group('s') != None: +2052 s = int(value.group('s')) +2053 if value.group('d') == 'pm' and 0 < h < 12: +2054 h = h + 12 +2055 if not (h in range(24) and m in range(60) and s +2056 in range(60)): +2057 raise ValueError\ +2058 ('Hours or minutes or seconds are outside of allowed range') +2059 value = datetime.time(h, m, s) +2060 return (value, None) +2061 except AttributeError: +2062 pass +2063 except ValueError: +2064 pass +2065 return (ivalue, translate(self.error_message)) +
    2066 +2067 +
    2068 -class IS_DATE(Validator): +
    2069 """ +2070 example:: +2071 +2072 INPUT(_type='text', _name='name', requires=IS_DATE()) +2073 +2074 date has to be in the ISO8960 format YYYY-MM-DD +2075 """ +2076 +
    2077 - def __init__(self, format='%Y-%m-%d', +2078 error_message='enter date as %(format)s'): +
    2079 self.format = str(format) +2080 self.error_message = str(error_message) +
    2081 +
    2082 - def __call__(self, value): +
    2083 if isinstance(value,datetime.date): +2084 return (value,None) +2085 try: +2086 (y, m, d, hh, mm, ss, t0, t1, t2) = \ +2087 time.strptime(value, str(self.format)) +2088 value = datetime.date(y, m, d) +2089 return (value, None) +2090 except: +2091 return (value, translate(self.error_message) % IS_DATETIME.nice(self.format)) +
    2092 +
    2093 - def formatter(self, value): +
    2094 format = self.format +2095 year = value.year +2096 y = '%.4i' % year +2097 format = format.replace('%y',y[-2:]) +2098 format = format.replace('%Y',y) +2099 if year<1900: +2100 year = 2000 +2101 d = datetime.date(year,value.month,value.day) +2102 return d.strftime(format) +
    2103 +2104 +
    2105 -class IS_DATETIME(Validator): +
    2106 """ +2107 example:: +2108 +2109 INPUT(_type='text', _name='name', requires=IS_DATETIME()) +2110 +2111 datetime has to be in the ISO8960 format YYYY-MM-DD hh:mm:ss +2112 """ +2113 +2114 isodatetime = '%Y-%m-%d %H:%M:%S' +2115 +2116 @staticmethod +
    2117 - def nice(format): +
    2118 code=(('%Y','1963'), +2119 ('%y','63'), +2120 ('%d','28'), +2121 ('%m','08'), +2122 ('%b','Aug'), +2123 ('%b','August'), +2124 ('%H','14'), +2125 ('%I','02'), +2126 ('%p','PM'), +2127 ('%M','30'), +2128 ('%S','59')) +2129 for (a,b) in code: +2130 format=format.replace(a,b) +2131 return dict(format=format) +
    2132 +
    2133 - def __init__(self, format='%Y-%m-%d %H:%M:%S', +2134 error_message='enter date and time as %(format)s'): +
    2135 self.format = str(format) +2136 self.error_message = str(error_message) +
    2137 +
    2138 - def __call__(self, value): +
    2139 if isinstance(value,datetime.datetime): +2140 return (value,None) +2141 try: +2142 (y, m, d, hh, mm, ss, t0, t1, t2) = \ +2143 time.strptime(value, str(self.format)) +2144 value = datetime.datetime(y, m, d, hh, mm, ss) +2145 return (value, None) +2146 except: +2147 return (value, translate(self.error_message) % IS_DATETIME.nice(self.format)) +
    2148 +
    2149 - def formatter(self, value): +
    2150 format = self.format +2151 year = value.year +2152 y = '%.4i' % year +2153 format = format.replace('%y',y[-2:]) +2154 format = format.replace('%Y',y) +2155 if year<1900: +2156 year = 2000 +2157 d = datetime.datetime(year,value.month,value.day,value.hour,value.minute,value.second) +2158 return d.strftime(format) +
    2159 +
    2160 -class IS_DATE_IN_RANGE(IS_DATE): +
    2161 """ +2162 example:: +2163 +2164 >>> v = IS_DATE_IN_RANGE(minimum=datetime.date(2008,1,1), \ +2165 maximum=datetime.date(2009,12,31), \ +2166 format="%m/%d/%Y",error_message="oops") +2167 +2168 >>> v('03/03/2008') +2169 (datetime.date(2008, 3, 3), None) +2170 +2171 >>> v('03/03/2010') +2172 (datetime.date(2010, 3, 3), 'oops') +2173 +2174 >>> v(datetime.date(2008,3,3)) +2175 (datetime.date(2008, 3, 3), None) +2176 +2177 >>> v(datetime.date(2010,3,3)) +2178 (datetime.date(2010, 3, 3), 'oops') +2179 +2180 """ +
    2181 - def __init__(self, +2182 minimum = None, +2183 maximum = None, +2184 format='%Y-%m-%d', +2185 error_message = None): +
    2186 self.minimum = minimum +2187 self.maximum = maximum +2188 if error_message is None: +2189 if minimum is None: +2190 error_message = "enter date on or before %(max)s" +2191 elif maximum is None: +2192 error_message = "enter date on or after %(min)s" +2193 else: +2194 error_message = "enter date in range %(min)s %(max)s" +2195 d = dict(min=minimum, max=maximum) +2196 IS_DATE.__init__(self, +2197 format = format, +2198 error_message = error_message % d) +
    2199 +
    2200 - def __call__(self, value): +
    2201 (value, msg) = IS_DATE.__call__(self,value) +2202 if msg is not None: +2203 return (value, msg) +2204 if self.minimum and self.minimum > value: +2205 return (value, translate(self.error_message)) +2206 if self.maximum and value > self.maximum: +2207 return (value, translate(self.error_message)) +2208 return (value, None) +
    2209 +2210 +
    2211 -class IS_DATETIME_IN_RANGE(IS_DATETIME): +
    2212 """ +2213 example:: +2214 +2215 >>> v = IS_DATETIME_IN_RANGE(\ +2216 minimum=datetime.datetime(2008,1,1,12,20), \ +2217 maximum=datetime.datetime(2009,12,31,12,20), \ +2218 format="%m/%d/%Y %H:%M",error_message="oops") +2219 >>> v('03/03/2008 12:40') +2220 (datetime.datetime(2008, 3, 3, 12, 40), None) +2221 +2222 >>> v('03/03/2010 10:34') +2223 (datetime.datetime(2010, 3, 3, 10, 34), 'oops') +2224 +2225 >>> v(datetime.datetime(2008,3,3,0,0)) +2226 (datetime.datetime(2008, 3, 3, 0, 0), None) +2227 +2228 >>> v(datetime.datetime(2010,3,3,0,0)) +2229 (datetime.datetime(2010, 3, 3, 0, 0), 'oops') +2230 """ +
    2231 - def __init__(self, +2232 minimum = None, +2233 maximum = None, +2234 format = '%Y-%m-%d %H:%M:%S', +2235 error_message = None): +
    2236 self.minimum = minimum +2237 self.maximum = maximum +2238 if error_message is None: +2239 if minimum is None: +2240 error_message = "enter date and time on or before %(max)s" +2241 elif maximum is None: +2242 error_message = "enter date and time on or after %(min)s" +2243 else: +2244 error_message = "enter date and time in range %(min)s %(max)s" +2245 d = dict(min = minimum, max = maximum) +2246 IS_DATETIME.__init__(self, +2247 format = format, +2248 error_message = error_message % d) +
    2249 +
    2250 - def __call__(self, value): +
    2251 (value, msg) = IS_DATETIME.__call__(self, value) +2252 if msg is not None: +2253 return (value, msg) +2254 if self.minimum and self.minimum > value: +2255 return (value, translate(self.error_message)) +2256 if self.maximum and value > self.maximum: +2257 return (value, translate(self.error_message)) +2258 return (value, None) +
    2259 +2260 +
    2261 -class IS_LIST_OF(Validator): +
    2262 +
    2263 - def __init__(self, other): +
    2264 self.other = other +
    2265 +
    2266 - def __call__(self, value): +
    2267 ivalue = value +2268 if not isinstance(value, list): +2269 ivalue = [ivalue] +2270 new_value = [] +2271 for item in ivalue: +2272 (v, e) = self.other(item) +2273 if e: +2274 return (value, e) +2275 else: +2276 new_value.append(v) +2277 return (new_value, None) +
    2278 +2279 +
    2280 -class IS_LOWER(Validator): +
    2281 """ +2282 convert to lower case +2283 +2284 >>> IS_LOWER()('ABC') +2285 ('abc', None) +2286 >>> IS_LOWER()('Ñ') +2287 ('\\xc3\\xb1', None) +2288 """ +2289 +
    2290 - def __call__(self, value): +
    2291 return (value.decode('utf8').lower().encode('utf8'), None) +
    2292 +2293 +
    2294 -class IS_UPPER(Validator): +
    2295 """ +2296 convert to upper case +2297 +2298 >>> IS_UPPER()('abc') +2299 ('ABC', None) +2300 >>> IS_UPPER()('ñ') +2301 ('\\xc3\\x91', None) +2302 """ +2303 +
    2304 - def __call__(self, value): +
    2305 return (value.decode('utf8').upper().encode('utf8'), None) +
    2306 +2307 +
    2308 -def urlify(value, maxlen=80, keep_underscores=False): +
    2309 """ +2310 Convert incoming string to a simplified ASCII subset. +2311 if (keep_underscores): underscores are retained in the string +2312 else: underscores are translated to hyphens (default) +2313 """ +2314 s = value.lower() # to lowercase +2315 s = s.decode('utf-8') # to utf-8 +2316 s = unicodedata.normalize('NFKD', s) # normalize eg è => e, ñ => n +2317 s = s.encode('ASCII', 'ignore') # encode as ASCII +2318 s = re.sub('&\w+;', '', s) # strip html entities +2319 if keep_underscores: +2320 s = re.sub('\s+', '-', s) # whitespace to hyphens +2321 s = re.sub('[^\w\-]', '', s) # strip all but alphanumeric/underscore/hyphen +2322 else: +2323 s = re.sub('[\s_]+', '-', s) # whitespace & underscores to hyphens +2324 s = re.sub('[^a-z0-9\-]', '', s) # strip all but alphanumeric/hyphen +2325 s = re.sub('[-_][-_]+', '-', s) # collapse strings of hyphens +2326 s = s.strip('-') # remove leading and trailing hyphens +2327 return s[:maxlen] # enforce maximum length +
    2328 +2329 +
    2330 -class IS_SLUG(Validator): +
    2331 """ +2332 convert arbitrary text string to a slug +2333 +2334 >>> IS_SLUG()('abc123') +2335 ('abc123', None) +2336 >>> IS_SLUG()('ABC123') +2337 ('abc123', None) +2338 >>> IS_SLUG()('abc-123') +2339 ('abc-123', None) +2340 >>> IS_SLUG()('abc--123') +2341 ('abc-123', None) +2342 >>> IS_SLUG()('abc 123') +2343 ('abc-123', None) +2344 >>> IS_SLUG()('abc\t_123') +2345 ('abc-123', None) +2346 >>> IS_SLUG()('-abc-') +2347 ('abc', None) +2348 >>> IS_SLUG()('--a--b--_ -c--') +2349 ('a-b-c', None) +2350 >>> IS_SLUG()('abc&amp;123') +2351 ('abc123', None) +2352 >>> IS_SLUG()('abc&amp;123&amp;def') +2353 ('abc123def', None) +2354 >>> IS_SLUG()('ñ') +2355 ('n', None) +2356 >>> IS_SLUG(maxlen=4)('abc123') +2357 ('abc1', None) +2358 >>> IS_SLUG()('abc_123') +2359 ('abc-123', None) +2360 >>> IS_SLUG(keep_underscores=False)('abc_123') +2361 ('abc-123', None) +2362 >>> IS_SLUG(keep_underscores=True)('abc_123') +2363 ('abc_123', None) +2364 >>> IS_SLUG(check=False)('abc') +2365 ('abc', None) +2366 >>> IS_SLUG(check=True)('abc') +2367 ('abc', None) +2368 >>> IS_SLUG(check=False)('a bc') +2369 ('a-bc', None) +2370 >>> IS_SLUG(check=True)('a bc') +2371 ('a bc', 'must be slug') +2372 """ +2373 +2374 @staticmethod +
    2375 - def urlify(value, maxlen=80, keep_underscores=False): +
    2376 return urlify(value, maxlen, keep_underscores) +
    2377 +
    2378 - def __init__(self, maxlen=80, check=False, error_message='must be slug', keep_underscores=False): +
    2379 self.maxlen = maxlen +2380 self.check = check +2381 self.error_message = error_message +2382 self.keep_underscores = keep_underscores +
    2383 +
    2384 - def __call__(self, value): +
    2385 if self.check and value != urlify(value, self.maxlen, self.keep_underscores): +2386 return (value, translate(self.error_message)) +2387 return (urlify(value,self.maxlen, self.keep_underscores), None) +
    2388 +
    2389 -class IS_EMPTY_OR(Validator): +
    2390 """ +2391 dummy class for testing IS_EMPTY_OR +2392 +2393 >>> IS_EMPTY_OR(IS_EMAIL())('abc@def.com') +2394 ('abc@def.com', None) +2395 >>> IS_EMPTY_OR(IS_EMAIL())(' ') +2396 (None, None) +2397 >>> IS_EMPTY_OR(IS_EMAIL(), null='abc')(' ') +2398 ('abc', None) +2399 >>> IS_EMPTY_OR(IS_EMAIL(), null='abc', empty_regex='def')('def') +2400 ('abc', None) +2401 >>> IS_EMPTY_OR(IS_EMAIL())('abc') +2402 ('abc', 'enter a valid email address') +2403 >>> IS_EMPTY_OR(IS_EMAIL())(' abc ') +2404 ('abc', 'enter a valid email address') +2405 """ +2406 +
    2407 - def __init__(self, other, null=None, empty_regex=None): +
    2408 (self.other, self.null) = (other, null) +2409 if empty_regex is not None: +2410 self.empty_regex = re.compile(empty_regex) +2411 else: +2412 self.empty_regex = None +2413 if hasattr(other, 'multiple'): +2414 self.multiple = other.multiple +2415 if hasattr(other, 'options'): +2416 self.options=self._options +
    2417 +
    2418 - def _options(self): +
    2419 options = self.other.options() +2420 if (not options or options[0][0]!='') and not self.multiple: +2421 options.insert(0,('','')) +2422 return options +
    2423 +
    2424 - def set_self_id(self, id): +
    2425 if isinstance(self.other, (list, tuple)): +2426 for item in self.other: +2427 if hasattr(item, 'set_self_id'): +2428 item.set_self_id(id) +2429 else: +2430 if hasattr(self.other, 'set_self_id'): +2431 self.other.set_self_id(id) +
    2432 +
    2433 - def __call__(self, value): +
    2434 value, empty = is_empty(value, empty_regex=self.empty_regex) +2435 if empty: +2436 return (self.null, None) +2437 if isinstance(self.other, (list, tuple)): +2438 for item in self.other: +2439 value, error = item(value) +2440 if error: break +2441 return value, error +2442 else: +2443 return self.other(value) +
    2444 +
    2445 - def formatter(self, value): +
    2446 if hasattr(self.other, 'formatter'): +2447 return self.other.formatter(value) +2448 return value +
    2449 +2450 IS_NULL_OR = IS_EMPTY_OR # for backward compatibility +2451 +2452 +
    2453 -class CLEANUP(Validator): +
    2454 """ +2455 example:: +2456 +2457 INPUT(_type='text', _name='name', requires=CLEANUP()) +2458 +2459 removes special characters on validation +2460 """ +2461 +
    2462 - def __init__(self, regex='[^\x09\x0a\x0d\x20-\x7e]'): +
    2463 self.regex = re.compile(regex) +
    2464 +
    2465 - def __call__(self, value): +
    2466 v = self.regex.sub('',str(value).strip()) +2467 return (v, None) +
    2468 +2469 +
    2470 -class CRYPT(object): +
    2471 """ +2472 example:: +2473 +2474 INPUT(_type='text', _name='name', requires=CRYPT()) +2475 +2476 encodes the value on validation with a digest. +2477 +2478 If no arguments are provided CRYPT uses the MD5 algorithm. +2479 If the key argument is provided the HMAC+MD5 algorithm is used. +2480 If the digest_alg is specified this is used to replace the +2481 MD5 with, for example, SHA512. The digest_alg can be +2482 the name of a hashlib algorithm as a string or the algorithm itself. +2483 """ +2484 +
    2485 - def __init__(self, key=None, digest_alg='md5'): +
    2486 self.key = key +2487 self.digest_alg = digest_alg +
    2488 +
    2489 - def __call__(self, value): +
    2490 if self.key: +2491 return (hmac_hash(value, self.key, self.digest_alg), None) +2492 else: +2493 return (simple_hash(value, self.digest_alg), None) +
    2494 +2495 +
    2496 -class IS_STRONG(object): +
    2497 """ +2498 example:: +2499 +2500 INPUT(_type='password', _name='passwd', +2501 requires=IS_STRONG(min=10, special=2, upper=2)) +2502 +2503 enforces complexity requirements on a field +2504 """ +2505 +
    2506 - def __init__(self, min=8, max=20, upper=1, lower=1, number=1, +2507 special=1, specials=r'~!@#$%^&*()_+-=?<>,.:;{}[]|', +2508 invalid=' "', error_message=None): +
    2509 self.min = min +2510 self.max = max +2511 self.upper = upper +2512 self.lower = lower +2513 self.number = number +2514 self.special = special +2515 self.specials = specials +2516 self.invalid = invalid +2517 self.error_message = error_message +
    2518 +
    2519 - def __call__(self, value): +
    2520 failures = [] +2521 if type(self.min) == int and self.min > 0: +2522 if not len(value) >= self.min: +2523 failures.append("Minimum length is %s" % self.min) +2524 if type(self.max) == int and self.max > 0: +2525 if not len(value) <= self.max: +2526 failures.append("Maximum length is %s" % self.max) +2527 if type(self.special) == int: +2528 all_special = [ch in value for ch in self.specials] +2529 if self.special > 0: +2530 if not all_special.count(True) >= self.special: +2531 failures.append("Must include at least %s of the following : %s" % (self.special, self.specials)) +2532 if self.invalid: +2533 all_invalid = [ch in value for ch in self.invalid] +2534 if all_invalid.count(True) > 0: +2535 failures.append("May not contain any of the following: %s" \ +2536 % self.invalid) +2537 if type(self.upper) == int: +2538 all_upper = re.findall("[A-Z]", value) +2539 if self.upper > 0: +2540 if not len(all_upper) >= self.upper: +2541 failures.append("Must include at least %s upper case" \ +2542 % str(self.upper)) +2543 else: +2544 if len(all_upper) > 0: +2545 failures.append("May not include any upper case letters") +2546 if type(self.lower) == int: +2547 all_lower = re.findall("[a-z]", value) +2548 if self.lower > 0: +2549 if not len(all_lower) >= self.lower: +2550 failures.append("Must include at least %s lower case" \ +2551 % str(self.lower)) +2552 else: +2553 if len(all_lower) > 0: +2554 failures.append("May not include any lower case letters") +2555 if type(self.number) == int: +2556 all_number = re.findall("[0-9]", value) +2557 if self.number > 0: +2558 numbers = "number" +2559 if self.number > 1: +2560 numbers = "numbers" +2561 if not len(all_number) >= self.number: +2562 failures.append("Must include at least %s %s" \ +2563 % (str(self.number), numbers)) +2564 else: +2565 if len(all_number) > 0: +2566 failures.append("May not include any numbers") +2567 if len(failures) == 0: +2568 return (value, None) +2569 if not translate(self.error_message): +2570 from html import XML +2571 return (value, XML('<br />'.join(failures))) +2572 else: +2573 return (value, translate(self.error_message)) +
    2574 +2575 +
    2576 -class IS_IN_SUBSET(IS_IN_SET): +
    2577 +
    2578 - def __init__(self, *a, **b): +
    2579 IS_IN_SET.__init__(self, *a, **b) +
    2580 +
    2581 - def __call__(self, value): +
    2582 values = re.compile("\w+").findall(str(value)) +2583 failures = [x for x in values if IS_IN_SET.__call__(self, x)[1]] +2584 if failures: +2585 return (value, translate(self.error_message)) +2586 return (value, None) +
    2587 +2588 +
    2589 -class IS_IMAGE(Validator): +
    2590 """ +2591 Checks if file uploaded through file input was saved in one of selected +2592 image formats and has dimensions (width and height) within given boundaries. +2593 +2594 Does *not* check for maximum file size (use IS_LENGTH for that). Returns +2595 validation failure if no data was uploaded. +2596 +2597 Supported file formats: BMP, GIF, JPEG, PNG. +2598 +2599 Code parts taken from +2600 http://mail.python.org/pipermail/python-list/2007-June/617126.html +2601 +2602 Arguments: +2603 +2604 extensions: iterable containing allowed *lowercase* image file extensions +2605 ('jpg' extension of uploaded file counts as 'jpeg') +2606 maxsize: iterable containing maximum width and height of the image +2607 minsize: iterable containing minimum width and height of the image +2608 +2609 Use (-1, -1) as minsize to pass image size check. +2610 +2611 Examples:: +2612 +2613 #Check if uploaded file is in any of supported image formats: +2614 INPUT(_type='file', _name='name', requires=IS_IMAGE()) +2615 +2616 #Check if uploaded file is either JPEG or PNG: +2617 INPUT(_type='file', _name='name', +2618 requires=IS_IMAGE(extensions=('jpeg', 'png'))) +2619 +2620 #Check if uploaded file is PNG with maximum size of 200x200 pixels: +2621 INPUT(_type='file', _name='name', +2622 requires=IS_IMAGE(extensions=('png'), maxsize=(200, 200))) +2623 """ +2624 +
    2625 - def __init__(self, +2626 extensions=('bmp', 'gif', 'jpeg', 'png'), +2627 maxsize=(10000, 10000), +2628 minsize=(0, 0), +2629 error_message='invalid image'): +
    2630 +2631 self.extensions = extensions +2632 self.maxsize = maxsize +2633 self.minsize = minsize +2634 self.error_message = error_message +
    2635 +
    2636 - def __call__(self, value): +
    2637 try: +2638 extension = value.filename.rfind('.') +2639 assert extension >= 0 +2640 extension = value.filename[extension + 1:].lower() +2641 if extension == 'jpg': +2642 extension = 'jpeg' +2643 assert extension in self.extensions +2644 if extension == 'bmp': +2645 width, height = self.__bmp(value.file) +2646 elif extension == 'gif': +2647 width, height = self.__gif(value.file) +2648 elif extension == 'jpeg': +2649 width, height = self.__jpeg(value.file) +2650 elif extension == 'png': +2651 width, height = self.__png(value.file) +2652 else: +2653 width = -1 +2654 height = -1 +2655 assert self.minsize[0] <= width <= self.maxsize[0] \ +2656 and self.minsize[1] <= height <= self.maxsize[1] +2657 value.file.seek(0) +2658 return (value, None) +2659 except: +2660 return (value, translate(self.error_message)) +
    2661 +
    2662 - def __bmp(self, stream): +
    2663 if stream.read(2) == 'BM': +2664 stream.read(16) +2665 return struct.unpack("<LL", stream.read(8)) +2666 return (-1, -1) +
    2667 +
    2668 - def __gif(self, stream): +
    2669 if stream.read(6) in ('GIF87a', 'GIF89a'): +2670 stream = stream.read(5) +2671 if len(stream) == 5: +2672 return tuple(struct.unpack("<HHB", stream)[:-1]) +2673 return (-1, -1) +
    2674 +
    2675 - def __jpeg(self, stream): +
    2676 if stream.read(2) == '\xFF\xD8': +2677 while True: +2678 (marker, code, length) = struct.unpack("!BBH", stream.read(4)) +2679 if marker != 0xFF: +2680 break +2681 elif code >= 0xC0 and code <= 0xC3: +2682 return tuple(reversed( +2683 struct.unpack("!xHH", stream.read(5)))) +2684 else: +2685 stream.read(length - 2) +2686 return (-1, -1) +
    2687 +
    2688 - def __png(self, stream): +
    2689 if stream.read(8) == '\211PNG\r\n\032\n': +2690 stream.read(4) +2691 if stream.read(4) == "IHDR": +2692 return struct.unpack("!LL", stream.read(8)) +2693 return (-1, -1) +
    2694 +2695 +
    2696 -class IS_UPLOAD_FILENAME(Validator): +
    2697 """ +2698 Checks if name and extension of file uploaded through file input matches +2699 given criteria. +2700 +2701 Does *not* ensure the file type in any way. Returns validation failure +2702 if no data was uploaded. +2703 +2704 Arguments:: +2705 +2706 filename: filename (before dot) regex +2707 extension: extension (after dot) regex +2708 lastdot: which dot should be used as a filename / extension separator: +2709 True means last dot, eg. file.png -> file / png +2710 False means first dot, eg. file.tar.gz -> file / tar.gz +2711 case: 0 - keep the case, 1 - transform the string into lowercase (default), +2712 2 - transform the string into uppercase +2713 +2714 If there is no dot present, extension checks will be done against empty +2715 string and filename checks against whole value. +2716 +2717 Examples:: +2718 +2719 #Check if file has a pdf extension (case insensitive): +2720 INPUT(_type='file', _name='name', +2721 requires=IS_UPLOAD_FILENAME(extension='pdf')) +2722 +2723 #Check if file has a tar.gz extension and name starting with backup: +2724 INPUT(_type='file', _name='name', +2725 requires=IS_UPLOAD_FILENAME(filename='backup.*', +2726 extension='tar.gz', lastdot=False)) +2727 +2728 #Check if file has no extension and name matching README +2729 #(case sensitive): +2730 INPUT(_type='file', _name='name', +2731 requires=IS_UPLOAD_FILENAME(filename='^README$', +2732 extension='^$', case=0)) +2733 """ +2734 +
    2735 - def __init__(self, filename=None, extension=None, lastdot=True, case=1, +2736 error_message='enter valid filename'): +
    2737 if isinstance(filename, str): +2738 filename = re.compile(filename) +2739 if isinstance(extension, str): +2740 extension = re.compile(extension) +2741 self.filename = filename +2742 self.extension = extension +2743 self.lastdot = lastdot +2744 self.case = case +2745 self.error_message = error_message +
    2746 +
    2747 - def __call__(self, value): +
    2748 try: +2749 string = value.filename +2750 except: +2751 return (value, translate(self.error_message)) +2752 if self.case == 1: +2753 string = string.lower() +2754 elif self.case == 2: +2755 string = string.upper() +2756 if self.lastdot: +2757 dot = string.rfind('.') +2758 else: +2759 dot = string.find('.') +2760 if dot == -1: +2761 dot = len(string) +2762 if self.filename and not self.filename.match(string[:dot]): +2763 return (value, translate(self.error_message)) +2764 elif self.extension and not self.extension.match(string[dot + 1:]): +2765 return (value, translate(self.error_message)) +2766 else: +2767 return (value, None) +
    2768 +2769 +
    2770 -class IS_IPV4(Validator): +
    2771 """ +2772 Checks if field's value is an IP version 4 address in decimal form. Can +2773 be set to force addresses from certain range. +2774 +2775 IPv4 regex taken from: http://regexlib.com/REDetails.aspx?regexp_id=1411 +2776 +2777 Arguments: +2778 +2779 minip: lowest allowed address; accepts: +2780 str, eg. 192.168.0.1 +2781 list or tuple of octets, eg. [192, 168, 0, 1] +2782 maxip: highest allowed address; same as above +2783 invert: True to allow addresses only from outside of given range; note +2784 that range boundaries are not matched this way +2785 is_localhost: localhost address treatment: +2786 None (default): indifferent +2787 True (enforce): query address must match localhost address +2788 (127.0.0.1) +2789 False (forbid): query address must not match localhost +2790 address +2791 is_private: same as above, except that query address is checked against +2792 two address ranges: 172.16.0.0 - 172.31.255.255 and +2793 192.168.0.0 - 192.168.255.255 +2794 is_automatic: same as above, except that query address is checked against +2795 one address range: 169.254.0.0 - 169.254.255.255 +2796 +2797 Minip and maxip may also be lists or tuples of addresses in all above +2798 forms (str, int, list / tuple), allowing setup of multiple address ranges: +2799 +2800 minip = (minip1, minip2, ... minipN) +2801 | | | +2802 | | | +2803 maxip = (maxip1, maxip2, ... maxipN) +2804 +2805 Longer iterable will be truncated to match length of shorter one. +2806 +2807 Examples:: +2808 +2809 #Check for valid IPv4 address: +2810 INPUT(_type='text', _name='name', requires=IS_IPV4()) +2811 +2812 #Check for valid IPv4 address belonging to specific range: +2813 INPUT(_type='text', _name='name', +2814 requires=IS_IPV4(minip='100.200.0.0', maxip='100.200.255.255')) +2815 +2816 #Check for valid IPv4 address belonging to either 100.110.0.0 - +2817 #100.110.255.255 or 200.50.0.0 - 200.50.0.255 address range: +2818 INPUT(_type='text', _name='name', +2819 requires=IS_IPV4(minip=('100.110.0.0', '200.50.0.0'), +2820 maxip=('100.110.255.255', '200.50.0.255'))) +2821 +2822 #Check for valid IPv4 address belonging to private address space: +2823 INPUT(_type='text', _name='name', requires=IS_IPV4(is_private=True)) +2824 +2825 #Check for valid IPv4 address that is not a localhost address: +2826 INPUT(_type='text', _name='name', requires=IS_IPV4(is_localhost=False)) +2827 +2828 >>> IS_IPV4()('1.2.3.4') +2829 ('1.2.3.4', None) +2830 >>> IS_IPV4()('255.255.255.255') +2831 ('255.255.255.255', None) +2832 >>> IS_IPV4()('1.2.3.4 ') +2833 ('1.2.3.4 ', 'enter valid IPv4 address') +2834 >>> IS_IPV4()('1.2.3.4.5') +2835 ('1.2.3.4.5', 'enter valid IPv4 address') +2836 >>> IS_IPV4()('123.123') +2837 ('123.123', 'enter valid IPv4 address') +2838 >>> IS_IPV4()('1111.2.3.4') +2839 ('1111.2.3.4', 'enter valid IPv4 address') +2840 >>> IS_IPV4()('0111.2.3.4') +2841 ('0111.2.3.4', 'enter valid IPv4 address') +2842 >>> IS_IPV4()('256.2.3.4') +2843 ('256.2.3.4', 'enter valid IPv4 address') +2844 >>> IS_IPV4()('300.2.3.4') +2845 ('300.2.3.4', 'enter valid IPv4 address') +2846 >>> IS_IPV4(minip='1.2.3.4', maxip='1.2.3.4')('1.2.3.4') +2847 ('1.2.3.4', None) +2848 >>> IS_IPV4(minip='1.2.3.5', maxip='1.2.3.9', error_message='bad ip')('1.2.3.4') +2849 ('1.2.3.4', 'bad ip') +2850 >>> IS_IPV4(maxip='1.2.3.4', invert=True)('127.0.0.1') +2851 ('127.0.0.1', None) +2852 >>> IS_IPV4(maxip='1.2.3.4', invert=True)('1.2.3.4') +2853 ('1.2.3.4', 'enter valid IPv4 address') +2854 >>> IS_IPV4(is_localhost=True)('127.0.0.1') +2855 ('127.0.0.1', None) +2856 >>> IS_IPV4(is_localhost=True)('1.2.3.4') +2857 ('1.2.3.4', 'enter valid IPv4 address') +2858 >>> IS_IPV4(is_localhost=False)('127.0.0.1') +2859 ('127.0.0.1', 'enter valid IPv4 address') +2860 >>> IS_IPV4(maxip='100.0.0.0', is_localhost=True)('127.0.0.1') +2861 ('127.0.0.1', 'enter valid IPv4 address') +2862 """ +2863 +2864 regex = re.compile( +2865 '^(([1-9]?\d|1\d\d|2[0-4]\d|25[0-5])\.){3}([1-9]?\d|1\d\d|2[0-4]\d|25[0-5])$') +2866 numbers = (16777216, 65536, 256, 1) +2867 localhost = 2130706433 +2868 private = ((2886729728L, 2886795263L), (3232235520L, 3232301055L)) +2869 automatic = (2851995648L, 2852061183L) +2870 +
    2871 - def __init__( +2872 self, +2873 minip='0.0.0.0', +2874 maxip='255.255.255.255', +2875 invert=False, +2876 is_localhost=None, +2877 is_private=None, +2878 is_automatic=None, +2879 error_message='enter valid IPv4 address'): +
    2880 for n, value in enumerate((minip, maxip)): +2881 temp = [] +2882 if isinstance(value, str): +2883 temp.append(value.split('.')) +2884 elif isinstance(value, (list, tuple)): +2885 if len(value) == len(filter(lambda item: isinstance(item, int), value)) == 4: +2886 temp.append(value) +2887 else: +2888 for item in value: +2889 if isinstance(item, str): +2890 temp.append(item.split('.')) +2891 elif isinstance(item, (list, tuple)): +2892 temp.append(item) +2893 numbers = [] +2894 for item in temp: +2895 number = 0 +2896 for i, j in zip(self.numbers, item): +2897 number += i * int(j) +2898 numbers.append(number) +2899 if n == 0: +2900 self.minip = numbers +2901 else: +2902 self.maxip = numbers +2903 self.invert = invert +2904 self.is_localhost = is_localhost +2905 self.is_private = is_private +2906 self.is_automatic = is_automatic +2907 self.error_message = error_message +
    2908 +
    2909 - def __call__(self, value): +
    2910 if self.regex.match(value): +2911 number = 0 +2912 for i, j in zip(self.numbers, value.split('.')): +2913 number += i * int(j) +2914 ok = False +2915 for bottom, top in zip(self.minip, self.maxip): +2916 if self.invert != (bottom <= number <= top): +2917 ok = True +2918 if not (self.is_localhost == None or self.is_localhost == \ +2919 (number == self.localhost)): +2920 ok = False +2921 if not (self.is_private == None or self.is_private == \ +2922 (sum([number[0] <= number <= number[1] for number in self.private]) > 0)): +2923 ok = False +2924 if not (self.is_automatic == None or self.is_automatic == \ +2925 (self.automatic[0] <= number <= self.automatic[1])): +2926 ok = False +2927 if ok: +2928 return (value, None) +2929 return (value, translate(self.error_message)) +
    2930 +2931 if __name__ == '__main__': +2932 import doctest +2933 doctest.testmod() +2934 +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.validators.CLEANUP-class.html Index: applications/examples/static/epydoc/web2py.gluon.validators.CLEANUP-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.validators.CLEANUP-class.html +++ applications/examples/static/epydoc/web2py.gluon.validators.CLEANUP-class.html @@ -0,0 +1,273 @@ + + + + + web2py.gluon.validators.CLEANUP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module validators :: + Class CLEANUP + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class CLEANUP

    source code

    +
    +object --+    
    +         |    
    + Validator --+
    +             |
    +            CLEANUP
    +
    + +
    +example: +
    +   INPUT(_type='text', _name='name', requires=CLEANUP())
    +
    + removes special characters on validation

    + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + regex='[^\t\n\r -~]')
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    __call__(self, + value) + source code + +
    + +
    +

    Inherited from Validator: + formatter +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + regex='[^\t\n\r -~]') +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.validators.CRYPT-class.html Index: applications/examples/static/epydoc/web2py.gluon.validators.CRYPT-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.validators.CRYPT-class.html +++ applications/examples/static/epydoc/web2py.gluon.validators.CRYPT-class.html @@ -0,0 +1,275 @@ + + + + + web2py.gluon.validators.CRYPT + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module validators :: + Class CRYPT + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class CRYPT

    source code

    +
    +object --+
    +         |
    +        CRYPT
    +
    + +
    +example: +
    +   INPUT(_type='text', _name='name', requires=CRYPT())
    +
    +

    encodes the value on validation with a digest.

    + If no arguments are provided CRYPT uses the MD5 algorithm. If the key + argument is provided the HMAC+MD5 algorithm is used. If the digest_alg is + specified this is used to replace the MD5 with, for example, SHA512. The + digest_alg can be the name of a hashlib algorithm as a string or the + algorithm itself.

    + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + key=1, + digest_alg='md5')
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    __call__(self, + value) + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + key=1, + digest_alg='md5') +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.validators.IS_ALPHANUMERIC-class.html Index: applications/examples/static/epydoc/web2py.gluon.validators.IS_ALPHANUMERIC-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.validators.IS_ALPHANUMERIC-class.html +++ applications/examples/static/epydoc/web2py.gluon.validators.IS_ALPHANUMERIC-class.html @@ -0,0 +1,265 @@ + + + + + web2py.gluon.validators.IS_ALPHANUMERIC + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module validators :: + Class IS_ALPHANUMERIC + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class IS_ALPHANUMERIC

    source code

    +
    +object --+        
    +         |        
    + Validator --+    
    +             |    
    +      IS_MATCH --+
    +                 |
    +                IS_ALPHANUMERIC
    +
    + +
    +example: +
    +   INPUT(_type='text', _name='name', requires=IS_ALPHANUMERIC())
    +
    +   >>> IS_ALPHANUMERIC()('1')
    +   ('1', None)
    +   >>> IS_ALPHANUMERIC()('')
    +   ('', None)
    +   >>> IS_ALPHANUMERIC()('A_a')
    +   ('A_a', None)
    +   >>> IS_ALPHANUMERIC()('!')
    +   ('!', 'enter only letters, numbers, and underscore')
    +


    + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + error_message='enter only letters, numbers, and underscore') + source code + +
    + +
    +

    Inherited from IS_MATCH: + __call__ +

    +

    Inherited from Validator: + formatter +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + error_message='enter only letters, numbers, and underscore') +
    (Constructor) +

    +
    source code  +
    + + +
    +
    Overrides: + IS_MATCH.__init__ +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.validators.IS_DATE-class.html Index: applications/examples/static/epydoc/web2py.gluon.validators.IS_DATE-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.validators.IS_DATE-class.html +++ applications/examples/static/epydoc/web2py.gluon.validators.IS_DATE-class.html @@ -0,0 +1,321 @@ + + + + + web2py.gluon.validators.IS_DATE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module validators :: + Class IS_DATE + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class IS_DATE

    source code

    +
    +object --+    
    +         |    
    + Validator --+
    +             |
    +            IS_DATE
    +
    + +
    Known Subclasses:
    +
    + IS_DATE_IN_RANGE +
    + +
    +example: +
    +   INPUT(_type='text', _name='name', requires=IS_DATE())
    +
    + date has to be in the ISO8960 format YYYY-MM-DD

    + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + format='%Y-%m-%d', + error_message='enter date as %(format)s')
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    __call__(self, + value) + source code + +
    + +
    +   + + + + + + +
    formatter(self, + value)
    + For some validators returns a formatted version (matching the + validator) of value.
    + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + format='%Y-%m-%d', + error_message='enter date as %(format)s') +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    formatter(self, + value) +

    +
    source code  +
    + + For some validators returns a formatted version (matching the + validator) of value. Otherwise just returns the value. +
    +
    Overrides: + Validator.formatter +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.validators.IS_DATETIME-class.html Index: applications/examples/static/epydoc/web2py.gluon.validators.IS_DATETIME-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.validators.IS_DATETIME-class.html +++ applications/examples/static/epydoc/web2py.gluon.validators.IS_DATETIME-class.html @@ -0,0 +1,380 @@ + + + + + web2py.gluon.validators.IS_DATETIME + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module validators :: + Class IS_DATETIME + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class IS_DATETIME

    source code

    +
    +object --+    
    +         |    
    + Validator --+
    +             |
    +            IS_DATETIME
    +
    + +
    Known Subclasses:
    +
    + IS_DATETIME_IN_RANGE +
    + +
    +example: +
    +   INPUT(_type='text', _name='name', requires=IS_DATETIME())
    +
    + datetime has to be in the ISO8960 format YYYY-MM-DD hh:mm:ss

    + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + format='%Y-%m-%d %H:%M:%S', + error_message='enter date and time as %(format)s')
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    __call__(self, + value) + source code + +
    + +
    +   + + + + + + +
    formatter(self, + value)
    + For some validators returns a formatted version (matching the + validator) of value.
    + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +   + + + + + + +
    nice(format) + source code + +
    + +
    + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + isodatetime = '%Y-%m-%d %H:%M:%S' +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + format='%Y-%m-%d %H:%M:%S', + error_message='enter date and time as %(format)s') +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    formatter(self, + value) +

    +
    source code  +
    + + For some validators returns a formatted version (matching the + validator) of value. Otherwise just returns the value. +
    +
    Overrides: + Validator.formatter +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.validators.IS_DATETIME_IN_RANGE-class.html Index: applications/examples/static/epydoc/web2py.gluon.validators.IS_DATETIME_IN_RANGE-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.validators.IS_DATETIME_IN_RANGE-class.html +++ applications/examples/static/epydoc/web2py.gluon.validators.IS_DATETIME_IN_RANGE-class.html @@ -0,0 +1,364 @@ + + + + + web2py.gluon.validators.IS_DATETIME_IN_RANGE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module validators :: + Class IS_DATETIME_IN_RANGE + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class IS_DATETIME_IN_RANGE

    source code

    +
    +object --+        
    +         |        
    + Validator --+    
    +             |    
    +   IS_DATETIME --+
    +                 |
    +                IS_DATETIME_IN_RANGE
    +
    + +
    +example: +
    +   >>> v = IS_DATETIME_IN_RANGE(                minimum=datetime.datetime(2008,1,1,12,20),                 maximum=datetime.datetime(2009,12,31,12,20),                 format="%m/%d/%Y %H:%M",error_message="oops")
    +   >>> v('03/03/2008 12:40')
    +   (datetime.datetime(2008, 3, 3, 12, 40), None)
    +
    +   >>> v('03/03/2010 10:34')
    +   (datetime.datetime(2010, 3, 3, 10, 34), 'oops')
    +
    +   >>> v(datetime.datetime(2008,3,3,0,0))
    +   (datetime.datetime(2008, 3, 3, 0, 0), None)
    +
    +   >>> v(datetime.datetime(2010,3,3,0,0))
    +   (datetime.datetime(2010, 3, 3, 0, 0), 'oops')
    +


    + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + minimum=1, + maximum=1, + format='%Y-%m-%d %H:%M:%S', + error_message=1)
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    __call__(self, + value) + source code + +
    + +
    +

    Inherited from IS_DATETIME: + formatter +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +

    Inherited from IS_DATETIME: + nice +

    +
    + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +

    Inherited from IS_DATETIME: + isodatetime +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + minimum=1, + maximum=1, + format='%Y-%m-%d %H:%M:%S', + error_message=1) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + IS_DATETIME.__init__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    __call__(self, + value) +
    (Call operator) +

    +
    source code  +
    + + +
    +
    Overrides: + IS_DATETIME.__call__ +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.validators.IS_DATE_IN_RANGE-class.html Index: applications/examples/static/epydoc/web2py.gluon.validators.IS_DATE_IN_RANGE-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.validators.IS_DATE_IN_RANGE-class.html +++ applications/examples/static/epydoc/web2py.gluon.validators.IS_DATE_IN_RANGE-class.html @@ -0,0 +1,315 @@ + + + + + web2py.gluon.validators.IS_DATE_IN_RANGE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module validators :: + Class IS_DATE_IN_RANGE + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class IS_DATE_IN_RANGE

    source code

    +
    +object --+        
    +         |        
    + Validator --+    
    +             |    
    +       IS_DATE --+
    +                 |
    +                IS_DATE_IN_RANGE
    +
    + +
    +example: +
    +   >>> v = IS_DATE_IN_RANGE(minimum=datetime.date(2008,1,1),                                  maximum=datetime.date(2009,12,31),                                  format="%m/%d/%Y",error_message="oops")
    +
    +   >>> v('03/03/2008')
    +   (datetime.date(2008, 3, 3), None)
    +
    +   >>> v('03/03/2010')
    +   (datetime.date(2010, 3, 3), 'oops')
    +
    +   >>> v(datetime.date(2008,3,3))
    +   (datetime.date(2008, 3, 3), None)
    +
    +   >>> v(datetime.date(2010,3,3))
    +   (datetime.date(2010, 3, 3), 'oops')
    +


    + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + minimum=1, + maximum=1, + format='%Y-%m-%d', + error_message=1)
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    __call__(self, + value) + source code + +
    + +
    +

    Inherited from IS_DATE: + formatter +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + minimum=1, + maximum=1, + format='%Y-%m-%d', + error_message=1) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + IS_DATE.__init__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    __call__(self, + value) +
    (Call operator) +

    +
    source code  +
    + + +
    +
    Overrides: + IS_DATE.__call__ +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.validators.IS_DECIMAL_IN_RANGE-class.html Index: applications/examples/static/epydoc/web2py.gluon.validators.IS_DECIMAL_IN_RANGE-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.validators.IS_DECIMAL_IN_RANGE-class.html +++ applications/examples/static/epydoc/web2py.gluon.validators.IS_DECIMAL_IN_RANGE-class.html @@ -0,0 +1,363 @@ + + + + + web2py.gluon.validators.IS_DECIMAL_IN_RANGE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module validators :: + Class IS_DECIMAL_IN_RANGE + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class IS_DECIMAL_IN_RANGE

    source code

    +
    +object --+    
    +         |    
    + Validator --+
    +             |
    +            IS_DECIMAL_IN_RANGE
    +
    + +
    +

    Determine that the argument is (or can be represented as) a Python + Decimal, and that it falls within the specified inclusive range. The + comparison is made with Python Decimal arithmetic.

    +

    The minimum and maximum limits can be None, meaning no lower or upper + limit, respectively.

    + example: +
    +   INPUT(_type='text', _name='name', requires=IS_DECIMAL_IN_RANGE(0, 10))
    +
    +   >>> IS_DECIMAL_IN_RANGE(1,5)('4')
    +   (Decimal('4'), None)
    +   >>> IS_DECIMAL_IN_RANGE(1,5)(4)
    +   (Decimal('4'), None)
    +   >>> IS_DECIMAL_IN_RANGE(1,5)(1)
    +   (Decimal('1'), None)
    +   >>> IS_DECIMAL_IN_RANGE(1,5)(5.25)
    +   (5.25, 'enter a number between 1 and 5')
    +   >>> IS_DECIMAL_IN_RANGE(5.25,6)(5.25)
    +   (Decimal('5.25'), None)
    +   >>> IS_DECIMAL_IN_RANGE(5.25,6)('5.25')
    +   (Decimal('5.25'), None)
    +   >>> IS_DECIMAL_IN_RANGE(1,5)(6.0)
    +   (6.0, 'enter a number between 1 and 5')
    +   >>> IS_DECIMAL_IN_RANGE(1,5)(3.5)
    +   (Decimal('3.5'), None)
    +   >>> IS_DECIMAL_IN_RANGE(1.5,5.5)(3.5)
    +   (Decimal('3.5'), None)
    +   >>> IS_DECIMAL_IN_RANGE(1.5,5.5)(6.5)
    +   (6.5, 'enter a number between 1.5 and 5.5')
    +   >>> IS_DECIMAL_IN_RANGE(1.5,None)(6.5)
    +   (Decimal('6.5'), None)
    +   >>> IS_DECIMAL_IN_RANGE(1.5,None)(0.5)
    +   (0.5, 'enter a number greater than or equal to 1.5')
    +   >>> IS_DECIMAL_IN_RANGE(None,5.5)(4.5)
    +   (Decimal('4.5'), None)
    +   >>> IS_DECIMAL_IN_RANGE(None,5.5)(6.5)
    +   (6.5, 'enter a number less than or equal to 5.5')
    +   >>> IS_DECIMAL_IN_RANGE()(6.5)
    +   (Decimal('6.5'), None)
    +   >>> IS_DECIMAL_IN_RANGE(0,99)(123.123)
    +   (123.123, 'enter a number between 0 and 99')
    +   >>> IS_DECIMAL_IN_RANGE(0,99)('123.123')
    +   ('123.123', 'enter a number between 0 and 99')
    +   >>> IS_DECIMAL_IN_RANGE(0,99)('12.34')
    +   (Decimal('12.34'), None)
    +   >>> IS_DECIMAL_IN_RANGE()('abc')
    +   ('abc', 'enter a decimal number')
    +


    + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + minimum=1, + maximum=1, + error_message=1, + dot='.')
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    __call__(self, + value) + source code + +
    + +
    +   + + + + + + +
    formatter(self, + value)
    + For some validators returns a formatted version (matching the + validator) of value.
    + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + minimum=1, + maximum=1, + error_message=1, + dot='.') +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    formatter(self, + value) +

    +
    source code  +
    + + For some validators returns a formatted version (matching the + validator) of value. Otherwise just returns the value. +
    +
    Overrides: + Validator.formatter +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.validators.IS_EMAIL-class.html Index: applications/examples/static/epydoc/web2py.gluon.validators.IS_EMAIL-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.validators.IS_EMAIL-class.html +++ applications/examples/static/epydoc/web2py.gluon.validators.IS_EMAIL-class.html @@ -0,0 +1,452 @@ + + + + + web2py.gluon.validators.IS_EMAIL + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module validators :: + Class IS_EMAIL + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class IS_EMAIL

    source code

    +
    +object --+    
    +         |    
    + Validator --+
    +             |
    +            IS_EMAIL
    +
    + +
    +
    +
    +Checks if field's value is a valid email address. Can be set to disallow
    +or force addresses from certain domain(s).
    +
    +Email regex adapted from
    +http://haacked.com/archive/2007/08/21/i-knew-how-to-validate-an-email-address-until-i.aspx,
    +generally following the RFCs, except that we disallow quoted strings
    +and permit underscores and leading numerics in subdomain labels
    +
    +Arguments:
    +
    +- banned: regex text for disallowed address domains
    +- forced: regex text for required address domains
    +
    +Both arguments can also be custom objects with a match(value) method.
    +
    +Examples::
    +
    +    #Check for valid email address:
    +    INPUT(_type='text', _name='name',
    +        requires=IS_EMAIL())
    +
    +    #Check for valid email address that can't be from a .com domain:
    +    INPUT(_type='text', _name='name',
    +        requires=IS_EMAIL(banned='^.*\.com(|\..*)$'))
    +
    +    #Check for valid email address that must be from a .edu domain:
    +    INPUT(_type='text', _name='name',
    +        requires=IS_EMAIL(forced='^.*\.edu(|\..*)$'))
    +
    +    >>> IS_EMAIL()('a@b.com')
    +    ('a@b.com', None)
    +    >>> IS_EMAIL()('abc@def.com')
    +    ('abc@def.com', None)
    +    >>> IS_EMAIL()('abc@3def.com')
    +    ('abc@3def.com', None)
    +    >>> IS_EMAIL()('abc@def.us')
    +    ('abc@def.us', None)
    +    >>> IS_EMAIL()('abc@d_-f.us')
    +    ('abc@d_-f.us', None)
    +    >>> IS_EMAIL()('@def.com')           # missing name
    +    ('@def.com', 'enter a valid email address')
    +    >>> IS_EMAIL()('"abc@def".com')      # quoted name
    +    ('"abc@def".com', 'enter a valid email address')
    +    >>> IS_EMAIL()('abc+def.com')        # no @
    +    ('abc+def.com', 'enter a valid email address')
    +    >>> IS_EMAIL()('abc@def.x')          # one-char TLD
    +    ('abc@def.x', 'enter a valid email address')
    +    >>> IS_EMAIL()('abc@def.12')         # numeric TLD
    +    ('abc@def.12', 'enter a valid email address')
    +    >>> IS_EMAIL()('abc@def..com')       # double-dot in domain
    +    ('abc@def..com', 'enter a valid email address')
    +    >>> IS_EMAIL()('abc@.def.com')       # dot starts domain
    +    ('abc@.def.com', 'enter a valid email address')
    +    >>> IS_EMAIL()('abc@def.c_m')        # underscore in TLD
    +    ('abc@def.c_m', 'enter a valid email address')
    +    >>> IS_EMAIL()('NotAnEmail')         # missing @
    +    ('NotAnEmail', 'enter a valid email address')
    +    >>> IS_EMAIL()('abc@NotAnEmail')     # missing TLD
    +    ('abc@NotAnEmail', 'enter a valid email address')
    +    >>> IS_EMAIL()('customer/department@example.com')
    +    ('customer/department@example.com', None)
    +    >>> IS_EMAIL()('$A12345@example.com')
    +    ('$A12345@example.com', None)
    +    >>> IS_EMAIL()('!def!xyz%abc@example.com')
    +    ('!def!xyz%abc@example.com', None)
    +    >>> IS_EMAIL()('_Yosemite.Sam@example.com')
    +    ('_Yosemite.Sam@example.com', None)
    +    >>> IS_EMAIL()('~@example.com')
    +    ('~@example.com', None)
    +    >>> IS_EMAIL()('.wooly@example.com')       # dot starts name
    +    ('.wooly@example.com', 'enter a valid email address')
    +    >>> IS_EMAIL()('wo..oly@example.com')      # adjacent dots in name
    +    ('wo..oly@example.com', 'enter a valid email address')
    +    >>> IS_EMAIL()('pootietang.@example.com')  # dot ends name
    +    ('pootietang.@example.com', 'enter a valid email address')
    +    >>> IS_EMAIL()('.@example.com')            # name is bare dot
    +    ('.@example.com', 'enter a valid email address')
    +    >>> IS_EMAIL()('Ima.Fool@example.com')
    +    ('Ima.Fool@example.com', None)
    +    >>> IS_EMAIL()('Ima Fool@example.com')     # space in name
    +    ('Ima Fool@example.com', 'enter a valid email address')
    +    >>> IS_EMAIL()('localguy@localhost')       # localhost as domain
    +    ('localguy@localhost', None)
    +
    +


    + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + banned=1, + forced=1, + error_message='enter a valid email address')
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    __call__(self, + value) + source code + +
    + +
    +

    Inherited from Validator: + formatter +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + regex = re.compile(r'(?ix)^(?!\.)([-a-z0-9!#\$%&\'\*\+/=\?\^_`... +
    +   + + regex_proposed_but_failed = re.compile(r'(?ix)^([\w!#\$%&\'\*\... +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + banned=1, + forced=1, + error_message='enter a valid email address') +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + +
    + + + + + +
    Class Variable Details[hide private]
    +
    + +
    + +
    +

    regex

    + +
    +
    +
    +
    Value:
    +
    +re.compile(r'(?ix)^(?!\.)([-a-z0-9!#\$%&\'\*\+/=\?\^_`\{\|\}~]|(?<!\.)\
    +\.)+(?<!\.)@(localhost|([a-z0-9]([-\w]*[a-z0-9])?\.)+[a-z]{2,})$')
    +
    +
    +
    +
    +
    + +
    + +
    +

    regex_proposed_but_failed

    + +
    +
    +
    +
    Value:
    +
    +re.compile(r'(?ix)^([\w!#\$%&\'\*\+-/=\?\^`\{\|\}~]+\.)*[\w!#\$%&\'\*\\
    ++-/=\?\^`\{\|\}~]+@((((([a-z0-9]{1}[a-z0-9-]{,62}[a-z0-9]{1})|[a-z])\.\
    +)+[a-z]{2,6})|(\d{1,3}\.){3}\d{1,3}(:\d{1,5})?)$')
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.validators.IS_EMPTY_OR-class.html Index: applications/examples/static/epydoc/web2py.gluon.validators.IS_EMPTY_OR-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.validators.IS_EMPTY_OR-class.html +++ applications/examples/static/epydoc/web2py.gluon.validators.IS_EMPTY_OR-class.html @@ -0,0 +1,360 @@ + + + + + web2py.gluon.validators.IS_EMPTY_OR + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module validators :: + Class IS_EMPTY_OR + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class IS_EMPTY_OR

    source code

    +
    +object --+    
    +         |    
    + Validator --+
    +             |
    +            IS_EMPTY_OR
    +
    + +
    +dummy class for testing IS_EMPTY_OR +
    +>>> IS_EMPTY_OR(IS_EMAIL())('abc@def.com')
    +('abc@def.com', None)
    +>>> IS_EMPTY_OR(IS_EMAIL())('   ')
    +(None, None)
    +>>> IS_EMPTY_OR(IS_EMAIL(), null='abc')('   ')
    +('abc', None)
    +>>> IS_EMPTY_OR(IS_EMAIL(), null='abc', empty_regex='def')('def')
    +('abc', None)
    +>>> IS_EMPTY_OR(IS_EMAIL())('abc')
    +('abc', 'enter a valid email address')
    +>>> IS_EMPTY_OR(IS_EMAIL())(' abc ')
    +('abc', 'enter a valid email address')


    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + other, + null=1, + empty_regex=1)
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    _options(self) + source code + +
    + +
    +   + + + + + + +
    set_self_id(self, + id) + source code + +
    + +
    +   + + + + + + +
    __call__(self, + value) + source code + +
    + +
    +   + + + + + + +
    formatter(self, + value)
    + For some validators returns a formatted version (matching the + validator) of value.
    + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + other, + null=1, + empty_regex=1) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    formatter(self, + value) +

    +
    source code  +
    + + For some validators returns a formatted version (matching the + validator) of value. Otherwise just returns the value. +
    +
    Overrides: + Validator.formatter +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.validators.IS_EQUAL_TO-class.html Index: applications/examples/static/epydoc/web2py.gluon.validators.IS_EQUAL_TO-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.validators.IS_EQUAL_TO-class.html +++ applications/examples/static/epydoc/web2py.gluon.validators.IS_EQUAL_TO-class.html @@ -0,0 +1,283 @@ + + + + + web2py.gluon.validators.IS_EQUAL_TO + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module validators :: + Class IS_EQUAL_TO + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class IS_EQUAL_TO

    source code

    +
    +object --+    
    +         |    
    + Validator --+
    +             |
    +            IS_EQUAL_TO
    +
    + +
    +example: +
    +   INPUT(_type='text', _name='password')
    +   INPUT(_type='text', _name='password2',
    +         requires=IS_EQUAL_TO(request.vars.password))
    +
    + the argument of IS_EQUAL_TO is a string +
    +>>> IS_EQUAL_TO('aaa')('aaa')
    +('aaa', None)
    +
    +>>> IS_EQUAL_TO('aaa')('aab')
    +('aab', 'no match')


    + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + expression, + error_message='no match')
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    __call__(self, + value) + source code + +
    + +
    +

    Inherited from Validator: + formatter +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + expression, + error_message='no match') +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.validators.IS_EXPR-class.html Index: applications/examples/static/epydoc/web2py.gluon.validators.IS_EXPR-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.validators.IS_EXPR-class.html +++ applications/examples/static/epydoc/web2py.gluon.validators.IS_EXPR-class.html @@ -0,0 +1,283 @@ + + + + + web2py.gluon.validators.IS_EXPR + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module validators :: + Class IS_EXPR + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class IS_EXPR

    source code

    +
    +object --+    
    +         |    
    + Validator --+
    +             |
    +            IS_EXPR
    +
    + +
    +example: +
    +   INPUT(_type='text', _name='name',
    +       requires=IS_EXPR('5 < int(value) < 10'))
    +
    + the argument of IS_EXPR must be python condition: +
    +   >>> IS_EXPR('int(value) < 2')('1')
    +   ('1', None)
    +
    +   >>> IS_EXPR('int(value) < 2')('2')
    +   ('2', 'invalid expression')
    +


    + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + expression, + error_message='invalid expression')
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    __call__(self, + value) + source code + +
    + +
    +

    Inherited from Validator: + formatter +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + expression, + error_message='invalid expression') +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.validators.IS_FLOAT_IN_RANGE-class.html Index: applications/examples/static/epydoc/web2py.gluon.validators.IS_FLOAT_IN_RANGE-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.validators.IS_FLOAT_IN_RANGE-class.html +++ applications/examples/static/epydoc/web2py.gluon.validators.IS_FLOAT_IN_RANGE-class.html @@ -0,0 +1,349 @@ + + + + + web2py.gluon.validators.IS_FLOAT_IN_RANGE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module validators :: + Class IS_FLOAT_IN_RANGE + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class IS_FLOAT_IN_RANGE

    source code

    +
    +object --+    
    +         |    
    + Validator --+
    +             |
    +            IS_FLOAT_IN_RANGE
    +
    + +
    +

    Determine that the argument is (or can be represented as) a float, and + that it falls within the specified inclusive range. The comparison is + made with native arithmetic.

    +

    The minimum and maximum limits can be None, meaning no lower or upper + limit, respectively.

    + example: +
    +   INPUT(_type='text', _name='name', requires=IS_FLOAT_IN_RANGE(0, 10))
    +
    +   >>> IS_FLOAT_IN_RANGE(1,5)('4')
    +   (4.0, None)
    +   >>> IS_FLOAT_IN_RANGE(1,5)(4)
    +   (4.0, None)
    +   >>> IS_FLOAT_IN_RANGE(1,5)(1)
    +   (1.0, None)
    +   >>> IS_FLOAT_IN_RANGE(1,5)(5.25)
    +   (5.25, 'enter a number between 1 and 5')
    +   >>> IS_FLOAT_IN_RANGE(1,5)(6.0)
    +   (6.0, 'enter a number between 1 and 5')
    +   >>> IS_FLOAT_IN_RANGE(1,5)(3.5)
    +   (3.5, None)
    +   >>> IS_FLOAT_IN_RANGE(1,None)(3.5)
    +   (3.5, None)
    +   >>> IS_FLOAT_IN_RANGE(None,5)(3.5)
    +   (3.5, None)
    +   >>> IS_FLOAT_IN_RANGE(1,None)(0.5)
    +   (0.5, 'enter a number greater than or equal to 1')
    +   >>> IS_FLOAT_IN_RANGE(None,5)(6.5)
    +   (6.5, 'enter a number less than or equal to 5')
    +   >>> IS_FLOAT_IN_RANGE()(6.5)
    +   (6.5, None)
    +   >>> IS_FLOAT_IN_RANGE()('abc')
    +   ('abc', 'enter a number')
    +


    + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + minimum=1, + maximum=1, + error_message=1, + dot='.')
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    __call__(self, + value) + source code + +
    + +
    +   + + + + + + +
    formatter(self, + value)
    + For some validators returns a formatted version (matching the + validator) of value.
    + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + minimum=1, + maximum=1, + error_message=1, + dot='.') +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    + +
    + +
    + + +
    +

    formatter(self, + value) +

    +
    source code  +
    + + For some validators returns a formatted version (matching the + validator) of value. Otherwise just returns the value. +
    +
    Overrides: + Validator.formatter +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.validators.IS_GENERIC_URL-class.html Index: applications/examples/static/epydoc/web2py.gluon.validators.IS_GENERIC_URL-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.validators.IS_GENERIC_URL-class.html +++ applications/examples/static/epydoc/web2py.gluon.validators.IS_GENERIC_URL-class.html @@ -0,0 +1,340 @@ + + + + + web2py.gluon.validators.IS_GENERIC_URL + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module validators :: + Class IS_GENERIC_URL + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class IS_GENERIC_URL

    source code

    +
    +object --+    
    +         |    
    + Validator --+
    +             |
    +            IS_GENERIC_URL
    +
    + +
    +
    +
    +Rejects a URL string if any of the following is true:
    +   * The string is empty or None
    +   * The string uses characters that are not allowed in a URL
    +   * The URL scheme specified (if one is specified) is not valid
    +
    +Based on RFC 2396: http://www.faqs.org/rfcs/rfc2396.html
    +
    +This function only checks the URL's syntax. It does not check that the URL
    +points to a real document, for example, or that it otherwise makes sense
    +semantically. This function does automatically prepend 'http://' in front
    +of a URL if and only if that's necessary to successfully parse the URL.
    +Please note that a scheme will be prepended only for rare cases
    +(e.g. 'google.ca:80')
    +
    +The list of allowed schemes is customizable with the allowed_schemes
    +parameter. If you exclude None from the list, then abbreviated URLs
    +(lacking a scheme such as 'http') will be rejected.
    +
    +The default prepended scheme is customizable with the prepend_scheme
    +parameter. If you set prepend_scheme to None then prepending will be
    +disabled. URLs that require prepending to parse will still be accepted,
    +but the return value will not be modified.
    +
    +@author: Jonathan Benn
    +
    +>>> IS_GENERIC_URL()('http://user@abc.com')
    +('http://user@abc.com', None)
    +
    +


    + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + error_message='enter a valid URL', + allowed_schemes=1, + prepend_scheme=1)
    + :param error_message: a string, the error message to give the end user + if the URL does not validate +:param allowed_schemes: a list containing strings or None.
    + source code + +
    + +
    +   + + + + + + +
    __call__(self, + value)
    + :param value: a string, the URL to validate...
    + source code + +
    + +
    +

    Inherited from Validator: + formatter +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + error_message='enter a valid URL', + allowed_schemes=1, + prepend_scheme=1) +
    (Constructor) +

    +
    source code  +
    + +
    +
    +:param error_message: a string, the error message to give the end user
    +    if the URL does not validate
    +:param allowed_schemes: a list containing strings or None. Each element
    +    is a scheme the inputed URL is allowed to use
    +:param prepend_scheme: a string, this scheme is prepended if it's
    +    necessary to make the URL valid
    +
    +
    +
    +
    Overrides: + object.__init__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    __call__(self, + value) +
    (Call operator) +

    +
    source code  +
    + +
    +
    +:param value: a string, the URL to validate
    +:returns: a tuple, where tuple[0] is the inputed value (possible
    +    prepended with prepend_scheme), and tuple[1] is either
    +    None (success!) or the string error_message
    +
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.validators.IS_HTTP_URL-class.html Index: applications/examples/static/epydoc/web2py.gluon.validators.IS_HTTP_URL-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.validators.IS_HTTP_URL-class.html +++ applications/examples/static/epydoc/web2py.gluon.validators.IS_HTTP_URL-class.html @@ -0,0 +1,352 @@ + + + + + web2py.gluon.validators.IS_HTTP_URL + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module validators :: + Class IS_HTTP_URL + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class IS_HTTP_URL

    source code

    +
    +object --+    
    +         |    
    + Validator --+
    +             |
    +            IS_HTTP_URL
    +
    + +
    +
    +
    +Rejects a URL string if any of the following is true:
    +   * The string is empty or None
    +   * The string uses characters that are not allowed in a URL
    +   * The string breaks any of the HTTP syntactic rules
    +   * The URL scheme specified (if one is specified) is not 'http' or 'https'
    +   * The top-level domain (if a host name is specified) does not exist
    +
    +Based on RFC 2616: http://www.faqs.org/rfcs/rfc2616.html
    +
    +This function only checks the URL's syntax. It does not check that the URL
    +points to a real document, for example, or that it otherwise makes sense
    +semantically. This function does automatically prepend 'http://' in front
    +of a URL in the case of an abbreviated URL (e.g. 'google.ca').
    +
    +The list of allowed schemes is customizable with the allowed_schemes
    +parameter. If you exclude None from the list, then abbreviated URLs
    +(lacking a scheme such as 'http') will be rejected.
    +
    +The default prepended scheme is customizable with the prepend_scheme
    +parameter. If you set prepend_scheme to None then prepending will be
    +disabled. URLs that require prepending to parse will still be accepted,
    +but the return value will not be modified.
    +
    +@author: Jonathan Benn
    +
    +>>> IS_HTTP_URL()('http://1.2.3.4')
    +('http://1.2.3.4', None)
    +>>> IS_HTTP_URL()('http://abc.com')
    +('http://abc.com', None)
    +>>> IS_HTTP_URL()('https://abc.com')
    +('https://abc.com', None)
    +>>> IS_HTTP_URL()('httpx://abc.com')
    +('httpx://abc.com', 'enter a valid URL')
    +>>> IS_HTTP_URL()('http://abc.com:80')
    +('http://abc.com:80', None)
    +>>> IS_HTTP_URL()('http://user@abc.com')
    +('http://user@abc.com', None)
    +>>> IS_HTTP_URL()('http://user@1.2.3.4')
    +('http://user@1.2.3.4', None)
    +
    +


    + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + error_message='enter a valid URL', + allowed_schemes=1, + prepend_scheme='http')
    + :param error_message: a string, the error message to give the end user + if the URL does not validate +:param allowed_schemes: a list containing strings or None.
    + source code + +
    + +
    +   + + + + + + +
    __call__(self, + value)
    + :param value: a string, the URL to validate...
    + source code + +
    + +
    +

    Inherited from Validator: + formatter +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + error_message='enter a valid URL', + allowed_schemes=1, + prepend_scheme='http') +
    (Constructor) +

    +
    source code  +
    + +
    +
    +:param error_message: a string, the error message to give the end user
    +    if the URL does not validate
    +:param allowed_schemes: a list containing strings or None. Each element
    +    is a scheme the inputed URL is allowed to use
    +:param prepend_scheme: a string, this scheme is prepended if it's
    +    necessary to make the URL valid
    +
    +
    +
    +
    Overrides: + object.__init__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    __call__(self, + value) +
    (Call operator) +

    +
    source code  +
    + +
    +
    +:param value: a string, the URL to validate
    +:returns: a tuple, where tuple[0] is the inputed value
    +    (possible prepended with prepend_scheme), and tuple[1] is either
    +    None (success!) or the string error_message
    +
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.validators.IS_IMAGE-class.html Index: applications/examples/static/epydoc/web2py.gluon.validators.IS_IMAGE-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.validators.IS_IMAGE-class.html +++ applications/examples/static/epydoc/web2py.gluon.validators.IS_IMAGE-class.html @@ -0,0 +1,369 @@ + + + + + web2py.gluon.validators.IS_IMAGE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module validators :: + Class IS_IMAGE + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class IS_IMAGE

    source code

    +
    +object --+    
    +         |    
    + Validator --+
    +             |
    +            IS_IMAGE
    +
    + +
    +

    Checks if file uploaded through file input was saved in one of + selected image formats and has dimensions (width and height) within given + boundaries.

    +

    Does *not* check for maximum file size (use IS_LENGTH for that). + Returns validation failure if no data was uploaded.

    +

    Supported file formats: BMP, GIF, JPEG, PNG.

    +

    Code parts taken from + http://mail.python.org/pipermail/python-list/2007-June/617126.html

    +

    Arguments:

    +

    extensions: iterable containing allowed *lowercase* image file + extensions ('jpg' extension of uploaded file counts as 'jpeg') maxsize: + iterable containing maximum width and height of the image minsize: + iterable containing minimum width and height of the image

    +

    Use (-1, -1) as minsize to pass image size check.

    + Examples: +
    +   #Check if uploaded file is in any of supported image formats:
    +   INPUT(_type='file', _name='name', requires=IS_IMAGE())
    +
    +   #Check if uploaded file is either JPEG or PNG:
    +   INPUT(_type='file', _name='name',
    +       requires=IS_IMAGE(extensions=('jpeg', 'png')))
    +
    +   #Check if uploaded file is PNG with maximum size of 200x200 pixels:
    +   INPUT(_type='file', _name='name',
    +       requires=IS_IMAGE(extensions=('png'), maxsize=(200, 200)))
    +


    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + extensions=('bmp', 'gif', 'jpeg', 'png'), + maxsize=(10000, 10000), + minsize=(0, 0), + error_message='invalid image')
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    __call__(self, + value) + source code + +
    + +
    +   + + + + + + +
    __bmp(self, + stream) + source code + +
    + +
    +   + + + + + + +
    __gif(self, + stream) + source code + +
    + +
    +   + + + + + + +
    __jpeg(self, + stream) + source code + +
    + +
    +   + + + + + + +
    __png(self, + stream) + source code + +
    + +
    +

    Inherited from Validator: + formatter +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + extensions=('bmp', 'gif', 'jpeg', 'png'), + maxsize=(10000, 10000), + minsize=(0, 0), + error_message='invalid image') +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.validators.IS_INT_IN_RANGE-class.html Index: applications/examples/static/epydoc/web2py.gluon.validators.IS_INT_IN_RANGE-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.validators.IS_INT_IN_RANGE-class.html +++ applications/examples/static/epydoc/web2py.gluon.validators.IS_INT_IN_RANGE-class.html @@ -0,0 +1,306 @@ + + + + + web2py.gluon.validators.IS_INT_IN_RANGE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module validators :: + Class IS_INT_IN_RANGE + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class IS_INT_IN_RANGE

    source code

    +
    +object --+    
    +         |    
    + Validator --+
    +             |
    +            IS_INT_IN_RANGE
    +
    + +
    +

    Determine that the argument is (or can be represented as) an int, and + that it falls within the specified range. The range is interpreted in the + Pythonic way, so the test is: min <= value < max.

    +

    The minimum and maximum limits can be None, meaning no lower or upper + limit, respectively.

    + example: +
    +   INPUT(_type='text', _name='name', requires=IS_INT_IN_RANGE(0, 10))
    +
    +   >>> IS_INT_IN_RANGE(1,5)('4')
    +   (4, None)
    +   >>> IS_INT_IN_RANGE(1,5)(4)
    +   (4, None)
    +   >>> IS_INT_IN_RANGE(1,5)(1)
    +   (1, None)
    +   >>> IS_INT_IN_RANGE(1,5)(5)
    +   (5, 'enter an integer between 1 and 4')
    +   >>> IS_INT_IN_RANGE(1,5)(5)
    +   (5, 'enter an integer between 1 and 4')
    +   >>> IS_INT_IN_RANGE(1,5)(3.5)
    +   (3, 'enter an integer between 1 and 4')
    +   >>> IS_INT_IN_RANGE(None,5)('4')
    +   (4, None)
    +   >>> IS_INT_IN_RANGE(None,5)('6')
    +   (6, 'enter an integer less than or equal to 4')
    +   >>> IS_INT_IN_RANGE(1,None)('4')
    +   (4, None)
    +   >>> IS_INT_IN_RANGE(1,None)('0')
    +   (0, 'enter an integer greater than or equal to 1')
    +   >>> IS_INT_IN_RANGE()(6)
    +   (6, None)
    +   >>> IS_INT_IN_RANGE()('abc')
    +   ('abc', 'enter an integer')
    +


    + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + minimum=1, + maximum=1, + error_message=1)
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    __call__(self, + value) + source code + +
    + +
    +

    Inherited from Validator: + formatter +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + minimum=1, + maximum=1, + error_message=1) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.validators.IS_IN_DB-class.html Index: applications/examples/static/epydoc/web2py.gluon.validators.IS_IN_DB-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.validators.IS_IN_DB-class.html +++ applications/examples/static/epydoc/web2py.gluon.validators.IS_IN_DB-class.html @@ -0,0 +1,344 @@ + + + + + web2py.gluon.validators.IS_IN_DB + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module validators :: + Class IS_IN_DB + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class IS_IN_DB

    source code

    +
    +object --+    
    +         |    
    + Validator --+
    +             |
    +            IS_IN_DB
    +
    + +
    +example: +
    +   INPUT(_type='text', _name='name',
    +         requires=IS_IN_DB(db, db.mytable.myfield, zero=''))
    +
    + used for reference fields, rendered as a dropbox

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + dbset, + field, + label=1, + error_message='value not in database', + orderby=1, + groupby=1, + cache=1, + multiple=True, + zero='', + sort=True, + _and=1)
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    set_self_id(self, + id) + source code + +
    + +
    +   + + + + + + +
    build_set(self) + source code + +
    + +
    +   + + + + + + +
    options(self, + zero=True) + source code + +
    + +
    +   + + + + + + +
    __call__(self, + value) + source code + +
    + +
    +

    Inherited from Validator: + formatter +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + dbset, + field, + label=1, + error_message='value not in database', + orderby=1, + groupby=1, + cache=1, + multiple=True, + zero='', + sort=True, + _and=1) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.validators.IS_IN_SET-class.html Index: applications/examples/static/epydoc/web2py.gluon.validators.IS_IN_SET-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.validators.IS_IN_SET-class.html +++ applications/examples/static/epydoc/web2py.gluon.validators.IS_IN_SET-class.html @@ -0,0 +1,324 @@ + + + + + web2py.gluon.validators.IS_IN_SET + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module validators :: + Class IS_IN_SET + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class IS_IN_SET

    source code

    +
    +object --+    
    +         |    
    + Validator --+
    +             |
    +            IS_IN_SET
    +
    + +
    Known Subclasses:
    +
    + IS_IN_SUBSET +
    + +
    +example: +
    +   INPUT(_type='text', _name='name',
    +         requires=IS_IN_SET(['max', 'john'],zero=''))
    +
    + the argument of IS_IN_SET must be a list or set +
    +>>> IS_IN_SET(['max', 'john'])('max')
    +('max', None)
    +>>> IS_IN_SET(['max', 'john'])('massimo')
    +('massimo', 'value not allowed')
    +>>> IS_IN_SET(['max', 'john'], multiple=True)(('max', 'john'))
    +(('max', 'john'), None)
    +>>> IS_IN_SET(['max', 'john'], multiple=True)(('bill', 'john'))
    +(('bill', 'john'), 'value not allowed')
    +>>> IS_IN_SET(('id1','id2'), ['first label','second label'])('id1') # Traditional way
    +('id1', None)
    +>>> IS_IN_SET({'id1':'first label', 'id2':'second label'})('id1')
    +('id1', None)
    +>>> import itertools
    +>>> IS_IN_SET(itertools.chain(['1','3','5'],['2','4','6']))('1')
    +('1', None)
    +>>> IS_IN_SET([('id1','first label'), ('id2','second label')])('id1') # Redundant way
    +('id1', None)


    + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + theset, + labels=1, + error_message='value not allowed', + multiple=True, + zero='', + sort=True)
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    options(self, + zero=True) + source code + +
    + +
    +   + + + + + + +
    __call__(self, + value) + source code + +
    + +
    +

    Inherited from Validator: + formatter +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + theset, + labels=1, + error_message='value not allowed', + multiple=True, + zero='', + sort=True) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.validators.IS_IN_SUBSET-class.html Index: applications/examples/static/epydoc/web2py.gluon.validators.IS_IN_SUBSET-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.validators.IS_IN_SUBSET-class.html +++ applications/examples/static/epydoc/web2py.gluon.validators.IS_IN_SUBSET-class.html @@ -0,0 +1,297 @@ + + + + + web2py.gluon.validators.IS_IN_SUBSET + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module validators :: + Class IS_IN_SUBSET + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class IS_IN_SUBSET

    source code

    +
    +object --+        
    +         |        
    + Validator --+    
    +             |    
    +     IS_IN_SET --+
    +                 |
    +                IS_IN_SUBSET
    +
    + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + *a, + **b)
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    __call__(self, + value) + source code + +
    + +
    +

    Inherited from IS_IN_SET: + options +

    +

    Inherited from Validator: + formatter +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + *a, + **b) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + IS_IN_SET.__init__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    __call__(self, + value) +
    (Call operator) +

    +
    source code  +
    + + +
    +
    Overrides: + IS_IN_SET.__call__ +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.validators.IS_IPV4-class.html Index: applications/examples/static/epydoc/web2py.gluon.validators.IS_IPV4-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.validators.IS_IPV4-class.html +++ applications/examples/static/epydoc/web2py.gluon.validators.IS_IPV4-class.html @@ -0,0 +1,466 @@ + + + + + web2py.gluon.validators.IS_IPV4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module validators :: + Class IS_IPV4 + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class IS_IPV4

    source code

    +
    +object --+    
    +         |    
    + Validator --+
    +             |
    +            IS_IPV4
    +
    + +
    +
    +
    +Checks if field's value is an IP version 4 address in decimal form. Can
    +be set to force addresses from certain range.
    +
    +IPv4 regex taken from: http://regexlib.com/REDetails.aspx?regexp_id=1411
    +
    +Arguments:
    +
    +minip: lowest allowed address; accepts:
    +       str, eg. 192.168.0.1
    +       list or tuple of octets, eg. [192, 168, 0, 1]
    +maxip: highest allowed address; same as above
    +invert: True to allow addresses only from outside of given range; note
    +        that range boundaries are not matched this way
    +is_localhost: localhost address treatment:
    +              None (default): indifferent
    +              True (enforce): query address must match localhost address
    +                              (127.0.0.1)
    +              False (forbid): query address must not match localhost
    +                              address
    +is_private: same as above, except that query address is checked against
    +            two address ranges: 172.16.0.0 - 172.31.255.255 and
    +            192.168.0.0 - 192.168.255.255
    +is_automatic: same as above, except that query address is checked against
    +              one address range: 169.254.0.0 - 169.254.255.255
    +
    +Minip and maxip may also be lists or tuples of addresses in all above
    +forms (str, int, list / tuple), allowing setup of multiple address ranges:
    +
    +    minip = (minip1, minip2, ... minipN)
    +               |       |           |
    +               |       |           |
    +    maxip = (maxip1, maxip2, ... maxipN)
    +
    +Longer iterable will be truncated to match length of shorter one.
    +
    +Examples::
    +
    +    #Check for valid IPv4 address:
    +    INPUT(_type='text', _name='name', requires=IS_IPV4())
    +
    +    #Check for valid IPv4 address belonging to specific range:
    +    INPUT(_type='text', _name='name',
    +        requires=IS_IPV4(minip='100.200.0.0', maxip='100.200.255.255'))
    +
    +    #Check for valid IPv4 address belonging to either 100.110.0.0 -
    +    #100.110.255.255 or 200.50.0.0 - 200.50.0.255 address range:
    +    INPUT(_type='text', _name='name',
    +        requires=IS_IPV4(minip=('100.110.0.0', '200.50.0.0'),
    +                         maxip=('100.110.255.255', '200.50.0.255')))
    +
    +    #Check for valid IPv4 address belonging to private address space:
    +    INPUT(_type='text', _name='name', requires=IS_IPV4(is_private=True))
    +
    +    #Check for valid IPv4 address that is not a localhost address:
    +    INPUT(_type='text', _name='name', requires=IS_IPV4(is_localhost=False))
    +
    +>>> IS_IPV4()('1.2.3.4')
    +('1.2.3.4', None)
    +>>> IS_IPV4()('255.255.255.255')
    +('255.255.255.255', None)
    +>>> IS_IPV4()('1.2.3.4 ')
    +('1.2.3.4 ', 'enter valid IPv4 address')
    +>>> IS_IPV4()('1.2.3.4.5')
    +('1.2.3.4.5', 'enter valid IPv4 address')
    +>>> IS_IPV4()('123.123')
    +('123.123', 'enter valid IPv4 address')
    +>>> IS_IPV4()('1111.2.3.4')
    +('1111.2.3.4', 'enter valid IPv4 address')
    +>>> IS_IPV4()('0111.2.3.4')
    +('0111.2.3.4', 'enter valid IPv4 address')
    +>>> IS_IPV4()('256.2.3.4')
    +('256.2.3.4', 'enter valid IPv4 address')
    +>>> IS_IPV4()('300.2.3.4')
    +('300.2.3.4', 'enter valid IPv4 address')
    +>>> IS_IPV4(minip='1.2.3.4', maxip='1.2.3.4')('1.2.3.4')
    +('1.2.3.4', None)
    +>>> IS_IPV4(minip='1.2.3.5', maxip='1.2.3.9', error_message='bad ip')('1.2.3.4')
    +('1.2.3.4', 'bad ip')
    +>>> IS_IPV4(maxip='1.2.3.4', invert=True)('127.0.0.1')
    +('127.0.0.1', None)
    +>>> IS_IPV4(maxip='1.2.3.4', invert=True)('1.2.3.4')
    +('1.2.3.4', 'enter valid IPv4 address')
    +>>> IS_IPV4(is_localhost=True)('127.0.0.1')
    +('127.0.0.1', None)
    +>>> IS_IPV4(is_localhost=True)('1.2.3.4')
    +('1.2.3.4', 'enter valid IPv4 address')
    +>>> IS_IPV4(is_localhost=False)('127.0.0.1')
    +('127.0.0.1', 'enter valid IPv4 address')
    +>>> IS_IPV4(maxip='100.0.0.0', is_localhost=True)('127.0.0.1')
    +('127.0.0.1', 'enter valid IPv4 address')
    +
    +


    + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + minip='0.0.0.0', + maxip='255.255.255.255', + invert=True, + is_localhost=1, + is_private=1, + is_automatic=1, + error_message='enter valid IPv4 address')
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    __call__(self, + value) + source code + +
    + +
    +

    Inherited from Validator: + formatter +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + regex = re.compile(r'^(([1-9]?\d|1\d\d|2[0-4]\d|25[0-5])\.){3}... +
    +   + + numbers = (16777216, 65536, 256, 1) +
    +   + + localhost = 2130706433 +
    +   + + private = ((2886729728, 2886795263), (3232235520, 3232301055)) +
    +   + + automatic = (2851995648, 2852061183) +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + minip='0.0.0.0', + maxip='255.255.255.255', + invert=True, + is_localhost=1, + is_private=1, + is_automatic=1, + error_message='enter valid IPv4 address') +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + +
    + + + + + +
    Class Variable Details[hide private]
    +
    + +
    + +
    +

    regex

    + +
    +
    +
    +
    Value:
    +
    +re.compile(r'^(([1-9]?\d|1\d\d|2[0-4]\d|25[0-5])\.){3}([1-9]?\d|1\d\d|\
    +2[0-4]\d|25[0-5])$')
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.validators.IS_LENGTH-class.html Index: applications/examples/static/epydoc/web2py.gluon.validators.IS_LENGTH-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.validators.IS_LENGTH-class.html +++ applications/examples/static/epydoc/web2py.gluon.validators.IS_LENGTH-class.html @@ -0,0 +1,297 @@ + + + + + web2py.gluon.validators.IS_LENGTH + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module validators :: + Class IS_LENGTH + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class IS_LENGTH

    source code

    +
    +object --+    
    +         |    
    + Validator --+
    +             |
    +            IS_LENGTH
    +
    + +
    +

    Checks if length of field's value fits between given boundaries. Works + for both text and file inputs.

    +

    Arguments:

    +

    maxsize: maximum allowed length / size minsize: minimum allowed length + / size

    + Examples: +
    +   #Check if text string is shorter than 33 characters:
    +   INPUT(_type='text', _name='name', requires=IS_LENGTH(32))
    +
    +   #Check if password string is longer than 5 characters:
    +   INPUT(_type='password', _name='name', requires=IS_LENGTH(minsize=6))
    +
    +   #Check if uploaded file has size between 1KB and 1MB:
    +   INPUT(_type='file', _name='name', requires=IS_LENGTH(1048576, 1024))
    +
    +   >>> IS_LENGTH()('')
    +   ('', None)
    +   >>> IS_LENGTH()('1234567890')
    +   ('1234567890', None)
    +   >>> IS_LENGTH(maxsize=5, minsize=0)('1234567890')  # too long
    +   ('1234567890', 'enter from 0 to 5 characters')
    +   >>> IS_LENGTH(maxsize=50, minsize=20)('1234567890')  # too short
    +   ('1234567890', 'enter from 20 to 50 characters')
    +


    + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + maxsize=255, + minsize=0, + error_message='enter from %(min)g to %(max)g characters')
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    __call__(self, + value) + source code + +
    + +
    +

    Inherited from Validator: + formatter +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + maxsize=255, + minsize=0, + error_message='enter from %(min)g to %(max)g characters') +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.validators.IS_LIST_OF-class.html Index: applications/examples/static/epydoc/web2py.gluon.validators.IS_LIST_OF-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.validators.IS_LIST_OF-class.html +++ applications/examples/static/epydoc/web2py.gluon.validators.IS_LIST_OF-class.html @@ -0,0 +1,267 @@ + + + + + web2py.gluon.validators.IS_LIST_OF + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module validators :: + Class IS_LIST_OF + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class IS_LIST_OF

    source code

    +
    +object --+    
    +         |    
    + Validator --+
    +             |
    +            IS_LIST_OF
    +
    + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + other)
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    __call__(self, + value) + source code + +
    + +
    +

    Inherited from Validator: + formatter +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + other) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.validators.IS_LOWER-class.html Index: applications/examples/static/epydoc/web2py.gluon.validators.IS_LOWER-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.validators.IS_LOWER-class.html +++ applications/examples/static/epydoc/web2py.gluon.validators.IS_LOWER-class.html @@ -0,0 +1,211 @@ + + + + + web2py.gluon.validators.IS_LOWER + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module validators :: + Class IS_LOWER + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class IS_LOWER

    source code

    +
    +object --+    
    +         |    
    + Validator --+
    +             |
    +            IS_LOWER
    +
    + +
    +convert to lower case +
    +>>> IS_LOWER()('ABC')
    +('abc', None)
    +>>> IS_LOWER()('Ñ')
    +('\xc3\xb1', None)


    + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __call__(self, + value) + source code + +
    + +
    +

    Inherited from Validator: + formatter +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __init__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.validators.IS_MATCH-class.html Index: applications/examples/static/epydoc/web2py.gluon.validators.IS_MATCH-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.validators.IS_MATCH-class.html +++ applications/examples/static/epydoc/web2py.gluon.validators.IS_MATCH-class.html @@ -0,0 +1,289 @@ + + + + + web2py.gluon.validators.IS_MATCH + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module validators :: + Class IS_MATCH + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class IS_MATCH

    source code

    +
    +object --+    
    +         |    
    + Validator --+
    +             |
    +            IS_MATCH
    +
    + +
    Known Subclasses:
    +
    + IS_ALPHANUMERIC +
    + +
    +example: +
    +   INPUT(_type='text', _name='name', requires=IS_MATCH('.+'))
    +
    + the argument of IS_MATCH is a regular expression: +
    +   >>> IS_MATCH('.+')('hello')
    +   ('hello', None)
    +
    +   >>> IS_MATCH('.+')('')
    +   ('', 'invalid expression')
    +


    + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + expression, + error_message='invalid expression', + strict=True)
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    __call__(self, + value) + source code + +
    + +
    +

    Inherited from Validator: + formatter +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + expression, + error_message='invalid expression', + strict=True) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.validators.IS_NOT_EMPTY-class.html Index: applications/examples/static/epydoc/web2py.gluon.validators.IS_NOT_EMPTY-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.validators.IS_NOT_EMPTY-class.html +++ applications/examples/static/epydoc/web2py.gluon.validators.IS_NOT_EMPTY-class.html @@ -0,0 +1,299 @@ + + + + + web2py.gluon.validators.IS_NOT_EMPTY + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module validators :: + Class IS_NOT_EMPTY + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class IS_NOT_EMPTY

    source code

    +
    +object --+    
    +         |    
    + Validator --+
    +             |
    +            IS_NOT_EMPTY
    +
    + +
    +example: +
    +   INPUT(_type='text', _name='name', requires=IS_NOT_EMPTY())
    +
    +   >>> IS_NOT_EMPTY()(1)
    +   (1, None)
    +   >>> IS_NOT_EMPTY()(0)
    +   (0, None)
    +   >>> IS_NOT_EMPTY()('x')
    +   ('x', None)
    +   >>> IS_NOT_EMPTY()(' x ')
    +   ('x', None)
    +   >>> IS_NOT_EMPTY()(None)
    +   (None, 'enter a value')
    +   >>> IS_NOT_EMPTY()('')
    +   ('', 'enter a value')
    +   >>> IS_NOT_EMPTY()('  ')
    +   ('', 'enter a value')
    +   >>> IS_NOT_EMPTY()(' \n\t')
    +   ('', 'enter a value')
    +   >>> IS_NOT_EMPTY()([])
    +   ([], 'enter a value')
    +   >>> IS_NOT_EMPTY(empty_regex='def')('def')
    +   ('', 'enter a value')
    +   >>> IS_NOT_EMPTY(empty_regex='de[fg]')('deg')
    +   ('', 'enter a value')
    +   >>> IS_NOT_EMPTY(empty_regex='def')('abc')
    +   ('abc', None)
    +


    + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + error_message='enter a value', + empty_regex=1)
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    __call__(self, + value) + source code + +
    + +
    +

    Inherited from Validator: + formatter +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + error_message='enter a value', + empty_regex=1) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.validators.IS_NOT_IN_DB-class.html Index: applications/examples/static/epydoc/web2py.gluon.validators.IS_NOT_IN_DB-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.validators.IS_NOT_IN_DB-class.html +++ applications/examples/static/epydoc/web2py.gluon.validators.IS_NOT_IN_DB-class.html @@ -0,0 +1,296 @@ + + + + + web2py.gluon.validators.IS_NOT_IN_DB + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module validators :: + Class IS_NOT_IN_DB + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class IS_NOT_IN_DB

    source code

    +
    +object --+    
    +         |    
    + Validator --+
    +             |
    +            IS_NOT_IN_DB
    +
    + +
    +example: +
    +   INPUT(_type='text', _name='name', requires=IS_NOT_IN_DB(db, db.table))
    +
    + makes the field unique

    + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + dbset, + field, + error_message='value already in database or empty', + allowed_override=[])
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    set_self_id(self, + id) + source code + +
    + +
    +   + + + + + + +
    __call__(self, + value) + source code + +
    + +
    +

    Inherited from Validator: + formatter +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + dbset, + field, + error_message='value already in database or empty', + allowed_override=[]) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.validators.IS_SLUG-class.html Index: applications/examples/static/epydoc/web2py.gluon.validators.IS_SLUG-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.validators.IS_SLUG-class.html +++ applications/examples/static/epydoc/web2py.gluon.validators.IS_SLUG-class.html @@ -0,0 +1,350 @@ + + + + + web2py.gluon.validators.IS_SLUG + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module validators :: + Class IS_SLUG + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class IS_SLUG

    source code

    +
    +object --+    
    +         |    
    + Validator --+
    +             |
    +            IS_SLUG
    +
    + +
    +convert arbitrary text string to a slug +
    +>>> IS_SLUG()('abc123')
    +('abc123', None)
    +>>> IS_SLUG()('ABC123')
    +('abc123', None)
    +>>> IS_SLUG()('abc-123')
    +('abc-123', None)
    +>>> IS_SLUG()('abc--123')
    +('abc-123', None)
    +>>> IS_SLUG()('abc 123')
    +('abc-123', None)
    +>>> IS_SLUG()('abc  _123')
    +('abc-123', None)
    +>>> IS_SLUG()('-abc-')
    +('abc', None)
    +>>> IS_SLUG()('--a--b--_ -c--')
    +('a-b-c', None)
    +>>> IS_SLUG()('abc&amp;123')
    +('abc123', None)
    +>>> IS_SLUG()('abc&amp;123&amp;def')
    +('abc123def', None)
    +>>> IS_SLUG()('ñ')
    +('n', None)
    +>>> IS_SLUG(maxlen=4)('abc123')
    +('abc1', None)
    +>>> IS_SLUG()('abc_123')
    +('abc-123', None)
    +>>> IS_SLUG(keep_underscores=False)('abc_123')
    +('abc-123', None)
    +>>> IS_SLUG(keep_underscores=True)('abc_123')
    +('abc_123', None)
    +>>> IS_SLUG(check=False)('abc')
    +('abc', None)
    +>>> IS_SLUG(check=True)('abc')
    +('abc', None)
    +>>> IS_SLUG(check=False)('a bc')
    +('a-bc', None)
    +>>> IS_SLUG(check=True)('a bc')
    +('a bc', 'must be slug')


    + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + maxlen=80, + check=True, + error_message='must be slug', + keep_underscores=True)
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    __call__(self, + value) + source code + +
    + +
    +

    Inherited from Validator: + formatter +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Static Methods[hide private]
    +
    +   + + + + + + +
    urlify(value, + maxlen=80, + keep_underscores=True) + source code + +
    + +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + maxlen=80, + check=True, + error_message='must be slug', + keep_underscores=True) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.validators.IS_STRONG-class.html Index: applications/examples/static/epydoc/web2py.gluon.validators.IS_STRONG-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.validators.IS_STRONG-class.html +++ applications/examples/static/epydoc/web2py.gluon.validators.IS_STRONG-class.html @@ -0,0 +1,285 @@ + + + + + web2py.gluon.validators.IS_STRONG + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module validators :: + Class IS_STRONG + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class IS_STRONG

    source code

    +
    +object --+
    +         |
    +        IS_STRONG
    +
    + +
    +example: +
    +   INPUT(_type='password', _name='passwd',
    +       requires=IS_STRONG(min=10, special=2, upper=2))
    +
    + enforces complexity requirements on a field

    + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + min=8, + max=20, + upper=1, + lower=1, + number=1, + special=1, + specials='~!@#$%^&*()_+-=?<>,.:;{}[]|', + invalid=' "', + error_message=1)
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    __call__(self, + value) + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + min=8, + max=20, + upper=1, + lower=1, + number=1, + special=1, + specials='~!@#$%^&*()_+-=?<>,.:;{}[]|', + invalid=' "', + error_message=1) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.validators.IS_TIME-class.html Index: applications/examples/static/epydoc/web2py.gluon.validators.IS_TIME-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.validators.IS_TIME-class.html +++ applications/examples/static/epydoc/web2py.gluon.validators.IS_TIME-class.html @@ -0,0 +1,303 @@ + + + + + web2py.gluon.validators.IS_TIME + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module validators :: + Class IS_TIME + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class IS_TIME

    source code

    +
    +object --+    
    +         |    
    + Validator --+
    +             |
    +            IS_TIME
    +
    + +
    +example: +
    +   INPUT(_type='text', _name='name', requires=IS_TIME())
    +
    +

    understands the following formats hh:mm:ss [am/pm] hh:mm [am/pm] hh + [am/pm]

    + [am/pm] is optional, ':' can be replaced by any other non-space + non-digit +
    +>>> IS_TIME()('21:30')
    +(datetime.time(21, 30), None)
    +>>> IS_TIME()('21-30')
    +(datetime.time(21, 30), None)
    +>>> IS_TIME()('21.30')
    +(datetime.time(21, 30), None)
    +>>> IS_TIME()('21:30:59')
    +(datetime.time(21, 30, 59), None)
    +>>> IS_TIME()('5:30')
    +(datetime.time(5, 30), None)
    +>>> IS_TIME()('5:30 am')
    +(datetime.time(5, 30), None)
    +>>> IS_TIME()('5:30 pm')
    +(datetime.time(17, 30), None)
    +>>> IS_TIME()('5:30 whatever')
    +('5:30 whatever', 'enter time as hh:mm:ss (seconds, am, pm optional)')
    +>>> IS_TIME()('5:30 20')
    +('5:30 20', 'enter time as hh:mm:ss (seconds, am, pm optional)')
    +>>> IS_TIME()('24:30')
    +('24:30', 'enter time as hh:mm:ss (seconds, am, pm optional)')
    +>>> IS_TIME()('21:60')
    +('21:60', 'enter time as hh:mm:ss (seconds, am, pm optional)')
    +>>> IS_TIME()('21:30::')
    +('21:30::', 'enter time as hh:mm:ss (seconds, am, pm optional)')
    +>>> IS_TIME()('')
    +('', 'enter time as hh:mm:ss (seconds, am, pm optional)')


    + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + error_message='enter time as hh:mm:ss (seconds, am, pm optional)')
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    __call__(self, + value) + source code + +
    + +
    +

    Inherited from Validator: + formatter +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + error_message='enter time as hh:mm:ss (seconds, am, pm optional)') +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.validators.IS_UPLOAD_FILENAME-class.html Index: applications/examples/static/epydoc/web2py.gluon.validators.IS_UPLOAD_FILENAME-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.validators.IS_UPLOAD_FILENAME-class.html +++ applications/examples/static/epydoc/web2py.gluon.validators.IS_UPLOAD_FILENAME-class.html @@ -0,0 +1,315 @@ + + + + + web2py.gluon.validators.IS_UPLOAD_FILENAME + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module validators :: + Class IS_UPLOAD_FILENAME + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class IS_UPLOAD_FILENAME

    source code

    +
    +object --+    
    +         |    
    + Validator --+
    +             |
    +            IS_UPLOAD_FILENAME
    +
    + +
    +
    +
    +Checks if name and extension of file uploaded through file input matches
    +given criteria.
    +
    +Does *not* ensure the file type in any way. Returns validation failure
    +if no data was uploaded.
    +
    +Arguments::
    +
    +filename: filename (before dot) regex
    +extension: extension (after dot) regex
    +lastdot: which dot should be used as a filename / extension separator:
    +         True means last dot, eg. file.png -> file / png
    +         False means first dot, eg. file.tar.gz -> file / tar.gz
    +case: 0 - keep the case, 1 - transform the string into lowercase (default),
    +      2 - transform the string into uppercase
    +
    +If there is no dot present, extension checks will be done against empty
    +string and filename checks against whole value.
    +
    +Examples::
    +
    +    #Check if file has a pdf extension (case insensitive):
    +    INPUT(_type='file', _name='name',
    +        requires=IS_UPLOAD_FILENAME(extension='pdf'))
    +
    +    #Check if file has a tar.gz extension and name starting with backup:
    +    INPUT(_type='file', _name='name',
    +        requires=IS_UPLOAD_FILENAME(filename='backup.*',
    +            extension='tar.gz', lastdot=False))
    +
    +    #Check if file has no extension and name matching README
    +    #(case sensitive):
    +    INPUT(_type='file', _name='name',
    +        requires=IS_UPLOAD_FILENAME(filename='^README$',
    +            extension='^$', case=0))
    +
    +


    + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + filename=1, + extension=1, + lastdot=True, + case=1, + error_message='enter valid filename')
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    __call__(self, + value) + source code + +
    + +
    +

    Inherited from Validator: + formatter +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + filename=1, + extension=1, + lastdot=True, + case=1, + error_message='enter valid filename') +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + object.__init__ +
    (inherited documentation)
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.validators.IS_UPPER-class.html Index: applications/examples/static/epydoc/web2py.gluon.validators.IS_UPPER-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.validators.IS_UPPER-class.html +++ applications/examples/static/epydoc/web2py.gluon.validators.IS_UPPER-class.html @@ -0,0 +1,211 @@ + + + + + web2py.gluon.validators.IS_UPPER + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module validators :: + Class IS_UPPER + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class IS_UPPER

    source code

    +
    +object --+    
    +         |    
    + Validator --+
    +             |
    +            IS_UPPER
    +
    + +
    +convert to upper case +
    +>>> IS_UPPER()('abc')
    +('ABC', None)
    +>>> IS_UPPER()('ñ')
    +('\xc3\x91', None)


    + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __call__(self, + value) + source code + +
    + +
    +

    Inherited from Validator: + formatter +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __init__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.validators.IS_URL-class.html Index: applications/examples/static/epydoc/web2py.gluon.validators.IS_URL-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.validators.IS_URL-class.html +++ applications/examples/static/epydoc/web2py.gluon.validators.IS_URL-class.html @@ -0,0 +1,392 @@ + + + + + web2py.gluon.validators.IS_URL + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module validators :: + Class IS_URL + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class IS_URL

    source code

    +
    +object --+    
    +         |    
    + Validator --+
    +             |
    +            IS_URL
    +
    + +
    +
    +
    +Rejects a URL string if any of the following is true:
    +   * The string is empty or None
    +   * The string uses characters that are not allowed in a URL
    +   * The string breaks any of the HTTP syntactic rules
    +   * The URL scheme specified (if one is specified) is not 'http' or 'https'
    +   * The top-level domain (if a host name is specified) does not exist
    +
    +(These rules are based on RFC 2616: http://www.faqs.org/rfcs/rfc2616.html)
    +
    +This function only checks the URL's syntax. It does not check that the URL
    +points to a real document, for example, or that it otherwise makes sense
    +semantically. This function does automatically prepend 'http://' in front
    +of a URL in the case of an abbreviated URL (e.g. 'google.ca').
    +
    +If the parameter mode='generic' is used, then this function's behavior
    +changes. It then rejects a URL string if any of the following is true:
    +   * The string is empty or None
    +   * The string uses characters that are not allowed in a URL
    +   * The URL scheme specified (if one is specified) is not valid
    +
    +(These rules are based on RFC 2396: http://www.faqs.org/rfcs/rfc2396.html)
    +
    +The list of allowed schemes is customizable with the allowed_schemes
    +parameter. If you exclude None from the list, then abbreviated URLs
    +(lacking a scheme such as 'http') will be rejected.
    +
    +The default prepended scheme is customizable with the prepend_scheme
    +parameter. If you set prepend_scheme to None then prepending will be
    +disabled. URLs that require prepending to parse will still be accepted,
    +but the return value will not be modified.
    +
    +IS_URL is compatible with the Internationalized Domain Name (IDN) standard
    +specified in RFC 3490 (http://tools.ietf.org/html/rfc3490). As a result,
    +URLs can be regular strings or unicode strings.
    +If the URL's domain component (e.g. google.ca) contains non-US-ASCII
    +letters, then the domain will be converted into Punycode (defined in
    +RFC 3492, http://tools.ietf.org/html/rfc3492). IS_URL goes a bit beyond
    +the standards, and allows non-US-ASCII characters to be present in the path
    +and query components of the URL as well. These non-US-ASCII characters will
    +be escaped using the standard '%20' type syntax. e.g. the unicode
    +character with hex code 0x4e86 will become '%4e%86'
    +
    +Code Examples::
    +
    +    INPUT(_type='text', _name='name', requires=IS_URL())
    +    >>> IS_URL()('abc.com')
    +    ('http://abc.com', None)
    +
    +    INPUT(_type='text', _name='name', requires=IS_URL(mode='generic'))
    +    >>> IS_URL(mode='generic')('abc.com')
    +    ('abc.com', None)
    +
    +    INPUT(_type='text', _name='name',
    +        requires=IS_URL(allowed_schemes=['https'], prepend_scheme='https'))
    +    >>> IS_URL(allowed_schemes=['https'], prepend_scheme='https')('https://abc.com')
    +    ('https://abc.com', None)
    +
    +    INPUT(_type='text', _name='name',
    +        requires=IS_URL(prepend_scheme='https'))
    +    >>> IS_URL(prepend_scheme='https')('abc.com')
    +    ('https://abc.com', None)
    +
    +    INPUT(_type='text', _name='name',
    +        requires=IS_URL(mode='generic', allowed_schemes=['ftps', 'https'],
    +            prepend_scheme='https'))
    +    >>> IS_URL(mode='generic', allowed_schemes=['ftps', 'https'], prepend_scheme='https')('https://abc.com')
    +    ('https://abc.com', None)
    +    >>> IS_URL(mode='generic', allowed_schemes=['ftps', 'https', None], prepend_scheme='https')('abc.com')
    +    ('abc.com', None)
    +
    +@author: Jonathan Benn
    +
    +


    + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + error_message='enter a valid URL', + mode='http', + allowed_schemes=1, + prepend_scheme='http')
    + :param error_message: a string, the error message to give the end user + if the URL does not validate +:param allowed_schemes: a list containing strings or None.
    + source code + +
    + +
    +   + + + + + + +
    __call__(self, + value)
    + :param value: a unicode or regular string, the URL to validate +:returns: a (string, string) tuple, where tuple[0] is the modified + input value and tuple[1] is either None (success!) or the + string error_message.
    + source code + +
    + +
    +

    Inherited from Validator: + formatter +

    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + error_message='enter a valid URL', + mode='http', + allowed_schemes=1, + prepend_scheme='http') +
    (Constructor) +

    +
    source code  +
    + +
    +
    +:param error_message: a string, the error message to give the end user
    +    if the URL does not validate
    +:param allowed_schemes: a list containing strings or None. Each element
    +    is a scheme the inputed URL is allowed to use
    +:param prepend_scheme: a string, this scheme is prepended if it's
    +    necessary to make the URL valid
    +
    +
    +
    +
    Overrides: + object.__init__ +
    +
    +
    +
    + +
    + +
    + + +
    +

    __call__(self, + value) +
    (Call operator) +

    +
    source code  +
    + +
    +
    +:param value: a unicode or regular string, the URL to validate
    +:returns: a (string, string) tuple, where tuple[0] is the modified
    +    input value and tuple[1] is either None (success!) or the
    +    string error_message. The input value will never be modified in the
    +    case of an error. However, if there is success then the input URL
    +    may be modified to (1) prepend a scheme, and/or (2) convert a
    +    non-compliant unicode URL into a compliant US-ASCII version.
    +
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.validators.Validator-class.html Index: applications/examples/static/epydoc/web2py.gluon.validators.Validator-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.validators.Validator-class.html +++ applications/examples/static/epydoc/web2py.gluon.validators.Validator-class.html @@ -0,0 +1,304 @@ + + + + + web2py.gluon.validators.Validator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module validators :: + Class Validator + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class Validator

    source code

    +
    +object --+
    +         |
    +        Validator
    +
    + +
    Known Subclasses:
    +
    + CLEANUP, + IS_MATCH, + IS_DATE, + IS_DATETIME, + IS_DECIMAL_IN_RANGE, + IS_EMAIL, + IS_EMPTY_OR, + IS_EQUAL_TO, + IS_EXPR, + IS_FLOAT_IN_RANGE, + IS_IMAGE, + IS_INT_IN_RANGE, + IS_IN_DB, + IS_IN_SET, + IS_IPV4, + IS_LENGTH, + IS_LIST_OF, + IS_LOWER, + IS_NOT_EMPTY, + IS_NOT_IN_DB, + IS_SLUG, + IS_TIME, + IS_UPLOAD_FILENAME, + IS_UPPER, + IS_URL, + IS_GENERIC_URL, + IS_HTTP_URL +
    + +
    +

    Root for all validators, mainly for documentation purposes.

    +

    Validators are classes used to validate input fields (including forms + generated from database tables).

    + Here is an example of using a validator with a FORM: +
    +   INPUT(_name='a', requires=IS_INT_IN_RANGE(0, 10))
    +
    + Here is an example of how to require a validator for a table + field: +
    +   db.define_table('person', SQLField('name'))
    +   db.person.name.requires=IS_NOT_EMPTY()
    +
    + Validators are always assigned using the requires attribute of a + field. A field can have a single validator or multiple validators. + Multiple validators are made part of a list: +
    +   db.person.name.requires=[IS_NOT_EMPTY(), IS_NOT_IN_DB(db, 'person.id')]
    +
    +

    Validators are called by the function accepts on a FORM or other HTML + helper object that contains a form. They are always called in the order + in which they are listed.

    + Built-in validators have constructors that take the optional argument + error message which allows you to change the default error message. Here + is an example of a validator on a database table: +
    +   db.person.name.requires=IS_NOT_EMPTY(error_message=T('fill this'))
    +
    +

    where we have used the translation operator T to allow for + internationalization.

    + Notice that default error messages are not translated.

    + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    formatter(self, + value)
    + For some validators returns a formatted version (matching the + validator) of value.
    + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __init__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    formatter(self, + value) +

    +
    source code  +
    + + For some validators returns a formatted version (matching the + validator) of value. Otherwise just returns the value. +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.widget-module.html Index: applications/examples/static/epydoc/web2py.gluon.widget-module.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.widget-module.html +++ applications/examples/static/epydoc/web2py.gluon.widget-module.html @@ -0,0 +1,384 @@ + + + + + web2py.gluon.widget + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module widget + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Module widget

    source code

    +

    This file is part of the web2py Web Framework Copyrighted by Massimo + Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)

    + The widget is called from web2py.

    + + + + + + + + + + + + + + + + +
    + + + + + +
    Classes[hide private]
    +
    +   + + BaseException
    + Common base class for all non-exit exceptions. +
    +   + + IO +
    +   + + web2pyDialog
    + Main window dialog +
    + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Functions[hide private]
    +
    +   + + + + + + +
    try_start_browser(url)
    + Try to start the default browser
    + source code + +
    + +
    +   + + + + + + +
    start_browser(ip, + port)
    + Starts the default browser
    + source code + +
    + +
    +   + + + + + + +
    presentation(root)
    + Draw the splash screen
    + source code + +
    + +
    +   + + + + + + +
    console()
    + Defines the behavior of the console web2py execution
    + source code + +
    + +
    +   + + + + + + +
    start(cron=True)
    + Start server
    + source code + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Variables[hide private]
    +
    +   + + ProgramName = 'web2py Web Framework' +
    +   + + ProgramAuthor = 'Created by Massimo Di Pierro, Copyright 2007-... +
    +   + + ProgramVersion = 'Version 1.98.2 (2011-08-03 18:44:38)' +
    +   + + ProgramInfo = 'web2py Web Framework\n Created ... +
    +   + + msg = msg % sys.version +
    +   + + logger = logging.getLogger("web2py") +
    + + + + + + +
    + + + + + +
    Variables Details[hide private]
    +
    + +
    + +
    +

    ProgramAuthor

    + +
    +
    +
    +
    Value:
    +
    +'Created by Massimo Di Pierro, Copyright 2007-2011'
    +
    +
    +
    +
    +
    + +
    + +
    +

    ProgramInfo

    + +
    +
    +
    +
    Value:
    +
    +'''web2py Web Framework
    +                 Created by Massimo Di Pierro, Copyright 2007-2011
    +                 Version 1.98.2 (2011-08-03 18:44:38)'''
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.widget-pysrc.html Index: applications/examples/static/epydoc/web2py.gluon.widget-pysrc.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.widget-pysrc.html +++ applications/examples/static/epydoc/web2py.gluon.widget-pysrc.html @@ -0,0 +1,1416 @@ + + + + + web2py.gluon.widget + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module widget + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Module web2py.gluon.widget

    +
    +  1  #!/usr/bin/env python 
    +  2  # -*- coding: utf-8 -*- 
    +  3   
    +  4  """ 
    +  5  This file is part of the web2py Web Framework 
    +  6  Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> 
    +  7  License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) 
    +  8   
    +  9  The widget is called from web2py. 
    + 10  """ 
    + 11   
    + 12  import sys 
    + 13  import cStringIO 
    + 14  import time 
    + 15  import thread 
    + 16  import re 
    + 17  import os 
    + 18  import socket 
    + 19  import signal 
    + 20  import math 
    + 21  import logging 
    + 22   
    + 23  import newcron 
    + 24  import main 
    + 25   
    + 26  from fileutils import w2p_pack, read_file, write_file 
    + 27  from shell import run, test 
    + 28  from settings import global_settings 
    + 29   
    + 30  try: 
    + 31      import Tkinter, tkMessageBox 
    + 32      import contrib.taskbar_widget 
    + 33      from winservice import web2py_windows_service_handler 
    + 34  except: 
    + 35      pass 
    + 36   
    + 37   
    + 38  try: 
    + 39      BaseException 
    + 40  except NameError: 
    + 41      BaseException = Exception 
    + 42   
    + 43  ProgramName = 'web2py Web Framework' 
    + 44  ProgramAuthor = 'Created by Massimo Di Pierro, Copyright 2007-2011' 
    + 45  ProgramVersion = read_file('VERSION').strip() 
    + 46   
    + 47  ProgramInfo = '''%s 
    + 48                   %s 
    + 49                   %s''' % (ProgramName, ProgramAuthor, ProgramVersion) 
    + 50   
    + 51  if not sys.version[:3] in ['2.4', '2.5', '2.6', '2.7']: 
    + 52      msg = 'Warning: web2py requires Python 2.4, 2.5 (recommended), 2.6 or 2.7 but you are running:\n%s' 
    + 53      msg = msg % sys.version 
    + 54      sys.stderr.write(msg) 
    + 55   
    + 56  logger = logging.getLogger("web2py") 
    + 57   
    +
    58 -class IO(object): +
    59 """ """ + 60 +
    61 - def __init__(self): +
    62 """ """ + 63 + 64 self.buffer = cStringIO.StringIO() +
    65 +
    66 - def write(self, data): +
    67 """ """ + 68 + 69 sys.__stdout__.write(data) + 70 if hasattr(self, 'callback'): + 71 self.callback(data) + 72 else: + 73 self.buffer.write(data) +
    74 + 75 +
    76 -def try_start_browser(url): +
    77 """ Try to start the default browser """ + 78 + 79 try: + 80 import webbrowser + 81 webbrowser.open(url) + 82 except: + 83 print 'warning: unable to detect your browser' +
    84 + 85 +
    86 -def start_browser(ip, port): +
    87 """ Starts the default browser """ + 88 print 'please visit:' + 89 print '\thttp://%s:%s' % (ip, port) + 90 print 'starting browser...' + 91 try_start_browser('http://%s:%s' % (ip, port)) +
    92 + 93 +
    94 -def presentation(root): +
    95 """ Draw the splash screen """ + 96 + 97 root.withdraw() + 98 + 99 dx = root.winfo_screenwidth() +100 dy = root.winfo_screenheight() +101 +102 dialog = Tkinter.Toplevel(root, bg='white') +103 dialog.geometry('%ix%i+%i+%i' % (500, 300, dx / 2 - 200, dy / 2 - 150)) +104 +105 dialog.overrideredirect(1) +106 dialog.focus_force() +107 +108 canvas = Tkinter.Canvas(dialog, +109 background='white', +110 width=500, +111 height=300) +112 canvas.pack() +113 root.update() +114 +115 img = Tkinter.PhotoImage(file='splashlogo.gif') +116 pnl = Tkinter.Label(canvas, image=img, background='white', bd=0) +117 pnl.pack(side='top', fill='both', expand='yes') +118 # Prevent garbage collection of img +119 pnl.image=img +120 +121 def add_label(text='Change Me', font_size=12, foreground='#195866', height=1): +122 return Tkinter.Label( +123 master=canvas, +124 width=250, +125 height=height, +126 text=text, +127 font=('Helvetica', font_size), +128 anchor=Tkinter.CENTER, +129 foreground=foreground, +130 background='white' +131 ) +
    132 +133 add_label('Welcome to...').pack(side='top') +134 add_label(ProgramName, 18, '#FF5C1F', 2).pack() +135 add_label(ProgramAuthor).pack() +136 add_label(ProgramVersion).pack() +137 +138 root.update() +139 time.sleep(5) +140 dialog.destroy() +141 return +142 +143 +
    144 -class web2pyDialog(object): +
    145 """ Main window dialog """ +146 +
    147 - def __init__(self, root, options): +
    148 """ web2pyDialog constructor """ +149 +150 root.title('web2py server') +151 self.root = Tkinter.Toplevel(root) +152 self.options = options +153 self.menu = Tkinter.Menu(self.root) +154 servermenu = Tkinter.Menu(self.menu, tearoff=0) +155 httplog = os.path.join(self.options.folder, 'httpserver.log') +156 +157 # Building the Menu +158 item = lambda: try_start_browser(httplog) +159 servermenu.add_command(label='View httpserver.log', +160 command=item) +161 +162 servermenu.add_command(label='Quit (pid:%i)' % os.getpid(), +163 command=self.quit) +164 +165 self.menu.add_cascade(label='Server', menu=servermenu) +166 +167 self.pagesmenu = Tkinter.Menu(self.menu, tearoff=0) +168 self.menu.add_cascade(label='Pages', menu=self.pagesmenu) +169 +170 helpmenu = Tkinter.Menu(self.menu, tearoff=0) +171 +172 # Home Page +173 item = lambda: try_start_browser('http://www.web2py.com') +174 helpmenu.add_command(label='Home Page', +175 command=item) +176 +177 # About +178 item = lambda: tkMessageBox.showinfo('About web2py', ProgramInfo) +179 helpmenu.add_command(label='About', +180 command=item) +181 +182 self.menu.add_cascade(label='Info', menu=helpmenu) +183 +184 self.root.config(menu=self.menu) +185 +186 if options.taskbar: +187 self.root.protocol('WM_DELETE_WINDOW', +188 lambda: self.quit(True)) +189 else: +190 self.root.protocol('WM_DELETE_WINDOW', self.quit) +191 +192 sticky = Tkinter.NW +193 +194 # IP +195 Tkinter.Label(self.root, +196 text='Server IP:', +197 justify=Tkinter.LEFT).grid(row=0, +198 column=0, +199 sticky=sticky) +200 self.ip = Tkinter.Entry(self.root) +201 self.ip.insert(Tkinter.END, self.options.ip) +202 self.ip.grid(row=0, column=1, sticky=sticky) +203 +204 # Port +205 Tkinter.Label(self.root, +206 text='Server Port:', +207 justify=Tkinter.LEFT).grid(row=1, +208 column=0, +209 sticky=sticky) +210 +211 self.port_number = Tkinter.Entry(self.root) +212 self.port_number.insert(Tkinter.END, self.options.port) +213 self.port_number.grid(row=1, column=1, sticky=sticky) +214 +215 # Password +216 Tkinter.Label(self.root, +217 text='Choose Password:', +218 justify=Tkinter.LEFT).grid(row=2, +219 column=0, +220 sticky=sticky) +221 +222 self.password = Tkinter.Entry(self.root, show='*') +223 self.password.bind('<Return>', lambda e: self.start()) +224 self.password.focus_force() +225 self.password.grid(row=2, column=1, sticky=sticky) +226 +227 # Prepare the canvas +228 self.canvas = Tkinter.Canvas(self.root, +229 width=300, +230 height=100, +231 bg='black') +232 self.canvas.grid(row=3, column=0, columnspan=2) +233 self.canvas.after(1000, self.update_canvas) +234 +235 # Prepare the frame +236 frame = Tkinter.Frame(self.root) +237 frame.grid(row=4, column=0, columnspan=2) +238 +239 # Start button +240 self.button_start = Tkinter.Button(frame, +241 text='start server', +242 command=self.start) +243 +244 self.button_start.grid(row=0, column=0) +245 +246 # Stop button +247 self.button_stop = Tkinter.Button(frame, +248 text='stop server', +249 command=self.stop) +250 +251 self.button_stop.grid(row=0, column=1) +252 self.button_stop.configure(state='disabled') +253 +254 if options.taskbar: +255 self.tb = contrib.taskbar_widget.TaskBarIcon() +256 self.checkTaskBar() +257 +258 if options.password != '<ask>': +259 self.password.insert(0, options.password) +260 self.start() +261 self.root.withdraw() +262 else: +263 self.tb = None +
    264 +
    265 - def checkTaskBar(self): +
    266 """ Check taskbar status """ +267 +268 if self.tb.status: +269 if self.tb.status[0] == self.tb.EnumStatus.QUIT: +270 self.quit() +271 elif self.tb.status[0] == self.tb.EnumStatus.TOGGLE: +272 if self.root.state() == 'withdrawn': +273 self.root.deiconify() +274 else: +275 self.root.withdraw() +276 elif self.tb.status[0] == self.tb.EnumStatus.STOP: +277 self.stop() +278 elif self.tb.status[0] == self.tb.EnumStatus.START: +279 self.start() +280 elif self.tb.status[0] == self.tb.EnumStatus.RESTART: +281 self.stop() +282 self.start() +283 del self.tb.status[0] +284 +285 self.root.after(1000, self.checkTaskBar) +
    286 +
    287 - def update(self, text): +
    288 """ Update app text """ +289 +290 try: +291 self.text.configure(state='normal') +292 self.text.insert('end', text) +293 self.text.configure(state='disabled') +294 except: +295 pass # ## this should only happen in case app is destroyed +
    296 +
    297 - def connect_pages(self): +
    298 """ Connect pages """ +299 +300 for arq in os.listdir('applications/'): +301 if os.path.exists('applications/%s/__init__.py' % arq): +302 url = self.url + '/' + arq +303 start_browser = lambda u = url: try_start_browser(u) +304 self.pagesmenu.add_command(label=url, +305 command=start_browser) +
    306 +
    307 - def quit(self, justHide=False): +
    308 """ Finish the program execution """ +309 +310 if justHide: +311 self.root.withdraw() +312 else: +313 try: +314 self.server.stop() +315 except: +316 pass +317 +318 try: +319 self.tb.Destroy() +320 except: +321 pass +322 +323 self.root.destroy() +324 sys.exit() +
    325 +
    326 - def error(self, message): +
    327 """ Show error message """ +328 +329 tkMessageBox.showerror('web2py start server', message) +
    330 +
    331 - def start(self): +
    332 """ Start web2py server """ +333 +334 password = self.password.get() +335 +336 if not password: +337 self.error('no password, no web admin interface') +338 +339 ip = self.ip.get() +340 +341 regexp = '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}' +342 if ip and not re.compile(regexp).match(ip): +343 return self.error('invalid host ip address') +344 +345 try: +346 port = int(self.port_number.get()) +347 except: +348 return self.error('invalid port number') +349 +350 self.url = 'http://%s:%s' % (ip, port) +351 self.connect_pages() +352 self.button_start.configure(state='disabled') +353 +354 try: +355 options = self.options +356 req_queue_size = options.request_queue_size +357 self.server = main.HttpServer( +358 ip, +359 port, +360 password, +361 pid_filename=options.pid_filename, +362 log_filename=options.log_filename, +363 profiler_filename=options.profiler_filename, +364 ssl_certificate=options.ssl_certificate, +365 ssl_private_key=options.ssl_private_key, +366 min_threads=options.minthreads, +367 max_threads=options.maxthreads, +368 server_name=options.server_name, +369 request_queue_size=req_queue_size, +370 timeout=options.timeout, +371 shutdown_timeout=options.shutdown_timeout, +372 path=options.folder, +373 interfaces=options.interfaces) +374 +375 thread.start_new_thread(self.server.start, ()) +376 except Exception, e: +377 self.button_start.configure(state='normal') +378 return self.error(str(e)) +379 +380 self.button_stop.configure(state='normal') +381 +382 if not options.taskbar: +383 thread.start_new_thread(start_browser, (ip, port)) +384 +385 self.password.configure(state='readonly') +386 self.ip.configure(state='readonly') +387 self.port_number.configure(state='readonly') +388 +389 if self.tb: +390 self.tb.SetServerRunning() +
    391 +
    392 - def stop(self): +
    393 """ Stop web2py server """ +394 +395 self.button_start.configure(state='normal') +396 self.button_stop.configure(state='disabled') +397 self.password.configure(state='normal') +398 self.ip.configure(state='normal') +399 self.port_number.configure(state='normal') +400 self.server.stop() +401 +402 if self.tb: +403 self.tb.SetServerStopped() +
    404 +
    405 - def update_canvas(self): +
    406 """ Update canvas """ +407 +408 try: +409 t1 = os.path.getsize('httpserver.log') +410 except: +411 self.canvas.after(1000, self.update_canvas) +412 return +413 +414 try: +415 fp = open('httpserver.log', 'r') +416 fp.seek(self.t0) +417 data = fp.read(t1 - self.t0) +418 fp.close() +419 value = self.p0[1:] + [10 + 90.0 / math.sqrt(1 + data.count('\n'))] +420 self.p0 = value +421 +422 for i in xrange(len(self.p0) - 1): +423 c = self.canvas.coords(self.q0[i]) +424 self.canvas.coords(self.q0[i], +425 (c[0], +426 self.p0[i], +427 c[2], +428 self.p0[i + 1])) +429 self.t0 = t1 +430 except BaseException: +431 self.t0 = time.time() +432 self.t0 = t1 +433 self.p0 = [100] * 300 +434 self.q0 = [self.canvas.create_line(i, 100, i + 1, 100, +435 fill='green') for i in xrange(len(self.p0) - 1)] +436 +437 self.canvas.after(1000, self.update_canvas) +
    438 +439 +
    440 -def console(): +
    441 """ Defines the behavior of the console web2py execution """ +442 import optparse +443 import textwrap +444 +445 usage = "python web2py.py" +446 +447 description = """\ +448 web2py Web Framework startup script. +449 ATTENTION: unless a password is specified (-a 'passwd') web2py will +450 attempt to run a GUI. In this case command line options are ignored.""" +451 +452 description = textwrap.dedent(description) +453 +454 parser = optparse.OptionParser(usage, None, optparse.Option, ProgramVersion) +455 +456 parser.description = description +457 +458 parser.add_option('-i', +459 '--ip', +460 default='127.0.0.1', +461 dest='ip', +462 help='ip address of the server (127.0.0.1)') +463 +464 parser.add_option('-p', +465 '--port', +466 default='8000', +467 dest='port', +468 type='int', +469 help='port of server (8000)') +470 +471 msg = 'password to be used for administration' +472 msg += ' (use -a "<recycle>" to reuse the last password))' +473 parser.add_option('-a', +474 '--password', +475 default='<ask>', +476 dest='password', +477 help=msg) +478 +479 parser.add_option('-c', +480 '--ssl_certificate', +481 default='', +482 dest='ssl_certificate', +483 help='file that contains ssl certificate') +484 +485 parser.add_option('-k', +486 '--ssl_private_key', +487 default='', +488 dest='ssl_private_key', +489 help='file that contains ssl private key') +490 +491 parser.add_option('-d', +492 '--pid_filename', +493 default='httpserver.pid', +494 dest='pid_filename', +495 help='file to store the pid of the server') +496 +497 parser.add_option('-l', +498 '--log_filename', +499 default='httpserver.log', +500 dest='log_filename', +501 help='file to log connections') +502 +503 parser.add_option('-n', +504 '--numthreads', +505 default=None, +506 type='int', +507 dest='numthreads', +508 help='number of threads (deprecated)') +509 +510 parser.add_option('--minthreads', +511 default=None, +512 type='int', +513 dest='minthreads', +514 help='minimum number of server threads') +515 +516 parser.add_option('--maxthreads', +517 default=None, +518 type='int', +519 dest='maxthreads', +520 help='maximum number of server threads') +521 +522 parser.add_option('-s', +523 '--server_name', +524 default=socket.gethostname(), +525 dest='server_name', +526 help='server name for the web server') +527 +528 msg = 'max number of queued requests when server unavailable' +529 parser.add_option('-q', +530 '--request_queue_size', +531 default='5', +532 type='int', +533 dest='request_queue_size', +534 help=msg) +535 +536 parser.add_option('-o', +537 '--timeout', +538 default='10', +539 type='int', +540 dest='timeout', +541 help='timeout for individual request (10 seconds)') +542 +543 parser.add_option('-z', +544 '--shutdown_timeout', +545 default='5', +546 type='int', +547 dest='shutdown_timeout', +548 help='timeout on shutdown of server (5 seconds)') +549 parser.add_option('-f', +550 '--folder', +551 default=os.getcwd(), +552 dest='folder', +553 help='folder from which to run web2py') +554 +555 parser.add_option('-v', +556 '--verbose', +557 action='store_true', +558 dest='verbose', +559 default=False, +560 help='increase --test verbosity') +561 +562 parser.add_option('-Q', +563 '--quiet', +564 action='store_true', +565 dest='quiet', +566 default=False, +567 help='disable all output') +568 +569 msg = 'set debug output level (0-100, 0 means all, 100 means none;' +570 msg += ' default is 30)' +571 parser.add_option('-D', +572 '--debug', +573 dest='debuglevel', +574 default=30, +575 type='int', +576 help=msg) +577 +578 msg = 'run web2py in interactive shell or IPython (if installed) with' +579 msg += ' specified appname (if app does not exist it will be created).' +580 msg += ' APPNAME like a/c/f (c,f optional)' +581 parser.add_option('-S', +582 '--shell', +583 dest='shell', +584 metavar='APPNAME', +585 help=msg) +586 +587 msg = 'run web2py in interactive shell or bpython (if installed) with' +588 msg += ' specified appname (if app does not exist it will be created).' +589 msg += '\n Use combined with --shell' +590 parser.add_option('-B', +591 '--bpython', +592 action='store_true', +593 default=False, +594 dest='bpython', +595 help=msg) +596 +597 msg = 'only use plain python shell; should be used with --shell option' +598 parser.add_option('-P', +599 '--plain', +600 action='store_true', +601 default=False, +602 dest='plain', +603 help=msg) +604 +605 msg = 'auto import model files; default is False; should be used' +606 msg += ' with --shell option' +607 parser.add_option('-M', +608 '--import_models', +609 action='store_true', +610 default=False, +611 dest='import_models', +612 help=msg) +613 +614 msg = 'run PYTHON_FILE in web2py environment;' +615 msg += ' should be used with --shell option' +616 parser.add_option('-R', +617 '--run', +618 dest='run', +619 metavar='PYTHON_FILE', +620 default='', +621 help=msg) +622 +623 msg = 'run doctests in web2py environment; ' +\ +624 'TEST_PATH like a/c/f (c,f optional)' +625 parser.add_option('-T', +626 '--test', +627 dest='test', +628 metavar='TEST_PATH', +629 default=None, +630 help=msg) +631 +632 parser.add_option('-W', +633 '--winservice', +634 dest='winservice', +635 default='', +636 help='-W install|start|stop as Windows service') +637 +638 msg = 'trigger a cron run manually; usually invoked from a system crontab' +639 parser.add_option('-C', +640 '--cron', +641 action='store_true', +642 dest='extcron', +643 default=False, +644 help=msg) +645 +646 msg = 'triggers the use of softcron' +647 parser.add_option('--softcron', +648 action='store_true', +649 dest='softcron', +650 default=False, +651 help=msg) +652 +653 parser.add_option('-N', +654 '--no-cron', +655 action='store_true', +656 dest='nocron', +657 default=False, +658 help='do not start cron automatically') +659 +660 parser.add_option('-J', +661 '--cronjob', +662 action='store_true', +663 dest='cronjob', +664 default=False, +665 help='identify cron-initiated command') +666 +667 parser.add_option('-L', +668 '--config', +669 dest='config', +670 default='', +671 help='config file') +672 +673 parser.add_option('-F', +674 '--profiler', +675 dest='profiler_filename', +676 default=None, +677 help='profiler filename') +678 +679 parser.add_option('-t', +680 '--taskbar', +681 action='store_true', +682 dest='taskbar', +683 default=False, +684 help='use web2py gui and run in taskbar (system tray)') +685 +686 parser.add_option('', +687 '--nogui', +688 action='store_true', +689 default=False, +690 dest='nogui', +691 help='text-only, no GUI') +692 +693 parser.add_option('-A', +694 '--args', +695 action='store', +696 dest='args', +697 default=None, +698 help='should be followed by a list of arguments to be passed to script, to be used with -S, -A must be the last option') +699 +700 parser.add_option('--no-banner', +701 action='store_true', +702 default=False, +703 dest='nobanner', +704 help='Do not print header banner') +705 +706 msg = 'listen on multiple addresses: "ip:port:cert:key;ip2:port2:cert2:key2;..." (:cert:key optional; no spaces)' +707 parser.add_option('--interfaces', +708 action='store', +709 dest='interfaces', +710 default=None, +711 help=msg) +712 +713 if '-A' in sys.argv: k = sys.argv.index('-A') +714 elif '--args' in sys.argv: k = sys.argv.index('--args') +715 else: k=len(sys.argv) +716 sys.argv, other_args = sys.argv[:k], sys.argv[k+1:] +717 (options, args) = parser.parse_args() +718 options.args = [options.run] + other_args +719 global_settings.cmd_options = options +720 global_settings.cmd_args = args +721 +722 if options.quiet: +723 capture = cStringIO.StringIO() +724 sys.stdout = capture +725 logger.setLevel(logging.CRITICAL + 1) +726 else: +727 logger.setLevel(options.debuglevel) +728 +729 if options.config[-3:] == '.py': +730 options.config = options.config[:-3] +731 +732 if options.cronjob: +733 global_settings.cronjob = True # tell the world +734 options.nocron = True # don't start cron jobs +735 options.plain = True # cronjobs use a plain shell +736 +737 options.folder = os.path.abspath(options.folder) +738 +739 # accept --interfaces in the form "ip:port:cert:key;ip2:port2;ip3:port3:cert3:key3" +740 # (no spaces; optional cert:key indicate SSL) +741 # +742 if isinstance(options.interfaces, str): +743 options.interfaces = [interface.split(':') for interface in options.interfaces.split(';')] +744 for interface in options.interfaces: +745 interface[1] = int(interface[1]) # numeric port +746 options.interfaces = [tuple(interface) for interface in options.interfaces] +747 +748 if options.numthreads is not None and options.minthreads is None: +749 options.minthreads = options.numthreads # legacy +750 +751 if not options.cronjob: +752 # If we have the applications package or if we should upgrade +753 if not os.path.exists('applications/__init__.py'): +754 write_file('applications/__init__.py', '') +755 +756 if not os.path.exists('welcome.w2p') or os.path.exists('NEWINSTALL'): +757 try: +758 w2p_pack('welcome.w2p','applications/welcome') +759 os.unlink('NEWINSTALL') +760 except: +761 msg = "New installation: unable to create welcome.w2p file" +762 sys.stderr.write(msg) +763 +764 return (options, args) +
    765 +766 +
    767 -def start(cron=True): +
    768 """ Start server """ +769 +770 # ## get command line arguments +771 +772 (options, args) = console() +773 +774 if not options.nobanner: +775 print ProgramName +776 print ProgramAuthor +777 print ProgramVersion +778 +779 from dal import drivers +780 if not options.nobanner: +781 print 'Database drivers available: %s' % ', '.join(drivers) +782 +783 +784 # ## if -L load options from options.config file +785 if options.config: +786 try: +787 options2 = __import__(options.config, {}, {}, '') +788 except Exception: +789 try: +790 # Jython doesn't like the extra stuff +791 options2 = __import__(options.config) +792 except Exception: +793 print 'Cannot import config file [%s]' % options.config +794 sys.exit(1) +795 for key in dir(options2): +796 if hasattr(options,key): +797 setattr(options,key,getattr(options2,key)) +798 +799 # ## if -T run doctests (no cron) +800 if hasattr(options,'test') and options.test: +801 test(options.test, verbose=options.verbose) +802 return +803 +804 # ## if -S start interactive shell (also no cron) +805 if options.shell: +806 if options.args!=None: +807 sys.argv[:] = options.args +808 run(options.shell, plain=options.plain, bpython=options.bpython, +809 import_models=options.import_models, startfile=options.run) +810 return +811 +812 # ## if -C start cron run (extcron) and exit +813 # ## if -N or not cron disable cron in this *process* +814 # ## if --softcron use softcron +815 # ## use hardcron in all other cases +816 if options.extcron: +817 print 'Starting extcron...' +818 global_settings.web2py_crontype = 'external' +819 extcron = newcron.extcron(options.folder) +820 extcron.start() +821 extcron.join() +822 return +823 elif cron and not options.nocron and options.softcron: +824 print 'Using softcron (but this is not very efficient)' +825 global_settings.web2py_crontype = 'soft' +826 elif cron and not options.nocron: +827 print 'Starting hardcron...' +828 global_settings.web2py_crontype = 'hard' +829 newcron.hardcron(options.folder).start() +830 +831 # ## if -W install/start/stop web2py as service +832 if options.winservice: +833 if os.name == 'nt': +834 web2py_windows_service_handler(['', options.winservice], +835 options.config) +836 else: +837 print 'Error: Windows services not supported on this platform' +838 sys.exit(1) +839 return +840 +841 # ## if no password provided and havetk start Tk interface +842 # ## or start interface if we want to put in taskbar (system tray) +843 +844 try: +845 options.taskbar +846 except: +847 options.taskbar = False +848 +849 if options.taskbar and os.name != 'nt': +850 print 'Error: taskbar not supported on this platform' +851 sys.exit(1) +852 +853 root = None +854 +855 if not options.nogui: +856 try: +857 import Tkinter +858 havetk = True +859 except ImportError: +860 logger.warn('GUI not available because Tk library is not installed') +861 havetk = False +862 +863 if options.password == '<ask>' and havetk or options.taskbar and havetk: +864 try: +865 root = Tkinter.Tk() +866 except: +867 pass +868 +869 if root: +870 root.focus_force() +871 if not options.quiet: +872 presentation(root) +873 master = web2pyDialog(root, options) +874 signal.signal(signal.SIGTERM, lambda a, b: master.quit()) +875 +876 try: +877 root.mainloop() +878 except: +879 master.quit() +880 +881 sys.exit() +882 +883 # ## if no tk and no password, ask for a password +884 +885 if not root and options.password == '<ask>': +886 options.password = raw_input('choose a password:') +887 +888 if not options.password and not options.nobanner: +889 print 'no password, no admin interface' +890 +891 # ## start server +892 +893 (ip, port) = (options.ip, int(options.port)) +894 +895 if not options.nobanner: +896 print 'please visit:' +897 print '\thttp://%s:%s' % (ip, port) +898 print 'use "kill -SIGTERM %i" to shutdown the web2py server' % os.getpid() +899 +900 server = main.HttpServer(ip=ip, +901 port=port, +902 password=options.password, +903 pid_filename=options.pid_filename, +904 log_filename=options.log_filename, +905 profiler_filename=options.profiler_filename, +906 ssl_certificate=options.ssl_certificate, +907 ssl_private_key=options.ssl_private_key, +908 min_threads=options.minthreads, +909 max_threads=options.maxthreads, +910 server_name=options.server_name, +911 request_queue_size=options.request_queue_size, +912 timeout=options.timeout, +913 shutdown_timeout=options.shutdown_timeout, +914 path=options.folder, +915 interfaces=options.interfaces) +916 +917 try: +918 server.start() +919 except KeyboardInterrupt: +920 server.stop() +921 logging.shutdown() +
    922 +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.widget.IO-class.html Index: applications/examples/static/epydoc/web2py.gluon.widget.IO-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.widget.IO-class.html +++ applications/examples/static/epydoc/web2py.gluon.widget.IO-class.html @@ -0,0 +1,259 @@ + + + + + web2py.gluon.widget.IO + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module widget :: + Class IO + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class IO

    source code

    +
    +object --+
    +         |
    +        IO
    +
    + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self)
    + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature
    + source code + +
    + +
    +   + + + + + + +
    write(self, + data) + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self) +
    (Constructor) +

    +
    source code  +
    + + x.__init__(...) initializes x; see x.__class__.__doc__ for + signature +
    +
    Overrides: + object.__init__ +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.widget.web2pyDialog-class.html Index: applications/examples/static/epydoc/web2py.gluon.widget.web2pyDialog-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.widget.web2pyDialog-class.html +++ applications/examples/static/epydoc/web2py.gluon.widget.web2pyDialog-class.html @@ -0,0 +1,385 @@ + + + + + web2py.gluon.widget.web2pyDialog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module widget :: + Class web2pyDialog + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class web2pyDialog

    source code

    +
    +object --+
    +         |
    +        web2pyDialog
    +
    + +
    +Main window dialog

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + root, + options)
    + web2pyDialog constructor
    + source code + +
    + +
    +   + + + + + + +
    checkTaskBar(self)
    + Check taskbar status
    + source code + +
    + +
    +   + + + + + + +
    update(self, + text)
    + Update app text
    + source code + +
    + +
    +   + + + + + + +
    connect_pages(self)
    + Connect pages
    + source code + +
    + +
    +   + + + + + + +
    quit(self, + justHide=True)
    + Finish the program execution
    + source code + +
    + +
    +   + + + + + + +
    error(self, + message)
    + Show error message
    + source code + +
    + +
    +   + + + + + + +
    start(self)
    + Start web2py server
    + source code + +
    + +
    +   + + + + + + +
    stop(self)
    + Stop web2py server
    + source code + +
    + +
    +   + + + + + + +
    update_canvas(self)
    + Update canvas
    + source code + +
    + +
    +

    Inherited from object: + __delattr__, + __getattribute__, + __hash__, + __new__, + __reduce__, + __reduce_ex__, + __repr__, + __setattr__, + __str__ +

    +
    + + + + + + + + + +
    + + + + + +
    Properties[hide private]
    +
    +

    Inherited from object: + __class__ +

    +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    __init__(self, + root, + options) +
    (Constructor) +

    +
    source code  +
    + + web2pyDialog constructor +
    +
    Overrides: + object.__init__ +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.winservice-module.html Index: applications/examples/static/epydoc/web2py.gluon.winservice-module.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.winservice-module.html +++ applications/examples/static/epydoc/web2py.gluon.winservice-module.html @@ -0,0 +1,190 @@ + + + + + web2py.gluon.winservice + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module winservice + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Module winservice

    source code

    +

    This file is part of the web2py Web Framework Developed by Massimo Di + Pierro <mdipierro@cs.depaul.edu> and Limodou + <limodou@gmail.com>. License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)

    + This makes uses of the pywin32 package + (http://sourceforge.net/projects/pywin32/). You do not need to install + this package to use web2py.

    + + + + + + + + + + + + + +
    + + + + + +
    Classes[hide private]
    +
    +   + + Service +
    +   + + Web2pyService +
    + + + + + + + + + +
    + + + + + +
    Functions[hide private]
    +
    +   + + + + + + +
    web2py_windows_service_handler(argv=None, + opt_file='options') + source code + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.winservice-pysrc.html Index: applications/examples/static/epydoc/web2py.gluon.winservice-pysrc.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.winservice-pysrc.html +++ applications/examples/static/epydoc/web2py.gluon.winservice-pysrc.html @@ -0,0 +1,472 @@ + + + + + web2py.gluon.winservice + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module winservice + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Module web2py.gluon.winservice

    +
    +  1  #!/usr/bin/env python 
    +  2  # -*- coding: utf-8 -*- 
    +  3  """ 
    +  4  This file is part of the web2py Web Framework 
    +  5  Developed by Massimo Di Pierro <mdipierro@cs.depaul.edu> and 
    +  6  Limodou <limodou@gmail.com>. 
    +  7  License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) 
    +  8   
    +  9  This makes uses of the pywin32 package 
    + 10  (http://sourceforge.net/projects/pywin32/). 
    + 11  You do not need to install this package to use web2py. 
    + 12   
    + 13   
    + 14  """ 
    + 15   
    + 16  import time 
    + 17  import os 
    + 18  import sys 
    + 19  import traceback 
    + 20  try: 
    + 21      import win32serviceutil 
    + 22      import win32service 
    + 23      import win32event 
    + 24  except: 
    + 25      if os.name == 'nt': 
    + 26          print "Warning, winservice is unable to install the Mark Hammond Win32 extensions" 
    + 27  import servicemanager 
    + 28  import _winreg 
    + 29  from fileutils import up 
    + 30   
    + 31  __all__ = ['web2py_windows_service_handler'] 
    + 32   
    +
    33 -class Service(win32serviceutil.ServiceFramework): +
    34 + 35 _svc_name_ = '_unNamed' + 36 _svc_display_name_ = '_Service Template' + 37 +
    38 - def __init__(self, *args): +
    39 win32serviceutil.ServiceFramework.__init__(self, *args) + 40 self.stop_event = win32event.CreateEvent(None, 0, 0, None) +
    41 +
    42 - def log(self, msg): +
    43 servicemanager.LogInfoMsg(str(msg)) +
    44 +
    45 - def SvcDoRun(self): +
    46 self.ReportServiceStatus(win32service.SERVICE_START_PENDING) + 47 try: + 48 self.ReportServiceStatus(win32service.SERVICE_RUNNING) + 49 self.start() + 50 win32event.WaitForSingleObject(self.stop_event, + 51 win32event.INFINITE) + 52 except: + 53 self.log(traceback.format_exc(sys.exc_info)) + 54 self.SvcStop() + 55 self.ReportServiceStatus(win32service.SERVICE_STOPPED) +
    56 +
    57 - def SvcStop(self): +
    58 self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) + 59 try: + 60 self.stop() + 61 except: + 62 self.log(traceback.format_exc(sys.exc_info)) + 63 win32event.SetEvent(self.stop_event) + 64 self.ReportServiceStatus(win32service.SERVICE_STOPPED) +
    65 + 66 # to be overridden + 67 +
    68 - def start(self): +
    69 pass +
    70 + 71 # to be overridden + 72 +
    73 - def stop(self): +
    74 pass +
    75 + 76 +
    77 -class Web2pyService(Service): +
    78 + 79 _svc_name_ = 'web2py' + 80 _svc_display_name_ = 'web2py Service' + 81 _exe_args_ = 'options' + 82 server = None + 83 +
    84 - def chdir(self): +
    85 try: + 86 h = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, + 87 r'SYSTEM\CurrentControlSet\Services\%s' + 88 % self._svc_name_) + 89 try: + 90 cls = _winreg.QueryValue(h, 'PythonClass') + 91 finally: + 92 _winreg.CloseKey(h) + 93 dir = os.path.dirname(cls) + 94 os.chdir(dir) + 95 return True + 96 except: + 97 self.log("Can't change to web2py working path; server is stopped") + 98 return False +
    99 +
    100 - def start(self): +
    101 self.log('web2py server starting') +102 if not self.chdir(): +103 return +104 if len(sys.argv) == 2: +105 opt_mod = sys.argv[1] +106 else: +107 opt_mod = self._exe_args_ +108 options = __import__(opt_mod, [], [], '') +109 if True: # legacy support for old options files, which have only (deprecated) numthreads +110 if hasattr(options, 'numthreads') and not hasattr(options, 'minthreads'): +111 options.minthreads = options.numthreads +112 if not hasattr(options, 'minthreads'): options.minthreads = None +113 if not hasattr(options, 'maxthreads'): options.maxthreads = None +114 import main +115 self.server = main.HttpServer( +116 ip=options.ip, +117 port=options.port, +118 password=options.password, +119 pid_filename=options.pid_filename, +120 log_filename=options.log_filename, +121 profiler_filename=options.profiler_filename, +122 ssl_certificate=options.ssl_certificate, +123 ssl_private_key=options.ssl_private_key, +124 min_threads=options.minthreads, +125 max_threads=options.maxthreads, +126 server_name=options.server_name, +127 request_queue_size=options.request_queue_size, +128 timeout=options.timeout, +129 shutdown_timeout=options.shutdown_timeout, +130 path=options.folder +131 ) +132 try: +133 self.server.start() +134 except: +135 +136 # self.server.stop() +137 +138 self.server = None +139 raise +
    140 +
    141 - def stop(self): +
    142 self.log('web2py server stopping') +143 if not self.chdir(): +144 return +145 if self.server: +146 self.server.stop() +147 time.sleep(1) +
    148 +149 +
    150 -def web2py_windows_service_handler(argv=None, opt_file='options'): +
    151 path = os.path.dirname(__file__) +152 classstring = os.path.normpath(os.path.join(up(path), +153 'gluon.winservice.Web2pyService')) +154 if opt_file: +155 Web2pyService._exe_args_ = opt_file +156 win32serviceutil.HandleCommandLine(Web2pyService, +157 serviceClassString=classstring, argv=['', 'install']) +158 win32serviceutil.HandleCommandLine(Web2pyService, +159 serviceClassString=classstring, argv=argv) +
    160 +161 +162 if __name__ == '__main__': +163 web2py_windows_service_handler() +164 +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.winservice.Service-class.html Index: applications/examples/static/epydoc/web2py.gluon.winservice.Service-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.winservice.Service-class.html +++ applications/examples/static/epydoc/web2py.gluon.winservice.Service-class.html @@ -0,0 +1,271 @@ + + + + + web2py.gluon.winservice.Service + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module winservice :: + Class Service + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class Service

    source code

    +
    +win32serviceutil.ServiceFramework --+
    +                                    |
    +                                   Service
    +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    __init__(self, + *args) + source code + +
    + +
    +   + + + + + + +
    log(self, + msg) + source code + +
    + +
    +   + + + + + + +
    SvcDoRun(self) + source code + +
    + +
    +   + + + + + + +
    SvcStop(self) + source code + +
    + +
    +   + + + + + + +
    start(self) + source code + +
    + +
    +   + + + + + + +
    stop(self) + source code + +
    + +
    + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + _svc_name_ = '_unNamed' +
    +   + + _svc_display_name_ = '_Service Template' +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.winservice.Web2pyService-class.html Index: applications/examples/static/epydoc/web2py.gluon.winservice.Web2pyService-class.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.winservice.Web2pyService-class.html +++ applications/examples/static/epydoc/web2py.gluon.winservice.Web2pyService-class.html @@ -0,0 +1,311 @@ + + + + + web2py.gluon.winservice.Web2pyService + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module winservice :: + Class Web2pyService + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Class Web2pyService

    source code

    +
    +win32serviceutil.ServiceFramework --+    
    +                                    |    
    +                              Service --+
    +                                        |
    +                                       Web2pyService
    +
    + +
    + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Instance Methods[hide private]
    +
    +   + + + + + + +
    chdir(self) + source code + +
    + +
    +   + + + + + + +
    start(self) + source code + +
    + +
    +   + + + + + + +
    stop(self) + source code + +
    + +
    +

    Inherited from Service: + SvcDoRun, + SvcStop, + __init__, + log +

    +
    + + + + + + + + + + + + + + + + + + +
    + + + + + +
    Class Variables[hide private]
    +
    +   + + _svc_name_ = 'web2py' +
    +   + + _svc_display_name_ = 'web2py Service' +
    +   + + _exe_args_ = 'options' +
    +   + + server = 1
    + PyMySQL: A pure-Python drop-in replacement for MySQLdb. +
    + + + + + + +
    + + + + + +
    Method Details[hide private]
    +
    + +
    + +
    + + +
    +

    start(self) +

    +
    source code  +
    + + +
    +
    Overrides: + Service.start +
    +
    +
    +
    + +
    + +
    + + +
    +

    stop(self) +

    +
    source code  +
    + + +
    +
    Overrides: + Service.stop +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.xmlrpc-module.html Index: applications/examples/static/epydoc/web2py.gluon.xmlrpc-module.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.xmlrpc-module.html +++ applications/examples/static/epydoc/web2py.gluon.xmlrpc-module.html @@ -0,0 +1,155 @@ + + + + + web2py.gluon.xmlrpc + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module xmlrpc + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    + +

    Module xmlrpc

    source code

    +This file is part of the web2py Web Framework Copyrighted by Massimo + Di Pierro <mdipierro@cs.depaul.edu> License: LGPLv3 + (http://www.gnu.org/licenses/lgpl.html)

    + + + + + + + + + + +
    + + + + + +
    Functions[hide private]
    +
    +   + + + + + + +
    handler(request, + response, + methods) + source code + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/epydoc/web2py.gluon.xmlrpc-pysrc.html Index: applications/examples/static/epydoc/web2py.gluon.xmlrpc-pysrc.html ================================================================== --- applications/examples/static/epydoc/web2py.gluon.xmlrpc-pysrc.html +++ applications/examples/static/epydoc/web2py.gluon.xmlrpc-pysrc.html @@ -0,0 +1,147 @@ + + + + + web2py.gluon.xmlrpc + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Package web2py :: + Package gluon :: + Module xmlrpc + + + + + + +
    [hide private]
    [frames] | no frames]
    +
    +

    Source Code for Module web2py.gluon.xmlrpc

    +
    + 1  #!/usr/bin/env python 
    + 2  # -*- coding: utf-8 -*- 
    + 3   
    + 4  """ 
    + 5  This file is part of the web2py Web Framework 
    + 6  Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> 
    + 7  License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) 
    + 8  """ 
    + 9   
    +10  from SimpleXMLRPCServer import SimpleXMLRPCDispatcher 
    +11   
    +12   
    +
    13 -def handler(request, response, methods): +
    14 response.session_id = None # no sessions for xmlrpc +15 dispatcher = SimpleXMLRPCDispatcher(allow_none=True, encoding=None) +16 for method in methods: +17 dispatcher.register_function(method) +18 dispatcher.register_introspection_functions() +19 response.headers['Content-Type'] = 'text/xml' +20 dispatch = getattr(dispatcher, '_dispatch', None) +21 return dispatcher._marshaled_dispatch(request.body.read(), dispatch) +
    22 +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + ADDED applications/examples/static/favicon.ico Index: applications/examples/static/favicon.ico ================================================================== --- applications/examples/static/favicon.ico +++ applications/examples/static/favicon.ico cannot compute difference between binary files ADDED applications/examples/static/img/Menu-2.png Index: applications/examples/static/img/Menu-2.png ================================================================== --- applications/examples/static/img/Menu-2.png +++ applications/examples/static/img/Menu-2.png cannot compute difference between binary files ADDED applications/examples/static/img/Stickers1.png Index: applications/examples/static/img/Stickers1.png ================================================================== --- applications/examples/static/img/Stickers1.png +++ applications/examples/static/img/Stickers1.png cannot compute difference between binary files ADDED applications/examples/static/img/Stickers2.png Index: applications/examples/static/img/Stickers2.png ================================================================== --- applications/examples/static/img/Stickers2.png +++ applications/examples/static/img/Stickers2.png cannot compute difference between binary files ADDED applications/examples/static/img/Stickers3.png Index: applications/examples/static/img/Stickers3.png ================================================================== --- applications/examples/static/img/Stickers3.png +++ applications/examples/static/img/Stickers3.png cannot compute difference between binary files ADDED applications/examples/static/img/Stickers4.png Index: applications/examples/static/img/Stickers4.png ================================================================== --- applications/examples/static/img/Stickers4.png +++ applications/examples/static/img/Stickers4.png cannot compute difference between binary files ADDED applications/examples/static/img/Stickers5.png Index: applications/examples/static/img/Stickers5.png ================================================================== --- applications/examples/static/img/Stickers5.png +++ applications/examples/static/img/Stickers5.png cannot compute difference between binary files ADDED applications/examples/static/img/Stickers6.png Index: applications/examples/static/img/Stickers6.png ================================================================== --- applications/examples/static/img/Stickers6.png +++ applications/examples/static/img/Stickers6.png cannot compute difference between binary files ADDED applications/examples/static/img/Stickers7.png Index: applications/examples/static/img/Stickers7.png ================================================================== --- applications/examples/static/img/Stickers7.png +++ applications/examples/static/img/Stickers7.png cannot compute difference between binary files ADDED applications/examples/static/img/Stickers8.png Index: applications/examples/static/img/Stickers8.png ================================================================== --- applications/examples/static/img/Stickers8.png +++ applications/examples/static/img/Stickers8.png cannot compute difference between binary files ADDED applications/examples/static/img/back-02.png Index: applications/examples/static/img/back-02.png ================================================================== --- applications/examples/static/img/back-02.png +++ applications/examples/static/img/back-02.png cannot compute difference between binary files ADDED applications/examples/static/img/back-03.png Index: applications/examples/static/img/back-03.png ================================================================== --- applications/examples/static/img/back-03.png +++ applications/examples/static/img/back-03.png cannot compute difference between binary files ADDED applications/examples/static/img/back-04.png Index: applications/examples/static/img/back-04.png ================================================================== --- applications/examples/static/img/back-04.png +++ applications/examples/static/img/back-04.png cannot compute difference between binary files ADDED applications/examples/static/img/back-05.png Index: applications/examples/static/img/back-05.png ================================================================== --- applications/examples/static/img/back-05.png +++ applications/examples/static/img/back-05.png cannot compute difference between binary files ADDED applications/examples/static/img/back-R-02.png Index: applications/examples/static/img/back-R-02.png ================================================================== --- applications/examples/static/img/back-R-02.png +++ applications/examples/static/img/back-R-02.png cannot compute difference between binary files ADDED applications/examples/static/img/demo.png Index: applications/examples/static/img/demo.png ================================================================== --- applications/examples/static/img/demo.png +++ applications/examples/static/img/demo.png cannot compute difference between binary files ADDED applications/examples/static/img/favicon.ico Index: applications/examples/static/img/favicon.ico ================================================================== --- applications/examples/static/img/favicon.ico +++ applications/examples/static/img/favicon.ico cannot compute difference between binary files ADDED applications/examples/static/img/favicon.png Index: applications/examples/static/img/favicon.png ================================================================== --- applications/examples/static/img/favicon.png +++ applications/examples/static/img/favicon.png cannot compute difference between binary files ADDED applications/examples/static/img/gluon.png Index: applications/examples/static/img/gluon.png ================================================================== --- applications/examples/static/img/gluon.png +++ applications/examples/static/img/gluon.png cannot compute difference between binary files ADDED applications/examples/static/img/icons/1283521892_my-account.png Index: applications/examples/static/img/icons/1283521892_my-account.png ================================================================== --- applications/examples/static/img/icons/1283521892_my-account.png +++ applications/examples/static/img/icons/1283521892_my-account.png cannot compute difference between binary files ADDED applications/examples/static/img/icons/1283522082_phone.png Index: applications/examples/static/img/icons/1283522082_phone.png ================================================================== --- applications/examples/static/img/icons/1283522082_phone.png +++ applications/examples/static/img/icons/1283522082_phone.png cannot compute difference between binary files ADDED applications/examples/static/img/icons/1283522094_email.png Index: applications/examples/static/img/icons/1283522094_email.png ================================================================== --- applications/examples/static/img/icons/1283522094_email.png +++ applications/examples/static/img/icons/1283522094_email.png cannot compute difference between binary files ADDED applications/examples/static/img/icons/1283522104_sign-up.png Index: applications/examples/static/img/icons/1283522104_sign-up.png ================================================================== --- applications/examples/static/img/icons/1283522104_sign-up.png +++ applications/examples/static/img/icons/1283522104_sign-up.png cannot compute difference between binary files ADDED applications/examples/static/img/icons/1283522131_contact.png Index: applications/examples/static/img/icons/1283522131_contact.png ================================================================== --- applications/examples/static/img/icons/1283522131_contact.png +++ applications/examples/static/img/icons/1283522131_contact.png cannot compute difference between binary files ADDED applications/examples/static/img/icons/1283522158_login.png Index: applications/examples/static/img/icons/1283522158_login.png ================================================================== --- applications/examples/static/img/icons/1283522158_login.png +++ applications/examples/static/img/icons/1283522158_login.png cannot compute difference between binary files ADDED applications/examples/static/img/icons/1283522183_twitter.png Index: applications/examples/static/img/icons/1283522183_twitter.png ================================================================== --- applications/examples/static/img/icons/1283522183_twitter.png +++ applications/examples/static/img/icons/1283522183_twitter.png cannot compute difference between binary files ADDED applications/examples/static/img/icons/1283522183_video.png Index: applications/examples/static/img/icons/1283522183_video.png ================================================================== --- applications/examples/static/img/icons/1283522183_video.png +++ applications/examples/static/img/icons/1283522183_video.png cannot compute difference between binary files ADDED applications/examples/static/img/icons/1283522207_library.png Index: applications/examples/static/img/icons/1283522207_library.png ================================================================== --- applications/examples/static/img/icons/1283522207_library.png +++ applications/examples/static/img/icons/1283522207_library.png cannot compute difference between binary files ADDED applications/examples/static/img/icons/1283522229_cv.png Index: applications/examples/static/img/icons/1283522229_cv.png ================================================================== --- applications/examples/static/img/icons/1283522229_cv.png +++ applications/examples/static/img/icons/1283522229_cv.png cannot compute difference between binary files ADDED applications/examples/static/img/icons/1283522239_order-2.png Index: applications/examples/static/img/icons/1283522239_order-2.png ================================================================== --- applications/examples/static/img/icons/1283522239_order-2.png +++ applications/examples/static/img/icons/1283522239_order-2.png cannot compute difference between binary files ADDED applications/examples/static/img/icons/1283523038_multi-agents.png Index: applications/examples/static/img/icons/1283523038_multi-agents.png ================================================================== --- applications/examples/static/img/icons/1283523038_multi-agents.png +++ applications/examples/static/img/icons/1283523038_multi-agents.png cannot compute difference between binary files ADDED applications/examples/static/img/icons/1283523136_chat.png Index: applications/examples/static/img/icons/1283523136_chat.png ================================================================== --- applications/examples/static/img/icons/1283523136_chat.png +++ applications/examples/static/img/icons/1283523136_chat.png cannot compute difference between binary files ADDED applications/examples/static/img/icons/1283523297_email.png Index: applications/examples/static/img/icons/1283523297_email.png ================================================================== --- applications/examples/static/img/icons/1283523297_email.png +++ applications/examples/static/img/icons/1283523297_email.png cannot compute difference between binary files ADDED applications/examples/static/img/icons/1283523332_calculator.png Index: applications/examples/static/img/icons/1283523332_calculator.png ================================================================== --- applications/examples/static/img/icons/1283523332_calculator.png +++ applications/examples/static/img/icons/1283523332_calculator.png cannot compute difference between binary files ADDED applications/examples/static/img/icons/1283523626_search.png Index: applications/examples/static/img/icons/1283523626_search.png ================================================================== --- applications/examples/static/img/icons/1283523626_search.png +++ applications/examples/static/img/icons/1283523626_search.png cannot compute difference between binary files ADDED applications/examples/static/img/icons/1283524371_calculator.png Index: applications/examples/static/img/icons/1283524371_calculator.png ================================================================== --- applications/examples/static/img/icons/1283524371_calculator.png +++ applications/examples/static/img/icons/1283524371_calculator.png cannot compute difference between binary files ADDED applications/examples/static/img/icons/1283524386_mobile.png Index: applications/examples/static/img/icons/1283524386_mobile.png ================================================================== --- applications/examples/static/img/icons/1283524386_mobile.png +++ applications/examples/static/img/icons/1283524386_mobile.png cannot compute difference between binary files ADDED applications/examples/static/img/icons/1283524452_cloud-filled.png Index: applications/examples/static/img/icons/1283524452_cloud-filled.png ================================================================== --- applications/examples/static/img/icons/1283524452_cloud-filled.png +++ applications/examples/static/img/icons/1283524452_cloud-filled.png cannot compute difference between binary files ADDED applications/examples/static/img/icons/1283524467_home.png Index: applications/examples/static/img/icons/1283524467_home.png ================================================================== --- applications/examples/static/img/icons/1283524467_home.png +++ applications/examples/static/img/icons/1283524467_home.png cannot compute difference between binary files ADDED applications/examples/static/img/icons/1285713038_video32.png Index: applications/examples/static/img/icons/1285713038_video32.png ================================================================== --- applications/examples/static/img/icons/1285713038_video32.png +++ applications/examples/static/img/icons/1285713038_video32.png cannot compute difference between binary files ADDED applications/examples/static/img/icons/CHAT_32x32-32.png Index: applications/examples/static/img/icons/CHAT_32x32-32.png ================================================================== --- applications/examples/static/img/icons/CHAT_32x32-32.png +++ applications/examples/static/img/icons/CHAT_32x32-32.png cannot compute difference between binary files ADDED applications/examples/static/img/icons/FILE_32x32-32.png Index: applications/examples/static/img/icons/FILE_32x32-32.png ================================================================== --- applications/examples/static/img/icons/FILE_32x32-32.png +++ applications/examples/static/img/icons/FILE_32x32-32.png cannot compute difference between binary files ADDED applications/examples/static/img/icons/FOLDER - DOWNLOAD_32x32-32.png Index: applications/examples/static/img/icons/FOLDER - DOWNLOAD_32x32-32.png ================================================================== --- applications/examples/static/img/icons/FOLDER - DOWNLOAD_32x32-32.png +++ applications/examples/static/img/icons/FOLDER - DOWNLOAD_32x32-32.png cannot compute difference between binary files ADDED applications/examples/static/img/icons/FOLDER - FONTS_32x32-32.png Index: applications/examples/static/img/icons/FOLDER - FONTS_32x32-32.png ================================================================== --- applications/examples/static/img/icons/FOLDER - FONTS_32x32-32.png +++ applications/examples/static/img/icons/FOLDER - FONTS_32x32-32.png cannot compute difference between binary files ADDED applications/examples/static/img/icons/FOLDER - GAMES_32x32-32.png Index: applications/examples/static/img/icons/FOLDER - GAMES_32x32-32.png ================================================================== --- applications/examples/static/img/icons/FOLDER - GAMES_32x32-32.png +++ applications/examples/static/img/icons/FOLDER - GAMES_32x32-32.png cannot compute difference between binary files ADDED applications/examples/static/img/icons/FOLDER - INTERNET_32x32-32.png Index: applications/examples/static/img/icons/FOLDER - INTERNET_32x32-32.png ================================================================== --- applications/examples/static/img/icons/FOLDER - INTERNET_32x32-32.png +++ applications/examples/static/img/icons/FOLDER - INTERNET_32x32-32.png cannot compute difference between binary files ADDED applications/examples/static/img/icons/FOLDER - MUSIC_32x32-32.png Index: applications/examples/static/img/icons/FOLDER - MUSIC_32x32-32.png ================================================================== --- applications/examples/static/img/icons/FOLDER - MUSIC_32x32-32.png +++ applications/examples/static/img/icons/FOLDER - MUSIC_32x32-32.png cannot compute difference between binary files ADDED applications/examples/static/img/icons/FOLDER - PICTURES_32x32-32.png Index: applications/examples/static/img/icons/FOLDER - PICTURES_32x32-32.png ================================================================== --- applications/examples/static/img/icons/FOLDER - PICTURES_32x32-32.png +++ applications/examples/static/img/icons/FOLDER - PICTURES_32x32-32.png cannot compute difference between binary files ADDED applications/examples/static/img/icons/FOLDER - PRINTER_32x32-32.png Index: applications/examples/static/img/icons/FOLDER - PRINTER_32x32-32.png ================================================================== --- applications/examples/static/img/icons/FOLDER - PRINTER_32x32-32.png +++ applications/examples/static/img/icons/FOLDER - PRINTER_32x32-32.png cannot compute difference between binary files ADDED applications/examples/static/img/icons/FOLDER_32x32-32.png Index: applications/examples/static/img/icons/FOLDER_32x32-32.png ================================================================== --- applications/examples/static/img/icons/FOLDER_32x32-32.png +++ applications/examples/static/img/icons/FOLDER_32x32-32.png cannot compute difference between binary files ADDED applications/examples/static/img/icons/HELP_32x32-32.png Index: applications/examples/static/img/icons/HELP_32x32-32.png ================================================================== --- applications/examples/static/img/icons/HELP_32x32-32.png +++ applications/examples/static/img/icons/HELP_32x32-32.png cannot compute difference between binary files ADDED applications/examples/static/img/icons/NETWORK - FOLDER OPEN_32x32-32.png Index: applications/examples/static/img/icons/NETWORK - FOLDER OPEN_32x32-32.png ================================================================== --- applications/examples/static/img/icons/NETWORK - FOLDER OPEN_32x32-32.png +++ applications/examples/static/img/icons/NETWORK - FOLDER OPEN_32x32-32.png cannot compute difference between binary files ADDED applications/examples/static/img/icons/TV_32x32-32.png Index: applications/examples/static/img/icons/TV_32x32-32.png ================================================================== --- applications/examples/static/img/icons/TV_32x32-32.png +++ applications/examples/static/img/icons/TV_32x32-32.png cannot compute difference between binary files ADDED applications/examples/static/img/icons/TWEET DECK_32x32-32.png Index: applications/examples/static/img/icons/TWEET DECK_32x32-32.png ================================================================== --- applications/examples/static/img/icons/TWEET DECK_32x32-32.png +++ applications/examples/static/img/icons/TWEET DECK_32x32-32.png cannot compute difference between binary files ADDED applications/examples/static/img/icons/_README_ecqlipse.2.png.txt Index: applications/examples/static/img/icons/_README_ecqlipse.2.png.txt ================================================================== --- applications/examples/static/img/icons/_README_ecqlipse.2.png.txt +++ applications/examples/static/img/icons/_README_ecqlipse.2.png.txt @@ -0,0 +1,13 @@ +=== ecqlipse 2 (.PNG) by chrfb === + +The packet contains 116 system and 165 application PNG's in white respectively black colour. +Icon size: 128x128 plus a separate folder including 48x48, 32x32 and 16x16 pixel. + +Please refer to the Creative Commons (CC) on my site if you want to use or modify the icons. + +http://chrfb.deviantart.com + +Munich, 2010/03 + + + ADDED applications/examples/static/img/icons/appliances.png Index: applications/examples/static/img/icons/appliances.png ================================================================== --- applications/examples/static/img/icons/appliances.png +++ applications/examples/static/img/icons/appliances.png cannot compute difference between binary files ADDED applications/examples/static/img/icons/demo.png Index: applications/examples/static/img/icons/demo.png ================================================================== --- applications/examples/static/img/icons/demo.png +++ applications/examples/static/img/icons/demo.png cannot compute difference between binary files ADDED applications/examples/static/img/icons/examples.png Index: applications/examples/static/img/icons/examples.png ================================================================== --- applications/examples/static/img/icons/examples.png +++ applications/examples/static/img/icons/examples.png cannot compute difference between binary files ADDED applications/examples/static/img/icons/livechat.png Index: applications/examples/static/img/icons/livechat.png ================================================================== --- applications/examples/static/img/icons/livechat.png +++ applications/examples/static/img/icons/livechat.png cannot compute difference between binary files ADDED applications/examples/static/img/icons/plugins.png Index: applications/examples/static/img/icons/plugins.png ================================================================== --- applications/examples/static/img/icons/plugins.png +++ applications/examples/static/img/icons/plugins.png cannot compute difference between binary files ADDED applications/examples/static/img/icons/slices.png Index: applications/examples/static/img/icons/slices.png ================================================================== --- applications/examples/static/img/icons/slices.png +++ applications/examples/static/img/icons/slices.png cannot compute difference between binary files ADDED applications/examples/static/img/icons/tutorials.png Index: applications/examples/static/img/icons/tutorials.png ================================================================== --- applications/examples/static/img/icons/tutorials.png +++ applications/examples/static/img/icons/tutorials.png cannot compute difference between binary files ADDED applications/examples/static/img/icons/twitter.png Index: applications/examples/static/img/icons/twitter.png ================================================================== --- applications/examples/static/img/icons/twitter.png +++ applications/examples/static/img/icons/twitter.png cannot compute difference between binary files ADDED applications/examples/static/img/icons/usergroups.png Index: applications/examples/static/img/icons/usergroups.png ================================================================== --- applications/examples/static/img/icons/usergroups.png +++ applications/examples/static/img/icons/usergroups.png cannot compute difference between binary files ADDED applications/examples/static/img/icons/uservoice.png Index: applications/examples/static/img/icons/uservoice.png ================================================================== --- applications/examples/static/img/icons/uservoice.png +++ applications/examples/static/img/icons/uservoice.png cannot compute difference between binary files ADDED applications/examples/static/img/icons/videos.png Index: applications/examples/static/img/icons/videos.png ================================================================== --- applications/examples/static/img/icons/videos.png +++ applications/examples/static/img/icons/videos.png cannot compute difference between binary files ADDED applications/examples/static/img/logo3Tones.png Index: applications/examples/static/img/logo3Tones.png ================================================================== --- applications/examples/static/img/logo3Tones.png +++ applications/examples/static/img/logo3Tones.png cannot compute difference between binary files ADDED applications/examples/static/img/logo_bw.png Index: applications/examples/static/img/logo_bw.png ================================================================== --- applications/examples/static/img/logo_bw.png +++ applications/examples/static/img/logo_bw.png cannot compute difference between binary files ADDED applications/examples/static/img/logo_db.png Index: applications/examples/static/img/logo_db.png ================================================================== --- applications/examples/static/img/logo_db.png +++ applications/examples/static/img/logo_db.png cannot compute difference between binary files ADDED applications/examples/static/img/logo_lb.png Index: applications/examples/static/img/logo_lb.png ================================================================== --- applications/examples/static/img/logo_lb.png +++ applications/examples/static/img/logo_lb.png cannot compute difference between binary files ADDED applications/examples/static/img/netdow1.png Index: applications/examples/static/img/netdow1.png ================================================================== --- applications/examples/static/img/netdow1.png +++ applications/examples/static/img/netdow1.png cannot compute difference between binary files ADDED applications/examples/static/img/netdow2.png Index: applications/examples/static/img/netdow2.png ================================================================== --- applications/examples/static/img/netdow2.png +++ applications/examples/static/img/netdow2.png cannot compute difference between binary files ADDED applications/examples/static/img/netdow3.png Index: applications/examples/static/img/netdow3.png ================================================================== --- applications/examples/static/img/netdow3.png +++ applications/examples/static/img/netdow3.png cannot compute difference between binary files ADDED applications/examples/static/img/online_book_cover.jpg Index: applications/examples/static/img/online_book_cover.jpg ================================================================== --- applications/examples/static/img/online_book_cover.jpg +++ applications/examples/static/img/online_book_cover.jpg cannot compute difference between binary files ADDED applications/examples/static/img/tipDownloads.png Index: applications/examples/static/img/tipDownloads.png ================================================================== --- applications/examples/static/img/tipDownloads.png +++ applications/examples/static/img/tipDownloads.png cannot compute difference between binary files ADDED applications/examples/static/img/tipDownloads2.png Index: applications/examples/static/img/tipDownloads2.png ================================================================== --- applications/examples/static/img/tipDownloads2.png +++ applications/examples/static/img/tipDownloads2.png cannot compute difference between binary files ADDED applications/examples/static/img/web2py_logo.png Index: applications/examples/static/img/web2py_logo.png ================================================================== --- applications/examples/static/img/web2py_logo.png +++ applications/examples/static/img/web2py_logo.png cannot compute difference between binary files ADDED applications/examples/static/js/calendar.js Index: applications/examples/static/js/calendar.js ================================================================== --- applications/examples/static/js/calendar.js +++ applications/examples/static/js/calendar.js cannot compute difference between binary files ADDED applications/examples/static/js/jquery.js Index: applications/examples/static/js/jquery.js ================================================================== --- applications/examples/static/js/jquery.js +++ applications/examples/static/js/jquery.js cannot compute difference between binary files ADDED applications/examples/static/js/web2py_ajax.js Index: applications/examples/static/js/web2py_ajax.js ================================================================== --- applications/examples/static/js/web2py_ajax.js +++ applications/examples/static/js/web2py_ajax.js @@ -0,0 +1,97 @@ +function popup(url) { + newwindow=window.open(url,'name','height=400,width=600'); + if (window.focus) newwindow.focus(); + return false; +} +function collapse(id) { jQuery('#'+id).slideToggle(); } +function fade(id,value) { if(value>0) jQuery('#'+id).hide().fadeIn('slow'); else jQuery('#'+id).show().fadeOut('slow'); } +function ajax(u,s,t) { + query = ''; + if (typeof s == "string") { + d = jQuery(s).serialize(); + if(d){ query = d; } + } else { + pcs = []; + for(i=0; i0){query = pcs.join("&");} + } + jQuery.ajax({type: "POST", url: u, data: query, success: function(msg) { if(t) { if(t==':eval') eval(msg); else jQuery("#" + t).html(msg); } } }); +} + +String.prototype.reverse = function () { return this.split('').reverse().join('');}; +function web2py_ajax_init() { + jQuery('.hidden').hide(); + jQuery('.error').hide().slideDown('slow'); + jQuery('.flash').click(function() { jQuery(this).fadeOut('slow'); return false; }); + // jQuery('input[type=submit]').click(function(){var t=jQuery(this);t.hide();t.after('')}); + jQuery('input.integer').live('keyup', function(){this.value=this.value.reverse().replace(/[^0-9\-]|\-(?=.)/g,'').reverse();}); + jQuery('input.double,input.decimal').live('keyup', function(){this.value=this.value.reverse().replace(/[^0-9\-\.,]|[\-](?=.)|[\.,](?=[0-9]*[\.,])/g,'').reverse();}); + var confirm_message = (typeof w2p_ajax_confirm_message != 'undefined') ? w2p_ajax_confirm_message : "Are you sure you want to delete this object?"; + jQuery("input[type='checkbox'].delete").live('click', function(){ if(this.checked) if(!confirm(confirm_message)) this.checked=false; }); + var date_format = (typeof w2p_ajax_date_format != 'undefined') ? w2p_ajax_date_format : "%Y-%m-%d"; + try {jQuery("input.date").live('focus',function() {Calendar.setup({ + inputField:this, ifFormat:date_format, showsTime:false + }); }); } catch(e) {}; + var datetime_format = (typeof w2p_ajax_datetime_format != 'undefined') ? w2p_ajax_datetime_format : "%Y-%m-%d %H:%M:%S"; + try { jQuery("input.datetime").live('focus', function() {Calendar.setup({ + inputField:this, ifFormat:datetime_format, showsTime: true,timeFormat: "24" + }); }); } catch(e) {}; + + jQuery("input.time").live('focus', function() { var el = jQuery(this); + if (!el.hasClass('hasTimeEntry')) try { el.timeEntry(); } catch(e) {}; + }); +}; + +jQuery(function() { + var flash = jQuery('.flash'); + flash.hide(); + if(flash.html()) flash.slideDown(); + web2py_ajax_init(); +}); +function web2py_trap_form(action,target) { + jQuery('#'+target+' form').each(function(i){ + var form=jQuery(this); + if(!form.hasClass('no_trap')) + form.submit(function(obj){ + jQuery('.flash').hide().html(''); + web2py_ajax_page('post',action,form.serialize(),target); + return false; + }); + }); +} +function web2py_ajax_page(method,action,data,target) { + jQuery.ajax({'type':method,'url':action,'data':data, + 'beforeSend':function(xhr) { + xhr.setRequestHeader('web2py-component-location',document.location); + xhr.setRequestHeader('web2py-component-element',target);}, + 'complete':function(xhr,text){ + var html=xhr.responseText; + var content=xhr.getResponseHeader('web2py-component-content'); + var command=xhr.getResponseHeader('web2py-component-command'); + var flash=xhr.getResponseHeader('web2py-component-flash'); + var t = jQuery('#'+target); + if(content=='prepend') t.prepend(html); + else if(content=='append') t.append(html); + else if(content!='hide') t.html(html); + web2py_trap_form(action,target); + web2py_ajax_init(); + if(command) eval(command); + if(flash) jQuery('.flash').html(flash).slideDown(); + } + }); +} +function web2py_component(action,target) { + jQuery(function(){ web2py_ajax_page('get',action,null,target); }); +} +function web2py_comet(url,onmessage,onopen,onclose) { + if ("WebSocket" in window) { + var ws = new WebSocket(url); + ws.onopen = onopen?onopen:(function(){}); + ws.onmessage = onmessage; + ws.onclose = onclose?onclose:(function(){}); + return true; // supported + } else return false; // not supported +} ADDED applications/examples/static/kpax.png Index: applications/examples/static/kpax.png ================================================================== --- applications/examples/static/kpax.png +++ applications/examples/static/kpax.png cannot compute difference between binary files ADDED applications/examples/static/markmin.html Index: applications/examples/static/markmin.html ================================================================== --- applications/examples/static/markmin.html +++ applications/examples/static/markmin.html @@ -0,0 +1,41 @@ +

    Markmin markup language

    About

    This is a new markup language that we call markmin designed to produce high quality scientific papers and books and also put them online. We provide serializers for html, latex and pdf. It is implemented in the markmin2html function in the markmin2html.py.

    Example of usage:

    >>> m = "Hello **world** [[link http://web2py.com]]"
    +>>> from markmin2html import markmin2html
    +>>> print markmin2html(m)
    +>>> from markmin2latex import markmin2latex
    +>>> print markmin2latex(m)
    +>>> from markmin2pdf import markmin2pdf # requires pdflatex
    +>>> print markmin2pdf(m)

    Why?

    We wanted a markup language with the following requirements:

    • less than 100 lines of functional code
    • easy to read
    • secure
    • support table, ul, ol, code
    • support html5 video and audio elements (html serialization only)
    • can align images and resize them
    • can specify class for tables and code elements
    • can add anchors
    • does not use _ for markup (since it creates odd behavior)
    • automatically links urls
    • fast
    • easy to extend
    • supports latex and pdf including references
    • allows to describe the markup in the markup (this document is generated from markmin syntax)

    (results depend on text but in average for text ~100K markmin is 30% faster than markdown, for text ~10K it is 10x faster)

    The web2py book published by lulu, for example, was entirely generated with markmin2pdf from the online web2py wiki

    Download

    • http://web2py.googlecode.com/hg/gluon/contrib/markmin/markmin2html.py
    • http://web2py.googlecode.com/hg/gluon/contrib/markmin/markmin2latex.py
    • http://web2py.googlecode.com/hg/gluon/contrib/markmin/markmin2pdf.py

    markmin2html.py and markmin2latex.py are single files and have no web2py dependence. Their license is BSD.

    Examples

    Bold, italic, code and links

    SOURCE OUTPUT
    # title title
    ## section section
    ### subsection subsection
    **bold** bold
    ''italic'' italic
    ``verbatim`` verbatim
    http://google.com http://google.com
    [[click me #myanchor]]click me
    +

    More on links

    The format is always [[title link]]. Notice you can nest bold, italic and code inside the link title.

    Anchors

    You can place an anchor anywhere in the text using the syntax [[name]] where name is the name of the anchor. +You can then link the anchor with link, i.e. [[link #myanchor]].

    Images

    some image +This paragraph has an image aligned to the right with a width of 200px. Its is placed using the code

    [[some image http://www.web2py.com/examples/static/web2py_logo.png right 200px]].

    Unordered Lists

    - Dog
    +- Cat
    +- Mouse

    is rendered as

    • Dog
    • Cat
    • Mouse

    Two new lines between items break the list in two lists.

    Ordered Lists

    + Dog
    ++ Cat
    ++ Mouse

    is rendered as

    1. Dog
    2. Cat
    3. Mouse

    Tables

    Something like this +

    ---------
    +**A** | **B** | **C**
    +0 | 0 | X
    +0 | X | 0
    +X | 0 | 0
    +-----:abc
    +is a table and is rendered as +
    ABC
    00X
    0X0
    X00
    Four or more dashes delimit the table and | separates the columns. +The :abc at the end sets the class for the table and it is optional.

    Blockquote

    A table with a single cell is rendered as a blockquote:

    Hello world
    +

    Code, <code>, escaping and extra stuff

    def test():
    +    return "this is Python code"

    Optionally a ` inside a ``...`` block can be inserted escaped with !`!. +The :python after the markup is also optional. If present, by default, it is used to set the class of the <code> block. +The behavior can be overridden by passing an argument extra to the render function. For example:

    >>> markmin2html("``aaa``:custom",
    +       extra=dict(custom=lambda text: 'x'+text+'x'))

    generates

    'xaaax'

    (the ``...``:custom block is rendered by the custom=lambda function passed to render).

    Html5 support

    Markmin also supports the <video> and <audio> html5 tags using the notation: +

    [[title link video]]
    +[[title link audio]]

    Latex and other extensions

    Formulas can be embedded into HTML with $$formula$$. +You can use Google charts to render the formula:

    >>> LATEX = '<img src="http://chart.apis.google.com/chart?cht=tx&chl=%s" align="center"/>'
    +>>> markmin2html(text,{'latex':lambda code: LATEX % code.replace('"','"')})

    Code with syntax highlighting

    This requires a syntax highlighting tool, such as the web2py CODE helper.

    >>> extra={'code_cpp':lambda text: CODE(text,language='cpp').xml(),
    +           'code_java':lambda text: CODE(text,language='java').xml(),
    +           'code_python':lambda text: CODE(text,language='python').xml(),
    +           'code_html':lambda text: CODE(text,language='html').xml()}
    +>>> markmin2html(text,extra=extra)

    Code can now be marked up as in this example:

    ``
    +<html><body>example</body></html>
    +``:code_html

    Citations and References

    Citations are treated as internal links in html and proper citations in latex if there is a final section called "References". Items like

    - [[key]] value

    in the References will be translated into Latex

    \bibitem{key} value

    Here is an example of usage:

    As shown in Ref.``mdipierro``:cite
    +
    +## References
    +- [[mdipierro]] web2py Manual, 3rd Edition, lulu.com

    Caveats

    <ul/>, <ol/>, <code/>, <table/>, <blockquote/>, <h1/>, ..., <h6/> do not have <p>...</p> around them.

    ADDED applications/examples/static/powered_by/web2py_sticker_3d8799.png Index: applications/examples/static/powered_by/web2py_sticker_3d8799.png ================================================================== --- applications/examples/static/powered_by/web2py_sticker_3d8799.png +++ applications/examples/static/powered_by/web2py_sticker_3d8799.png cannot compute difference between binary files ADDED applications/examples/static/powered_by/web2py_sticker_3d9960.png Index: applications/examples/static/powered_by/web2py_sticker_3d9960.png ================================================================== --- applications/examples/static/powered_by/web2py_sticker_3d9960.png +++ applications/examples/static/powered_by/web2py_sticker_3d9960.png cannot compute difference between binary files ADDED applications/examples/static/powered_by/web2py_sticker_463d99.png Index: applications/examples/static/powered_by/web2py_sticker_463d99.png ================================================================== --- applications/examples/static/powered_by/web2py_sticker_463d99.png +++ applications/examples/static/powered_by/web2py_sticker_463d99.png cannot compute difference between binary files ADDED applications/examples/static/powered_by/web2py_sticker_73993d.png Index: applications/examples/static/powered_by/web2py_sticker_73993d.png ================================================================== --- applications/examples/static/powered_by/web2py_sticker_73993d.png +++ applications/examples/static/powered_by/web2py_sticker_73993d.png cannot compute difference between binary files ADDED applications/examples/static/powered_by/web2py_sticker_993d3d.png Index: applications/examples/static/powered_by/web2py_sticker_993d3d.png ================================================================== --- applications/examples/static/powered_by/web2py_sticker_993d3d.png +++ applications/examples/static/powered_by/web2py_sticker_993d3d.png cannot compute difference between binary files ADDED applications/examples/static/powered_by/web2py_sticker_993d98.png Index: applications/examples/static/powered_by/web2py_sticker_993d98.png ================================================================== --- applications/examples/static/powered_by/web2py_sticker_993d98.png +++ applications/examples/static/powered_by/web2py_sticker_993d98.png cannot compute difference between binary files ADDED applications/examples/static/powered_by/web2py_sticker_996f3d.png Index: applications/examples/static/powered_by/web2py_sticker_996f3d.png ================================================================== --- applications/examples/static/powered_by/web2py_sticker_996f3d.png +++ applications/examples/static/powered_by/web2py_sticker_996f3d.png cannot compute difference between binary files ADDED applications/examples/static/powered_by/web2py_sticker_99963d.png Index: applications/examples/static/powered_by/web2py_sticker_99963d.png ================================================================== --- applications/examples/static/powered_by/web2py_sticker_99963d.png +++ applications/examples/static/powered_by/web2py_sticker_99963d.png cannot compute difference between binary files ADDED applications/examples/static/robots.txt Index: applications/examples/static/robots.txt ================================================================== --- applications/examples/static/robots.txt +++ applications/examples/static/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Disallow: /examples/global/ +Disallow: /welcome +Disallow: /admin ADDED applications/examples/static/title.png Index: applications/examples/static/title.png ================================================================== --- applications/examples/static/title.png +++ applications/examples/static/title.png cannot compute difference between binary files ADDED applications/examples/static/web2py_contributor_agreement.pdf Index: applications/examples/static/web2py_contributor_agreement.pdf ================================================================== --- applications/examples/static/web2py_contributor_agreement.pdf +++ applications/examples/static/web2py_contributor_agreement.pdf cannot compute difference between binary files ADDED applications/examples/static/web2py_logo.png Index: applications/examples/static/web2py_logo.png ================================================================== --- applications/examples/static/web2py_logo.png +++ applications/examples/static/web2py_logo.png cannot compute difference between binary files ADDED applications/examples/static/web2py_logo_light.png Index: applications/examples/static/web2py_logo_light.png ================================================================== --- applications/examples/static/web2py_logo_light.png +++ applications/examples/static/web2py_logo_light.png cannot compute difference between binary files ADDED applications/examples/views/ajax_examples/fade.html Index: applications/examples/views/ajax_examples/fade.html ================================================================== --- applications/examples/views/ajax_examples/fade.html +++ applications/examples/views/ajax_examples/fade.html @@ -0,0 +1,8 @@ +{{extend 'layout.html'}} + +
    + + +
    + +
    {{='Hello World '*100}}
    ADDED applications/examples/views/ajax_examples/index.html Index: applications/examples/views/ajax_examples/index.html ================================================================== --- applications/examples/views/ajax_examples/index.html +++ applications/examples/views/ajax_examples/index.html @@ -0,0 +1,12 @@ +{{extend 'layout.html'}} + +

    Type something and press the button. + The last 10 entries will appear sorted in a table below.

    + +
    + + +
    +
    +
    ADDED applications/examples/views/appadmin.html Index: applications/examples/views/appadmin.html ================================================================== --- applications/examples/views/appadmin.html +++ applications/examples/views/appadmin.html @@ -0,0 +1,198 @@ +{{extend 'layout.html'}} + + +{{if request.function=='index':}} +

    {{=T("Available databases and tables")}}

    + {{if not databases:}}{{=T("No databases in this application")}}{{pass}} + {{for db in sorted(databases):}} + {{for table in databases[db].tables:}} + {{qry='%s.%s.id>0'%(db,table)}} + {{tbl=databases[db][table]}} + {{if hasattr(tbl,'_primarykey'):}} + {{if tbl._primarykey:}} + {{firstkey=tbl[tbl._primarykey[0]]}} + {{if firstkey.type in ['string','text']:}} + {{qry='%s.%s.%s!=""'%(db,table,firstkey.name)}} + {{else:}} + {{qry='%s.%s.%s>0'%(db,table,firstkey.name)}} + {{pass}} + {{else:}} + {{qry=''}} + {{pass}} + {{pass}} +

    {{=A("%s.%s" % (db,table),_href=URL('select',args=[db],vars=dict(query=qry)))}} +

    + [ {{=A(str(T('insert new'))+' '+table,_href=URL('insert',args=[db,table]))}} ] +

    + {{pass}} + {{pass}} + +{{elif request.function=='select':}} +

    {{=XML(str(T("database %s select"))%A(request.args[0],_href=URL('index'))) }} +

    + {{if table:}} + [ {{=A(str(T('insert new %s'))%table,_href=URL('insert',args=[request.args[0],table]))}} ]

    +

    {{=T("Rows in table")}}


    + {{else:}} +

    {{=T("Rows selected")}}


    + {{pass}} + {{=form}} +

    {{=T('The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.')}}
    + {{=T('Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.')}}
    + {{=T('"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN')}}

    +

    +

    {{=nrows}} {{=T("selected")}}

    + {{if start>0:}}[ {{=A(T('previous 100 rows'),_href=URL('select',args=request.args[0],vars=dict(start=start-100)))}} ]{{pass}} + {{if stop + {{linkto=URL('update',args=request.args[0])}} + {{upload=URL('download',args=request.args[0])}} + {{=SQLTABLE(rows,linkto,upload,orderby=True,_class='sortable')}} + + {{pass}} +

    {{=T("Import/Export")}}


    + [ {{=T("export as csv file")}} ] + {{if table:}} + {{=FORM(str(T('or import from csv file'))+" ",INPUT(_type='file',_name='csvfile'),INPUT(_type='hidden',_value=table,_name='table'),INPUT(_type='submit',_value='import'))}} + {{pass}} + + +{{elif request.function=='insert':}} +

    {{=T("database")}} {{=A(request.args[0],_href=URL('index'))}} + {{if hasattr(table,'_primarykey'):}} + {{fieldname=table._primarykey[0]}} + {{dbname=request.args[0]}} + {{tablename=request.args[1]}} + {{cond = table[fieldname].type in ['string','text'] and '!=""' or '>0'}} + {{=T("table")}} {{=A(tablename,_href=URL('select',args=dbname,vars=dict(query='%s.%s.%s%s'%(dbname,tablename,fieldname,cond))))}} + {{else:}} + {{=T("table")}} {{=A(request.args[1],_href=URL('select',args=request.args[0],vars=dict(query='%s.%s.id>0'%tuple(request.args[:2]))))}} + {{pass}} +

    +

    {{=T("New Record")}}


    + {{=form}} + + + +{{elif request.function=='update':}} +

    {{=T("database")}} {{=A(request.args[0],_href=URL('index'))}} + {{if hasattr(table,'_primarykey'):}} + {{fieldname=request.vars.keys()[0]}} + {{dbname=request.args[0]}} + {{tablename=request.args[1]}} + {{cond = table[fieldname].type in ['string','text'] and '!=""' or '>0'}} + {{=T("table")}} {{=A(tablename,_href=URL('select',args=dbname,vars=dict(query='%s.%s.%s%s'%(dbname,tablename,fieldname,cond))))}} + {{=T("record")}} {{=A('%s=%s'%request.vars.items()[0],_href=URL('update',args=request.args[:2],vars=request.vars))}} + {{else:}} + {{=T("table")}} {{=A(request.args[1],_href=URL('select',args=request.args[0],vars=dict(query='%s.%s.id>0'%tuple(request.args[:2]))))}} + {{=T("record id")}} {{=A(request.args[2],_href=URL('update',args=request.args[:3]))}} + {{pass}} +

    +

    {{=T("Edit current record")}}



    {{=form}} + + + +{{elif request.function=='state':}} +

    {{=T("Internal State")}}

    +

    {{=T("Current request")}}

    + {{=BEAUTIFY(request)}} +

    {{=T("Current response")}}

    + {{=BEAUTIFY(response)}} +

    {{=T("Current session")}}

    + {{=BEAUTIFY(session)}} + + +{{elif request.function == 'ccache':}} +

    Cache

    +
    +
    +
    + Statistics +
    +
    +

    Overview

    +

    + Hit Ratio: + {{=total['ratio']}}% + ({{=total['hits']}} hits + and {{=total['misses']}} misses) +

    +

    + Size of cache: + {{=total['objects']}} items, + {{=total['bytes']}} bytes + {{if total['bytes'] > 524287:}} + ({{="%.0d" % (total['bytes'] / 1048576)}} MB) + {{pass}} +

    +

    + Cache contains items up to + {{="%02d" % total['oldest'][0]}} hours + {{="%02d" % total['oldest'][1]}} minutes + {{="%02d" % total['oldest'][2]}} seconds old. +

    +

    RAM

    +

    + Hit Ratio: + {{=ram['ratio']}}% + ({{=ram['hits']}} hits + and {{=ram['misses']}} misses) +

    +

    + Size of cache: + {{=ram['objects']}} items, + {{=ram['bytes']}} bytes + {{if ram['bytes'] > 524287:}} + ({{=ram['bytes'] / 1048576}} MB) + {{pass}} +

    +

    + RAM contains items up to + {{="%02d" % ram['oldest'][0]}} hours + {{="%02d" % ram['oldest'][1]}} minutes + {{="%02d" % ram['oldest'][2]}} seconds old. +

    +

    DISK

    +

    + Hit Ratio: + {{=disk['ratio']}}% + ({{=disk['hits']}} hits + and {{=disk['misses']}} misses) +

    +

    + Size of cache: + {{=disk['objects']}} items, + {{=disk['bytes']}} bytes + {{if disk['bytes'] > 524287:}} + ({{=disk['bytes'] / 1048576}} MB) + {{pass}} +

    +

    + DISK contains items up to + {{="%02d" % disk['oldest'][0]}} hours + {{="%02d" % disk['oldest'][1]}} minutes + {{="%02d" % disk['oldest'][2]}} seconds old. +

    +
    + +
    + Manage Cache +
    +
    +

    + {{=form}} +

    +
    +
    +
    +
    +{{pass}} ADDED applications/examples/views/database_examples/buy.html Index: applications/examples/views/database_examples/buy.html ================================================================== --- applications/examples/views/database_examples/buy.html +++ applications/examples/views/database_examples/buy.html @@ -0,0 +1,7 @@ +{{extend 'layout_examples/layout_civilized.html'}} +

    Purchase form

    + {{=form}} + [ {{=A('reset purchased',_href=URL('reset_purchased'))}} | + {{=A('delete purchased',_href=URL('delete_purchased'))}} ]
    +

    Current purchases (SQL JOIN!)

    +

    {{=records}}

    ADDED applications/examples/views/database_examples/register_dog.html Index: applications/examples/views/database_examples/register_dog.html ================================================================== --- applications/examples/views/database_examples/register_dog.html +++ applications/examples/views/database_examples/register_dog.html @@ -0,0 +1,6 @@ +{{extend 'layout_examples/layout_civilized.html'}} + +

    Dog registration form

    +{{=form}} +

    Current dogs

    +{{=records}} ADDED applications/examples/views/database_examples/register_product.html Index: applications/examples/views/database_examples/register_product.html ================================================================== --- applications/examples/views/database_examples/register_product.html +++ applications/examples/views/database_examples/register_product.html @@ -0,0 +1,6 @@ +{{extend 'layout_examples/layout_civilized.html'}} + +

    Product registration form

    +{{=form}} +

    Current products

    +{{=records}} ADDED applications/examples/views/database_examples/register_user.html Index: applications/examples/views/database_examples/register_user.html ================================================================== --- applications/examples/views/database_examples/register_user.html +++ applications/examples/views/database_examples/register_user.html @@ -0,0 +1,6 @@ +{{extend 'layout_examples/layout_civilized.html'}} + +

    User registration form

    +{{=form}} +

    Current users

    +{{=records}} ADDED applications/examples/views/default/changelog.html Index: applications/examples/views/default/changelog.html ================================================================== --- applications/examples/views/default/changelog.html +++ applications/examples/views/default/changelog.html @@ -0,0 +1,5 @@ +{{extend 'layout.html'}} + +
    + {{=changelog}} +
    ADDED applications/examples/views/default/documentation.html Index: applications/examples/views/default/documentation.html ================================================================== --- applications/examples/views/default/documentation.html +++ applications/examples/views/default/documentation.html @@ -0,0 +1,18 @@ +{{extend 'layout.html'}} + +
    +
    + {{=get_content('main')}} +
    +
    + {{=get_content('official')}} +
    +
    + {{=get_content('community')}} +
    +
    + {{=get_content('more')}} +
    +
    + + ADDED applications/examples/views/default/download.html Index: applications/examples/views/default/download.html ================================================================== --- applications/examples/views/default/download.html +++ applications/examples/views/default/download.html @@ -0,0 +1,100 @@ +{{response.files.append(URL('static','css/artwork.css'))}} +{{extend 'layout.html'}} +{{import os}} +{{version = request.env.web2py_version.split('(')}} +
    +

    web2pyTM Download

    +
    +
    + + + + + + + + + + + +
    Current (for everybody)
    {{=version[0]}}
    ({{=version[1]}}
    Nightly Built (for testers)Trunk (for developers)
    + + + + + +
    +
    +

    + The source code version works on all supported platforms, including Linux, but it requires Python 2.4, 2.5, 2.6, or 2.7. + It runs on Windows and most Unix systems, including Linux and BSD. +

    +
    + +

    Instructions

    +

    After download, unzip it and click on web2py.exe (windows) or web2py.app (osx). + To run from source type:

    + {{=CODE("python2.5 web2py.py",language=None,counter='>',_class='boxCode')}} +

    or for more info type:

    + {{=CODE("python2.5 web2py.py -h",language=None,counter='>',_class='boxCode')}} + + +

    Caveats

    +

    After installation, every time you run it, web2py asks you to choose a password. This password is your administrative password. If the password is left blank, the administrative interface is disabled. The administrative interface /admin/default/index is only accessible via localhost and always requires a password.
    Any url /a/b/c maps into a call to application a, controller b.py and function c in that controller.
    You are strongly advised to also use Apache with mod_proxy or mod_wsgi to access applications in the framework. This allows better security and concurrency.

    + +

    License

    +

    Web2py code is released under LGPLv3 License. This license does not extend to third party libraries distributed with web2py (which can be MIT, BSD or Apache type licenses) nor does it extend to applications built with web2py (under the terms of the LGPL.

    +

    Applications built with web2py can be released under any license the author wishes as long they do not contain web2py code. They can link unmodified web2py libraries and they can be distributed with official web2py binaries. In particular web2py applications can be distributed in closed source. The admin interface provides a button to byte-code compile.

    +

    It is fine to distribute web2py (source or compiled) with your applications as long as you make it clear in the license where your application ends and web2py starts.

    +

    web2py is copyrighted by Massimo Di Pierro. The web2py trademark is owned by Massimo Di Pierro.

    + [read more] + +

    Applications

    +

    You can find many free and ready to use web2py applications with source code here. +

    +
    +
    + web2py artwork + + + +
    +
    + Stickers + + + + + + + +
    + Download WEB2PY artwork pack in editable .png format +
    + +
    + + Logo, Stickers and Layout developed by José V. Sousa and Bruno Rocha (at Blouweb) All rights reserved by Massimo Di Pierro © 2010 +
    + Favicon and HTML5 compatibility by Martin Mulone +
    + Icon set made by Christian Burprich licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 License +
    +
    +{{block sidebar}}{{end}} +{{block leftbadges}}{{end}} ADDED applications/examples/views/default/examples.html Index: applications/examples/views/default/examples.html ================================================================== --- applications/examples/views/default/examples.html +++ applications/examples/views/default/examples.html @@ -0,0 +1,685 @@ +{{extend 'layout.html'}} +{{import os}} + +
    +

    web2pyTM Examples

    + +
    + +

    Simple Examples

    + +

    Here are some working and complete examples that explain the basic syntax of the framework.
    + You can click on the web2py keywords (in the highlighted code!) to get documentation.

    + +

    Example {{c=1}}{{=c}}{{c+=1}}

    In controller: simple_examples.py + {{=CODE(""" + def hello1(): + return "Hello World" + """.strip(),language='web2py',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} + +

    If the controller function returns a string, that is the body of the rendered page.
    Try it here: hello1

    + +

    Example {{=c}}{{c+=1}}

    In controller: simple_examples.py + {{=CODE(""" + def hello2(): + return T("Hello World") + """.strip(),language='web2py',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} + +

    The function T() marks strings that need to be translated. Translation dictionaries can be created at /admin/default/design
    Try it here: hello2

    + +

    Example {{=c}}{{c+=1}}

    In controller: simple_examples.py + {{=CODE(""" + def hello3(): + return dict(message=T("Hello World")) + """.strip(),language='web2py',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} + + and view: simple_examples/hello3.html + {{=CODE(open(os.path.join(request.folder,'views/simple_examples/hello3.html'),'r').read(),language='html',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} +

    If you return a dictionary, the variables defined in the dictionery are visible to the view (template). +
    Try it here: hello3

    + +

    Actions can also be be rendered in other formsts like JSON, hello3.json, and XML, hello3.xml

    + +

    Example {{=c}}{{c+=1}}

    In controller: simple_examples.py + {{=CODE(""" + def hello4(): + response.view='simple_examples/hello3.html' + return dict(message=T("Hello World")) + """.strip(),language='web2py',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} +

    You can change the view, but the default is /[controller]/[function].html. If the default is not found web2py tries to render the page using the generic.html view. +
    Try it here: hello4

    + +

    Example {{=c}}{{c+=1}}

    In controller: simple_examples.py + {{=CODE(""" + def hello5(): + return HTML(BODY(H1(T('Hello World'),_style="color: red;"))).xml() # .xml to serialize + """.strip(),language='web2py',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} +

    You can also generate HTML using helper objects HTML, BODY, H1, etc. Each of these tags is a class and the views know how to render the corresponding objects. The method .xml() serializes them and produce html/xml code for the page. + Each tag, DIV for example, takes three types of arguments:

    +
      +
    • unnamed arguments, they correspond to nested tags
    • +
    • named arguments and name starts with '_'. These are mapped blindly into tag attributes and the '_' is removed. attributes without value like "READONLY" can be created with the argument "_readonly=ON".
    • +
    • named arguments and name does not start with '_'. They have a special meaning. See "value=" for INPUT, TEXTAREA, SELECT tags later. +
    +

    Try it here: hello5

    + +

    Example {{=c}}{{c+=1}}

    In controller: simple_examples.py + {{=CODE(""" + def hello6(): + response.flash=T("Hello World in a flash!") + return dict(message=T("Hello World")) + """.strip(),language='web2py',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} + +

    response.flash allows you to flash a message to the user when the page is returned. Use session.flash instead of response.flash to display a message after redirection. With default layout, you can click on the flash to make it disappear. +
    Try it here: hello6

    + +

    Example {{=c}}{{c+=1}}

    In controller: simple_examples.py + {{=CODE(""" + def status(): + return dict(request=request,session=session,response=response) + """.strip(),language='web2py',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} +

    Here we are showing the request, session and response objects using the generic.html template. +
    Try it here: status

    + +

    Example {{=c}}{{c+=1}}

    In controller: simple_examples.py + {{=CODE(""" + def redirectme(): + redirect(URL('hello3')) + """.strip(),language='web2py',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} +

    You can do redirect. +
    Try it here: redirectme

    + +

    Example {{=c}}{{c+=1}}

    In controller: simple_examples.py + {{=CODE(""" + def raisehttp(): + raise HTTP(400,"internal error") + """.strip(),language='web2py',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} +

    You can raise HTTP exceptions to return an error page. +
    Try it here: raisehttp

    + +

    Example {{=c}}{{c+=1}}

    In controller: simple_examples.py + {{=CODE(""" + def raiseexception(): + 1/0 + return 'oops' + """.strip(),language='web2py',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} +

    If an exception occurs (other than HTTP) a ticket is generated and the event is logged for the administrator. These tickets and logs can be accessed, reviewed and deleted at any later time. +
    Try it here: raiseexception

    + +

    Example {{=c}}{{c+=1}}

    In controller: simple_examples.py + {{=CODE(""" + def servejs(): + import gluon.contenttype + response.headers['Content-Type']=gluon.contenttype.contenttype('.js') + return 'alert("This is a Javascript document, it is not supposed to run!");' + """.strip(),language='web2py',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} +

    You can serve other than HTML pages by changing the contenttype via the response.headers. The gluon.contenttype module can help you figure the type of the file to be served. NOTICE: this is not necessary for static files unless you want to require authorization. +
    Try it here: servejs

    + +

    Example {{=c}}{{c+=1}}

    In controller: simple_examples.py + {{=CODE(""" + def makejson(): + return response.json(['foo', {'bar': ('baz', None, 1.0, 2)}]) + """.strip(),language='web2py',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} +

    If you are into Ajax, web2py includes gluon.contrib.simplejson, developed by Bob Ippolito. This module provides a fast and easy way to serve asynchronous content to your Ajax page. gluon.simplesjson.dumps(...) can serialize most Python types into JSON. gluon.contrib.simplejson.loads(...) performs the reverse operation. +
    Try it here: makejson

    + +

    New in web2py 1.63: Any normal action returning a dict is automatically serialized in JSON if '.json' is appended to the URL.

    + +

    Example {{=c}}{{c+=1}}

    In controller: simple_examples.py + {{=CODE(""" + def makertf(): + import gluon.contrib.pyrtf as q + doc=q.Document() + section=q.Section() + doc.Sections.append(section) + section.append('Section Title') + section.append('web2py is great. '*100) + response.headers['Content-Type']='text/rtf' + return q.dumps(doc) + """.strip(),language='web2py',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} +

    web2py also includes gluon.contrib.pyrtf, developed by Simon Cusack and revised by Grant Edwards. This module allows you to generate Rich Text Format documents including colored formatted text and pictures.
    Try it here: makertf

    + +

    Example {{=c}}{{c+=1}}

    In controller: simple_examples.py + {{=CODE(""" + def rss_aggregator(): + import datetime + import gluon.contrib.rss2 as rss2 + import gluon.contrib.feedparser as feedparser + d = feedparser.parse("http://rss.slashdot.org/Slashdot/slashdot/to") + + rss = rss2.RSS2(title=d.channel.title, + link = d.channel.link, + description = d.channel.description, + lastBuildDate = datetime.datetime.now(), + items = [ + rss2.RSSItem( + title = entry.title, + link = entry.link, + description = entry.description, + # guid = rss2.Guid('unkown'), + pubDate = datetime.datetime.now()) for entry in d.entries] + ) + response.headers['Content-Type']='application/rss+xml' + return rss2.dumps(rss) + """.strip(),language='web2py',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} +

    web2py includes gluon.contrib.rss2, developed by Dalke Scientific Software, which generates RSS2 feeds, and + gluon.contrib.feedparser, developed by Mark Pilgrim, which collects RSS and ATOM feeds. The above controller collects a slashdot feed and makes new one. +
    Try it here: rss_aggregator

    + + +

    Example {{=c}}{{c+=1}}

    In controller: simple_examples.py + {{=CODE(""" + def ajaxwiki(): + form=FORM(TEXTAREA(_id='text',_name='text'), + INPUT(_type='button',_value='markmin', + _onclick="ajax('ajaxwiki_onclick',['text'],'html')")) + return dict(form=form,html=DIV(_id='html')) + + def ajaxwiki_onclick(): + return MARKMIN(request.vars.text).xml() + """.strip(),language='web2py',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} +

    The markmin wiki markup is described here. + web2py also includes gluon.contrib.markdown.WIKI helper (markdown2) which converts WIKI markup to HTML following this syntax. In this example we added a fancy ajax effect.
    Try it here: ajaxwiki

    + +

    Session Examples

    + + +

    Example {{=c}}{{c+=1}}

    In controller: session_examples.py + {{=CODE(""" + def counter(): + if not session.counter: session.counter=0 + session.counter+=1 + return dict(counter=session.counter) + """.strip(),language='web2py',link=URL(r=request,c='global',f='vars'),_class='boxCode')}}and view: session_examples/counter.html + {{=CODE(open(os.path.join(request.folder,'views/session_examples/counter.html'),'r').read(),language='html',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} +

    Click to count. The session.counter is persistent for this user and application. Every applicaiton within the system has its own separate session management. +
    Try it here: counter

    + +

    Template Examples

    + + +

    Example {{=c}}{{c+=1}}

    In controller: template_examples.py + {{=CODE(""" + def variables(): return dict(a=10, b=20) + """.strip(),language='web2py',link=URL(r=request,c='global',f='vars'),_class='boxCode')}}and view: template_examples/variables.html + {{=CODE(open(os.path.join(request.folder,'views/template_examples/variables.html'),'r').read(),language='html',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} +

    A view (also known as template) is just an HTML file with {{...}} tags. You can put ANY python code into the tags, no need to indent but you must use pass to close blocks. The view is transformed into a python code and then executed. {{=a}} prints a.xml() or escape(str(a)). +
    Try it here: variables

    + +

    Example {{=c}}{{c+=1}}

    In controller: template_examples.py + {{=CODE(""" + def test_for(): return dict() + """.strip(),language='web2py',link=URL(r=request,c='global',f='vars'),_class='boxCode')}}and view: template_examples/test_for.html + {{=CODE(open(os.path.join(request.folder,'views/template_examples/test_for.html'),'r').read(),language='html',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} +

    You can do for and while loops. +
    Try it here: test_for

    + +

    Example {{=c}}{{c+=1}}

    In controller: template_examples.py + {{=CODE(""" + def test_if(): return dict() + """.strip(),language='web2py',link=URL(r=request,c='global',f='vars'),_class='boxCode')}}and view: template_examples/test_if.html + {{=CODE(open(os.path.join(request.folder,'views/template_examples/test_if.html'),'r').read(),language='html',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} +

    You can do if, elif, else. +
    Try it here: test_if

    + +

    Example {{=c}}{{c+=1}}

    In controller: template_examples.py + {{=CODE(""" + def test_try(): return dict() + """.strip(),language='web2py',link=URL(r=request,c='global',f='vars'),_class='boxCode')}}and view: template_examples/test_try.html + {{=CODE(open(os.path.join(request.folder,'views/template_examples/test_try.html'),'r').read(),language='html',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} +

    You can do try, except, finally. +
    Try it here: test_try

    + +

    Example {{=c}}{{c+=1}}

    In controller: template_examples.py + {{=CODE(""" + def test_def(): return dict() + """.strip(),language='web2py',link=URL(r=request,c='global',f='vars'),_class='boxCode')}}and view: template_examples/test_def.html + {{=CODE(open(os.path.join(request.folder,'views/template_examples/test_def.html'),'r').read(),language='html',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} +

    You can write functions in HTML too. +
    Try it here: test_def

    + +

    Example {{=c}}{{c+=1}}

    In controller: template_examples.py + {{=CODE(""" + def escape(): return dict(message='

    text is escaped

    ') + """.strip(),language='web2py',link=URL(r=request,c='global',f='vars'),_class='boxCode')}}and view: template_examples/escape.html + {{=CODE(open(os.path.join(request.folder,'views/template_examples/escape.html'),'r').read(),language='html',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} +

    The argument of {{=...}} is always escaped unless it is an object with a .xml() method such as link, A(...), a FORM(...), a XML(...) block, etc. +
    Try it here: escape

    + +

    Example {{=c}}{{c+=1}}

    In controller: template_examples.py + {{=CODE(""" + def xml(): + return dict(message=XML('

    text is not escaped

    ')) + """.strip(),language='web2py',link=URL(r=request,c='global',f='vars'),_class='boxCode')}}and view: template_examples/xml.html + {{=CODE(open(os.path.join(request.folder,'views/template_examples/xml.html'),'r').read(),language='html',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} +

    If you do not want to escape the argument of {{=...}} mark it as XML. +
    Try it here: xml

    + +

    Example {{=c}}{{c+=1}}

    In controller: template_examples.py + {{=CODE(""" + def beautify(): return dict(message=BEAUTIFY(request)) + """.strip(),language='web2py',link=URL(r=request,c='global',f='vars'),_class='boxCode')}}and view: template_examples/beautify.html + {{=CODE(open(os.path.join(request.folder,'views/template_examples/beautify.html'),'r').read(),language='html',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} +

    You can use BEAUTIFY to turn lists and dictionaries into organized HTML. +
    Try it here: beautify

    + +

    Layout Examples

    + + +

    Example {{=c}}{{c+=1}}

    In controller: layout_examples.py + {{=CODE(""" + def civilized(): + response.menu=[['civilized',True,URL('civilized')], + ['slick',False,URL('slick')], + ['basic',False,URL('basic')]] + response.flash='you clicked on civilized' + return dict(message="you clicked on civilized") + """.strip(),language='web2py',link=URL(r=request,c='global',f='vars'),_class='boxCode')}}and view: layout_examples/civilized.html + {{=CODE(open(os.path.join(request.folder,'views/layout_examples/civilized.html'),'r').read(),language='html',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} +

    You can specify the layout file at the top of your view. civilized Layout file is a view that somewhere in the body contains {{include}}. +
    Try it here: civilized

    + +

    Example {{=c}}{{c+=1}}

    In controller: layout_examples.py + {{=CODE(""" + def slick(): + response.menu=[['civilized',False,URL('civilized')], + ['slick',True,URL('slick')], + ['basic',False,URL('basic')]] + response.flash='you clicked on slick' + return dict(message="you clicked on slick") + """.strip(),language='web2py',link=URL(r=request,c='global',f='vars'),_class='boxCode')}}and view: layout_examples/slick.html + {{=CODE(open(os.path.join(request.folder,'views/layout_examples/slick.html'),'r').read(),language='html',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} +

    Same here, but using a different template.
    Try it here: slick

    + +

    Example {{=c}}{{c+=1}}

    In controller: layout_examples.py + {{=CODE(""" + def basic(): + response.menu=[['civilized',False,URL('civilized')], + ['slick',False,URL('slick')], + ['basic',True,URL('basic')]] + response.flash='you clicked on basic' + return dict(message="you clicked on basic") + """.strip(),language='web2py',link=URL(r=request,c='global',f='vars'),_class='boxCode')}}and view: layout_examples/basic.html + {{=CODE(open(os.path.join(request.folder,'views/layout_examples/basic.html'),'r').read(),language='html',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} +

    'layout.html' is the default template, every application has a copy of it. +
    Try it here: basic

    + +

    Form Examples

    + + +

    Example {{=c}}{{c+=1}}

    In controller: form_examples.py + {{=CODE(""" + def form(): + form=FORM(TABLE(TR("Your name:",INPUT(_type="text",_name="name",requires=IS_NOT_EMPTY())), + TR("Your email:",INPUT(_type="text",_name="email",requires=IS_EMAIL())), + TR("Admin",INPUT(_type="checkbox",_name="admin")), + TR("Sure?",SELECT('yes','no',_name="sure",requires=IS_IN_SET(['yes','no']))), + TR("Profile",TEXTAREA(_name="profile",value="write something here")), + TR("",INPUT(_type="submit",_value="SUBMIT")))) + if form.accepts(request,session): + response.flash="form accepted" + elif form.errors: + response.flash="form is invalid" + else: + response.flash="please fill the form" + return dict(form=form,vars=form.vars) + """.strip(),language='web2py',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} +

    You can use HTML helpers like FORM, INPUT, TEXTAREA, OPTION, SELECT to build forms. The "value=" attribute sets the initial value of the field (works for TEXTAREA and OPTION/SELECT too) and the requires attribute sets the validators. + FORM.accepts(..) tries to validate the form and, on success, stores vars into form.vars. On failure the error messages are stored into form.errors and shown in the form. +
    Try it here: form

    + +

    Database Examples

    + +

    You can find more examples of the web2py Database Abstraction Layer here

    + +

    Let's create a simple model with users, dogs, products and purchases (the database of an animal store). Users can have many dogs (ONE TO MANY), can buy many products and every product can have many buyers (MANY TO MANY).

    + +

    Example {{=c}}{{c+=1}}

    in model: db.py + {{=CODE(""" + db=DAL('sqlite://storage.db') + + db.define_table('users', + Field('name'), + Field('email')) + + # ONE (users) TO MANY (dogs) + db.define_table('dogs', + Field('owner_id',db.users), + Field('name'), + Field('type'), + Field('vaccinated','boolean',default=False), + Field('picture','upload',default='')) + + db.define_table('products', + Field('name'), + Field('description','text')) + + # MANY (users) TO MANY (products) + db.define_table('purchases', + Field('buyer_id',db.users), + Field('product_id',db.products), + Field('quantity','integer')) + + purchased=((db.users.id==db.purchases.buyer_id)&(db.products.id==db.purchases.product_id)) + + db.users.name.requires=IS_NOT_EMPTY() + db.users.email.requires=[IS_EMAIL(), IS_NOT_IN_DB(db,'users.email')] + db.dogs.owner_id.requires=IS_IN_DB(db,'users.id','users.name') + db.dogs.name.requires=IS_NOT_EMPTY() + db.dogs.type.requires=IS_IN_SET(['small','medium','large']) + db.purchases.buyer_id.requires=IS_IN_DB(db,'users.id','users.name') + db.purchases.product_id.requires=IS_IN_DB(db,'products.id','products.name') + db.purchases.quantity.requires=IS_INT_IN_RANGE(0,10) + """.strip(),language='web2py',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} +

    + Tables are created if they do not exist (try... except). + Here "purchased" is an SQLQuery object, "db(purchased)" would be a SQLSet objects. A SQLSet object can be selected, updated, deleted. SQLSets can also be intersected. Allowed field types are string, integer, password, text, blob, upload, date, time, datetime, references(*), and id(*). The id field is there by default and must not be declared. references are for one to many and many to many as in the example above. For strings you should specify a length or you get length=32.

    + You can use db.tablename.fieldname.requires= to set restrictions on the field values. These restrictions are automatically converted into widgets when generating forms from the table with SQLFORM(db.tablename). +

    + define_tables creates the table and attempts a migration if table has changed or if database name has changed since last time. If you know you already have the table in the database and you do not want to attempt a migration add one last argument to define_table migrate=False.

    + + +

    Example {{=c}}{{c+=1}}

    In controller: database_examples.py + {{=CODE(""" + response.menu=[['Register User',False,URL('register_user')], + ['Register Dog',False,URL('register_dog')], + ['Register Product',False,URL('register_product')], + ['Buy product',False,URL('buy')]] + + def register_user(): + ### create an insert form from the table + form=SQLFORM(db.users) + ### if form is correct, perform the insert + if form.accepts(request,session): + response.flash='new record inserted' + ### and get a list of all users + records=SQLTABLE(db().select(db.users.ALL)) + return dict(form=form,records=records) + """.strip(),language='web2py',link=URL(r=request,c='global',f='vars'),_class='boxCode')}}and view: database_examples/register_user.html + {{=CODE(open(os.path.join(request.folder,'views/database_examples/register_user.html'),'r').read(),language='html',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} +

    This is a simple user registration form. SQLFORM takes a table and returns the corresponding entry form with validators, etc. SQLFORM.accepts is similar to FORM.accepts but, if form is validated, the corresponding insert is also performed. SQLFORM can also do update and edit if a record is passed as its second argument. + SQLTABLE instead turns a set of records (result of a select) into an HTML table with links as specified by its optional parameters. + The response.menu on top is just a variable used by the layout to make the navigation menu for all functions in this controller.
    + Try it here: register_user

    + +

    Example {{=c}}{{c+=1}}

    In controller: database_examples.py + {{=CODE(""" + def register_dog(): + form=SQLFORM(db.dogs) + if form.accepts(request,session): + response.flash='new record inserted' + download=URL('download') # to see the picture + records=SQLTABLE(db().select(db.dogs.ALL),upload=download) + return dict(form=form,records=records) + """.strip(),language='web2py',link=URL(r=request,c='global',f='vars'),_class='boxCode')}}and view: database_examples/register_dog.html + {{=CODE(open(os.path.join(request.folder,'views/database_examples/register_dog.html'),'r').read(),language='html',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} +

    Here is a dog registration form. Notice that the "image" (type "upload") field is rendered into a <INPUT type="file"> html tag. SQLFORM.accepts(...) handles the upload of the file into the uploads/ folder. +
    Try it here: register_dog

    + +

    Example {{=c}}{{c+=1}}

    In controller: database_examples.py + {{=CODE(""" + def register_product(): + form=SQLFORM(db.products) + if form.accepts(request,session): + response.flash='new record inserted' + records=SQLTABLE(db().select(db.products.ALL)) + return dict(form=form,records=records) + """.strip(),language='web2py',link=URL(r=request,c='global',f='vars'),_class='boxCode')}}and view: database_examples/register_product.html + {{=CODE(open(os.path.join(request.folder,'views/database_examples/register_product.html'),'r').read(),language='html',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} +

    Nothing new here. +
    Try it here: register_product

    + +

    Example {{=c}}{{c+=1}}

    In controller: database_examples.py + {{=CODE(""" + def buy(): + form=FORM(TABLE(TR("Buyer id:",INPUT(_type="text",_name="buyer_id",requires=IS_NOT_EMPTY())), + TR("Product id:",INPUT(_type="text",_name="product_id",requires=IS_NOT_EMPTY())), + TR("Quantity:",INPUT(_type="text",_name="quantity",requires=IS_INT_IN_RANGE(1,100))), + TR("",INPUT(_type="submit",_value="Order")))) + if form.accepts(request,session): + ### check if user is in the database + if len(db(db.users.id==form.vars.buyer_id).select())==0: + form.errors.buyer_id="buyer not in database" + ### check if product is in the database + if len(db(db.products.id==form.vars.product_id).select())==0: + form.errors.product_id="product not in database" + ### if no errors + if len(form.errors)==0: + ### get a list of same purchases by same user + purchases=db((db.purchases.buyer_id==form.vars.buyer_id)& + (db.purchases.product_id==form.vars.product_id)).select() + ### if list contains a record, update that record + if len(purchases)>0: + purchases[0].update_record(quantity=purchases[0].quantity+form.vars.quantity) + ### or insert a new record in table + else: + db.purchases.insert(buyer_id=form.vars.buyer_id, + product_id=form.vars.product_id, + quantity=form.vars.quantity) + response.flash="product purchased!" + if len(form.errors): response.flash="invalid valus in form!" + ### now get a list of all purchases + records=db(purchased).select(db.users.name,db.purchases.quantity,db.products.name) + return dict(form=form,records=SQLTABLE(records),vars=form.vars,vars2=request.vars) + """.strip(),language='web2py',link=URL(r=request,c='global',f='vars'),_class='boxCode')}}and view: database_examples/buy.html + {{=CODE(open(os.path.join(request.folder,'views/database_examples/buy.html'),'r').read(),language='html',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} +

    Here is a rather sophisticated buy form. It checks that the buyer and the product are in the database and updates the corresponding record or inserts a new purchase. It also does a JOIN to list all purchases. +
    Try it here: buy

    + +

    Example {{=c}}{{c+=1}}

    In controller: database_examples.py + {{=CODE(""" + def delete_purchased(): + db(db.purchases.id>0).delete() + redirect(URL('buy')) + """.strip(),language='web2py',link=URL(r=request,c='global',f='vars'),_class='boxCode')}}Try it here: delete_purchased + +

    Example {{=c}}{{c+=1}}

    In controller: database_examples.py + {{=CODE(""" + def reset_purchased(): + db(db.purchases.id>0).update(quantity=0) + redirect(URL('buy')) + """.strip(),language='web2py',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} +

    This is an update on an SQLSet. (db.purchase.id>0 identifies the set containing only table db.purchases.) +
    Try it here: reset_purchased

    + +

    Example {{=c}}{{c+=1}}

    In controller: database_examples.py + {{=CODE(""" + def download(): + return response.download(request,db) + """.strip(),language='web2py',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} +

    This controller allows users to download the uploaded pictures of the dogs. + Remember the upload=URL(...'download'...) statement in the register_dog function. Notice that in the URL path /application/controller/function/a/b/etc a, b, etc are passed to the controller as request.args[0], request.args[1], etc. Since the URL is validated request.args[] always contain valid filenames and no '~' or '..' etc. This is useful to allow visitors to link uploaded files.

    + +

    Cache Examples

    + +

    Example {{=c}}{{c+=1}}

    In controller: cache_examples.py + {{=CODE(""" + def cache_in_ram(): + import time + t=cache.ram('time',lambda:time.ctime(),time_expire=5) + return dict(time=t,link=A('click to reload',_href=URL(r=request))) + """.strip(),language='web2py',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} +

    The output of lambda:time.ctime() is cached in ram for 5 seconds. The string 'time' is used as cache key. +
    Try it here: cache_in_ram

    + + +

    Example {{=c}}{{c+=1}}

    In controller: cache_examples.py + {{=CODE(""" + def cache_on_disk(): + import time + t=cache.disk('time',lambda:time.ctime(),time_expire=5) + return dict(time=t,link=A('click to reload',_href=URL(r=request))) + """.strip(),language='web2py',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} +

    The output of lambda:time.ctime() is cached on disk (using the shelve module) for 5 seconds. +
    Try it here: cache_on_disk

    + +

    Example {{=c}}{{c+=1}}

    In controller: cache_examples.py + {{=CODE(""" + def cache_in_ram_and_disk(): + import time + t=cache.ram('time',lambda:cache.disk('time', + lambda:time.ctime(),time_expire=5),time_expire=5) + return dict(time=t,link=A('click to reload',_href=URL(r=request))) + """.strip(),language='web2py',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} +

    The output of lambda:time.ctime() is cached on disk (using the shelve module) and then in ram for 5 seconds. web2py looks in ram first and if not there it looks on disk. If it is not on disk it calls the function. This is useful in a multiprocess type of environment. The two times do not have to be the same. +
    Try it here: cache_in_ram_and_disk

    + + +

    Example {{=c}}{{c+=1}}

    In controller: cache_examples.py + {{=CODE(""" + @cache(request.env.path_info,time_expire=5,cache_model=cache.ram) + def cache_controller_in_ram(): + import time + t=time.ctime() + return dict(time=t,link=A('click to reload',_href=URL(r=request)))""".strip(),language='web2py',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} +

    Here the entire controller (dictionary) is cached in ram for 5 seconds. The result of a select cannot be cached unless it is first serialized into a table lambda:SQLTABLE(db().select(db.users.ALL)).xml(). You can read below for an even better way to do it. +
    Try it here: cache_controller_in_ram

    + +

    Example {{=c}}{{c+=1}}

    In controller: cache_examples.py + {{=CODE(""" + @cache(request.env.path_info,time_expire=5,cache_model=cache.disk) + def cache_controller_on_disk(): + import time + t=time.ctime() + return dict(time=t,link=A('click to reload',_href=URL(r=request))) + """.strip(),language='web2py',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} +

    Here the entire controller (dictionary) is cached on disk for 5 seconds. This will not work if the dictionary contains unpickleable objects. +
    Try it here: cache_controller_on_disk

    + +

    Example {{=c}}{{c+=1}}

    In controller: cache_examples.py + {{=CODE(""" + @cache(request.env.path_info,time_expire=5,cache_model=cache.ram) + def cache_controller_and_view(): + import time + t=time.ctime() + d=dict(time=t,link=A('click to reload',_href=URL(r=request))) + return response.render(d) + """.strip(),language='web2py',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} +

    response.render(d) renders the dictionary inside the controller, so everything is cached now for 5 seconds. This is best and fastest way of caching! +
    Try it here: cache_controller_and_view

    + +

    Example {{=c}}{{c+=1}}

    In controller: cache_examples.py + {{=CODE(""" + def cache_db_select(): + import time + db.users.insert(name='somebody',email='gluon@mdp.cti.depaul.edu') + records=db().select(db.users.ALL,cache=(cache.ram,5)) + if len(records)>20: db(db.users.id>0).delete() + return dict(records=records) + """.strip(),language='web2py',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} +

    The results of a select are complex unpickleable objects that cannot be cached using the previous method, but the select command takes an argument cache=(cache_model,time_expire) and will cache the result of the query accordingly. Notice that the key is not necessary since key is generated based on the database name and the select string. +
    Try it here: cache_db_select

    + +

    Ajax Examples

    + + +

    Example {{=c}}{{c+=1}}

    In controller: ajax_examples.py + {{=CODE(""" + def index(): + return dict() + + def data(): + if not session.m or len(session.m)==10: session.m=[] + if request.vars.q: session.m.append(request.vars.q) + session.m.sort() + return TABLE(*[TR(v) for v in session.m]).xml() + """.strip(),language='web2py',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} + In view: ajax_examples/index.html + {{=CODE(open(os.path.join(request.folder,'views/ajax_examples/index.html'),'r').read(),language='html',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} +

    The javascript function "ajax" is provided in "web2py_ajax.html" and included by "layout.html". It takes three arguments, a url, a list of ids and a target id. When called, it sends to the url (via a get) the values of the ids and display the response in the value (of innerHTML) of the target id. +
    Try it here: index

    + +

    Example {{=c}}{{c+=1}}

    In controller: ajax_examples.py + {{=CODE(""" + def flash(): + response.flash='this text should appear!' + return dict() + """.strip(),language='web2py',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} +

    Try it here: flash

    + +

    Example {{=c}}{{c+=1}}

    In controller: ajax_examples.py + {{=CODE(""" + def fade(): + return dict() + """.strip(),language='web2py',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} + In view: ajax_examples/fade.html
    + {{=CODE(open(os.path.join(request.folder,'views/ajax_examples/fade.html'),'r').read(),language='html',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} +

    Try it here: fade

    + +

    Excel-like spreadsheet via Ajax

    + Web2py includes a widget that acts like an Excel-like spreadsheet and can be used to build forms + [read more]. + +

    Testing Examples

    + + +

    Example {{=c}}{{c+=1}}

    +

    Using the Python doctest notation it is possible to write tests for all controller functions. Tests are then run via the administrative interface which generates a report. Here is an example of a test in the code: + {{=CODE(""" + def index(): + ''' + This is a docstring. The following 3 lines are a doctest: + >>> request.vars.name='Max' + >>> index() + {'name': 'Max'} + ''' + return dict(name=request.vars.name) + """.strip(),language='web2py',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} +

    + +

    Streaming Examples

    + + +

    Example {{=c}}{{c+=1}}

    +

    It is very easy in web2py to stream large files. Here is an example of a controller that does so:

    + {{=CODE(""" + def streamer(): + import os + path=os.path.join(request.folder,'private','largefile.mpeg4') + return response.stream(open(path,'rb'),chunk_size=4096) + """.strip(),language='web2py',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} +

    By default all static files and files stored in 'upload' fields in the database are streamed when larger than 1MByte.

    + +

    web2py automatically and transparently handles PARTIAL_CONTENT and RANGE requests.

    + +

    XML-RPC Examples

    + +

    Example {{=c}}{{c+=1}}

    +

    Web2py has native support for the XMLRPC protocol. Below is a controller function "handler" that exposes two functions, "add" and "sub" via XMLRPC. The controller "tester" executes the two function remotely via xmlrpc.

    + {{=CODE(""" + from gluon.tools import Service + service = Service(globals()) + + @service.xmlrpc + def add(a,b): return a+b + + @service.xmlrpc + def sub(a,b): return a-b + + def call(): return service() + + def tester(): + import xmlrpclib + server=xmlrpclib.ServerProxy('http://hostname:port/app/controller/call/xmlrpc') + return str(server.add(3,4)+server.sub(3,4)) + """.strip(),language='web2py',link=URL(r=request,c='global',f='vars'),_class='boxCode')}} + +
    +
    +{{block sidebar}}{{end}} ADDED applications/examples/views/default/index.html Index: applications/examples/views/default/index.html ================================================================== --- applications/examples/views/default/index.html +++ applications/examples/views/default/index.html @@ -0,0 +1,32 @@ +{{extend 'layout.html'}} + +
    +
    + {{=get_content('maincontent')}} +
    +
    + {{=get_content('whyweb2py')}} +
    +
    +

    {{=T('Recent development')}}

    + {{=code_feed_reader(project='web2py',mode='nodiv')}} +
    +
    +

    {{=T('Latest posts from user forum')}}

    + {{=group_feed_reader(group='web2py',mode='nodiv')}} +
    +
    + +{{block sidebar}} + {{super}} + +
    +
    + +
    +
    +
    +
    +{{end}} ADDED applications/examples/views/default/license.html Index: applications/examples/views/default/license.html ================================================================== --- applications/examples/views/default/license.html +++ applications/examples/views/default/license.html @@ -0,0 +1,7 @@ +{{extend 'layout.html'}} + +
    +

    web2py License Agreement

    + {{=license}} +
    + ADDED applications/examples/views/default/support.html Index: applications/examples/views/default/support.html ================================================================== --- applications/examples/views/default/support.html +++ applications/examples/views/default/support.html @@ -0,0 +1,40 @@ +{{extend 'layout.html'}} +
    +

    Support for web2pyTM

    + +

    You can get a lot of free support by joining our mailing list. We also have a FAQ page.

    + +

    Affiliated Companies

    + +

    For long term professional support, code review and contract work you can contact our core developers:

    + +
    + +
    + +

    For professional support you can also contact one of the companies below:

    + +
    + + +
    +
    + +{{block leftbadges}}{{end}} ADDED applications/examples/views/default/usergroups.html Index: applications/examples/views/default/usergroups.html ================================================================== --- applications/examples/views/default/usergroups.html +++ applications/examples/views/default/usergroups.html @@ -0,0 +1,9 @@ +{{extend 'layout.html'}} + +
    +
    + {{=get_content('grouplist')}} +
    +
    + + ADDED applications/examples/views/default/videos.html Index: applications/examples/views/default/videos.html ================================================================== --- applications/examples/views/default/videos.html +++ applications/examples/views/default/videos.html @@ -0,0 +1,16 @@ +{{extend 'layout.html'}} +

    +
    +

    {{=T('web2py videos')}}

    +
    + + + + + + + +
    +
    + + ADDED applications/examples/views/default/what.html Index: applications/examples/views/default/what.html ================================================================== --- applications/examples/views/default/what.html +++ applications/examples/views/default/what.html @@ -0,0 +1,58 @@ +{{extend 'layout.html'}} {{import os}} +
    +

    What is web2pyTM

    +
    +

    The best way to understand web2py is to try it. + You can try it online here.
    + This online version is identical to the actual web2py although some functions are disabled for security reasons.

    +
    +

    web2py was inspired by Ruby on Rails and, as Rails, it focuses on rapid development and follows a Model View Controller design. web2py differs from Rails because it is based on Python (thus it is faster and more scalable), because it provides a comprehensive web-based administrative interface (thus there is no need to ever type shell commands unless you wish), includes libraries to handle more protocols (for example XML-RPC and RSS feeds), and can run on the Google App Engine.

    + +

    web2py was also inspired by Django and, as Django, it has the ability to generate forms from database tables and it includes an extensive set of validators. web2py differs from Django because it is more compact, easier to learn and does not have any project-level configuration files.

    + +

    web2py is less verbose than Java-based frameworks and its syntax is much cleaner than PHP-based frameworks. This makes applications simpler to develop, and easier to read + and maintain.

    +
    +

    Here is a features comparison of web2py vs other popular web frameworks +

    +

    web2py comes in source code version (for any Operating System that runs Python) and in binary versions for OSX and Windows. web2py does not need to be installed. You unzip it, click on it, and choose a one-time administrator password. It then opens the browser for you and directs you to the administrative interface. Everything it needs to make this happen (the Python interpreter, the web-server, the relational database, etc.) is already packaged with web2py. If you need more power you customize your applications to use your preferred web-server (for example Apache) and your preferred database engine (for example PostgreSQL or Oracle).

    + +

    Via the admin interface you can upload a packed application, create a new application, design an application, maintain an existing application, bytecode-compile an application, pack and download an application. Everything can be done via the web-based admin interface, including editing the files that comprise your applications, clearing temp files, browsing past tickets/errors, run tests, interact with the database. If you so choose, it is also possible to interact with web2py via the Operating System shell or the Python shell.

    + +

    Any web2py application is comprised of Models (files that contain a description of the data representation), Views (files that contain a description of the data presentation), Controllers (files that contain a description of the business logic and workflow), Cron Jobs (tasks that need to be executed regularly in background), Modules (collections of reusable classes and functions), and Static Files (images, scripts, stylesheets, etc.). +

    + +

    Controllers consist of functions that are associated to a URL and are called when the associated URL is visited. Models are executed before the function is called, independently on the visited URL (for each app). Views are called when the function returns data other than a string, and renders the data in the proper format (for example html).

    + +

    +

    A web2py application can be as simple as a single file (controllers/default.py) containing:

    + + {{=CODE('def index(): return "Hello World"',counter=None,_class='boxCode')}} + +

    When http://localhost:8000/app/default/index is visited the function is called and it displays the message "Hello World".

    + +

    Here is a more complex complete application that lets the visitor upload images into a database:

    + + {{=CODE("""# in Model +db=DAL('sqlite://storage.db') +db.define_table('image', + Field('name'), + Field('file','upload'))""",counter=None,_class='boxCode')}} + {{=CODE("""# in Controller +def index(): + form = SQLFORM(db.image) + if form.accepts(request.vars, session): + response.flash = 'image uploaded' + return dict(form = form)""",counter=None,_class='boxCode')}} + + {{=CODE( + """# in View +[[extend 'layout.html']] +

    Image upload form

    +[[= form]]""".replace('[','{').replace(']','}'),counter=None,_class='boxCode')}} + +

    Uploaded images are safely renamed to avoid directory traversal vulnerabilities, stored on the filesystem (or database) and a corresponding entry is inserted in the database, linking the file. A built-in mechanism prevents involuntary double form submission. All DB IO is transaction safe by default. Any exception in the code causes the transaction to rollback.

    +
    +

    Examples of more complex sample applications can be found here

    +
    +
    ADDED applications/examples/views/default/who.html Index: applications/examples/views/default/who.html ================================================================== --- applications/examples/views/default/who.html +++ applications/examples/views/default/who.html @@ -0,0 +1,147 @@ +{{extend 'layout.html'}} +
    +

    + The web2py™ Team +

    +

    + Lead Developer +

    +
      +
    • + Massimo Di Pierro + (Associate Professor in Computer Science at DePaul University in Chicago) +
    • +
    +

    + Contributor Agreement +

    +

    + By contributing to web2py you implicitly agree to the + web2py contributor agreement. + Please also send us a signed copy by fax or, scanned, by email. +

    +

    +

    + Main Contributors +

    +
      + +
    • Alexey Nezhdanov (GAE and database performance) +
    • Alvaro Justen (dynamical translations) +
    • Anthony Bastardi (new poweredby site, LoadFactory) +
    • Arun K. Rajeevan (plugin_wiki) +
    • Attila Csipa (cron job) +
    • Bill Ferrett (modular DAL design) +
    • Boris Manojlovic (ajax edit) +
    • Branko Vukelic (new admin app) +
    • Brian Meredyk (SQLite and executesql) +
    • Bruno Rocha (book, new examples app, better forms) +
    • Carsten Haese (Informix) +
    • Chris Clark (Ingres, Jython support) +
    • Chris Steel +
    • Christian Foster Howes (GAE support) +
    • Christopher Smiga (Informix) +
    • CJ Lazell (tester) +
    • Craig Younkins (Security) +
    • Daniel Lin (Taiwanese internationalization) +
    • David Wagner (security and cryptography expert) +
    • Denes Lengyel (validators, DB2 support, DAL, custom forms, legacy table support) +
    • Douglas Soares de Andrade (2.4 and 2.6 compliance, docstrings) +
    • Eric Vicenti (email with ssl) +
    • Falko Krause (mysql support) +
    • Fran Boon (authorization and authentication) +
    • Francisco Gama (bug fixing) +
    • Fred Yanowski (XHTML compliance) +
    • Gilson Filho +
    • Graham Dumpleton (WSGI) +
    • Gyuris Szabolcs (PGP Mail) +
    • Hamdy Abdel-Badeea (crud) +
    • Hans Donner (GAE support, Google login, widgets, Sphinx documentation) +
    • Hans Murx (Database support) +
    • Hans C. v. Stockhausen (OpenID, Google Wave) +
    • Ian Reinhart Geiser (html helpers) +
    • Jonathan Benn (is_url validator and tests) +
    • Jonathan Lundell (multiple contributions) +
    • Josh Goldfoot (xaml/html sanitizer) +
    • Jose Jachuf (Firebird support) +
    • Josh Jaques (web2py_ajax) +
    • José Vicente de Sousa (Layout for examples app/ main website) +
    • Keith Yang (openid) +
    • Kyle Smith (javascript) +
    • Limodou (winservice) +
    • Lucas D'Ávila +
    • Marcel Leuthi (Oracle support) +
    • Marcel Hellkamp (Bottle developer, multiple web server support) +
    • Marcello Della Longa (italian translation) +
    • Mariano Reingart (pysoaplib) +
    • Mark Larsen (taskbar widget) +
    • Mark Moore (databases and daemon scripts) +
    • Markus Gritsch (bug fixing) +
    • Martin Hufsky (expressions in DAL) +
    • Martin Mulone (new welcome app) +
    • Mateusz Banach (stickers, IS_EMAIL, IS_IMAGE, contenttype) +
    • Michael Willis (shell) +
    • Michele Comitini (faceboook) +
    • Nathan Freeze (admin design, IS_STRONG, DAL features, web2pyslices.com) +
    • Niall Sweeny (MSSQL support) +
    • Niccolo Polo (epydoc) +
    • Nicolas Bruxer (memcache support) +
    • Ondrej Such (MSSQL support) +
    • Ovidio Marinho Falcao Neto +
    • Pai (internationalization) +
    • Patrick Breitenbach +
    • Phyo Arkar Lwin (web hosting and Jython tester) +
    • Pierre Thibault (Eclipse integration and custom import) +
    • Robin Bhattacharyya (Google App Engine support) +
    • Ross Peoples (MSSQL, multiple contributions) +
    • Ruijun Luo (a.k.a. Iceberg) (setup_exe.py) +
    • Ryan Seto (template.py) +
    • Scott Roberts (testing, book) +
    • Sergey Podlesnyi (Oracle and migrations tester) +
    • Sharriff Aina (tester and PyAMF integration) +
    • Sriram Durbha (book) +
    • Sterling Hankins (tester, book) +
    • Stuart Rackham (MSSQL support) +
    • Telman Yusupov (Oracle support) +
    • Thadeus Burgess (validators) +
    • Tim Michelsen (Sphinx documentation) +
    • Timothy Farrell (python 2.6 compliance, windows support) +
    • Yair Eshel (internationalizaiton) +
    • Yarko Tymciurak (design, Sphinx documentation) +
    • Younghyun Jo (internationalization) +
    • Vidul Nikolaev Petrov (captcha) +
    • Zahariash (memory management) +
    • + +
    +
    +
    +

    + Third party software included in web2py +

    +
      + +
    • Python created by Guido van Rossum.
    • +
    • Rocket Web Server developed by Timothy Farrell.
    • +
    • EditArea developed by Christophe Dolivet
    • +
    • nicEdit developed by Brian Kirchoff
    • +
    • simplejson developed by Bob Ippolito
    • +
    • PyRTF developed by Simon Cusack and revised by Grant Edwards
    • +
    • PyRSS2Gen developed by Dalke Scientific Software
    • +
    • feedparser developed by Mark Pilgrim
    • +
    • markdown2 developed by Trent Mick
    • +
    • fcgi.py devloped by Allan Saddi (for production Lighttpd servers)
    • +
    • memcache developed by Evan Martin
    • +
    • jQuery developed by John Resig
    • +
    • A syntax highlighter inspired by the code of Peter Wilkinson
    • + +
    +
    +
    + + + + + + + ADDED applications/examples/views/generic.html Index: applications/examples/views/generic.html ================================================================== --- applications/examples/views/generic.html +++ applications/examples/views/generic.html @@ -0,0 +1,20 @@ +{{extend 'layout.html'}} +{{""" + +You should not modify this file. +It is used as default when a view is not provided for your controllers + +"""}} + +{{=BEAUTIFY(response._vars)}} + + + + + + + + + + +{{block sidebar}}{{end}} ADDED applications/examples/views/generic.json Index: applications/examples/views/generic.json ================================================================== --- applications/examples/views/generic.json +++ applications/examples/views/generic.json @@ -0,0 +1,15 @@ +{{ +### +# response._vars contains the dictionary returned by the controller action +### +try: + from gluon.serializers import json + response.write(json(response._vars), escape=False) + response.headers['Content-Type'] = 'application/json' +except (TypeError, ValueError): + raise HTTP(405, 'JSON serialization error') +except ImportError: + raise HTTP(405, 'JSON not available') +except: + raise HTTP(405, 'JSON error') +}} ADDED applications/examples/views/generic.load Index: applications/examples/views/generic.load ================================================================== --- applications/examples/views/generic.load +++ applications/examples/views/generic.load @@ -0,0 +1,1 @@ +{{response.headers['web2py-response-flash']=response.flash}}{{if len(response._vars)==1:}}{{=response._vars.values()[0]}}{{else:}}{{=BEAUTIFY(response._vars)}}{{pass}} ADDED applications/examples/views/generic.rss Index: applications/examples/views/generic.rss ================================================================== --- applications/examples/views/generic.rss +++ applications/examples/views/generic.rss @@ -0,0 +1,20 @@ +{{ +### +# response._vars contains the dictionary returned by the controller action +# for this to work the action must return something like +# +# dict(title=...,link=...,description=...,created_on='...',items=...) +# +# items is a list of dictionaries each with title, link, description, pub_date. +### +try: + from gluon.serializers import rss + response.write(rss(response._vars), escape=False) + response.headers['Content-Type'] = 'application/rss+xml' +except (TypeError, ValueError): + raise HTTP(405, 'RSS serialization error') +except ImportError: + raise HTTP(405, 'RSS not available') +except: + raise HTTP(405, 'RSS error') +}} ADDED applications/examples/views/generic.xml Index: applications/examples/views/generic.xml ================================================================== --- applications/examples/views/generic.xml +++ applications/examples/views/generic.xml @@ -0,0 +1,15 @@ +{{ +### +# response._vars contains the dictionary returned by thecontroller action +### +try: + from gluon.serializers import xml + response.write(xml(response._vars), escape=False) + response.headers['Content-Type'] = 'text/xml' +except (TypeError, ValueError): + raise HTTP(405, 'XML serialization error') +except ImportError: + raise HTTP(405, 'XML not available') +except: + raise HTTP(405, 'XML error') +}} ADDED applications/examples/views/global/vars.html Index: applications/examples/views/global/vars.html ================================================================== --- applications/examples/views/global/vars.html +++ applications/examples/views/global/vars.html @@ -0,0 +1,47 @@ +{{extend 'layout.html'}} +{{import cgi}} + +
    +

    {{=T('Docs for')}} {{=title}}

    + + + +

    {{=T('Description')}}

    + +
    + {{if t:}} + {{=t}}{{if d:}} extends {{=d}}{{pass}} + {{pass}} +
    + {{pass}} + + {{if doc:}}

    {{=CODE(str(doc),language=None,counter=None,_class='boxCode')}}{{pass}} +

    +
    +

    {{=T('Attributes')}}

    + + {{keys=attributes.keys(); keys.sort()}} + + + {{for a in keys:}} + {{doc1,t1,c1,d1=attributes[a]}} + + + + + + {{pass}} +

    {{#=a}}{{=A(a,_href=URL(r=request,args=a.split('.')))}} + {{if t1:}} + {{=t1}}{{if d1:}} extends {{=d1}}{{pass}} + {{if c1:}} belongs to class {{=c1}}{{pass}} +
    + {{pass}} + {{if doc1:}}{{=XML(cgi.escape(str(doc1)).replace(chr(13),'
    '))}}{{pass}} +

    +
    +
    ADDED applications/examples/views/images_examples/index.html Index: applications/examples/views/images_examples/index.html ================================================================== --- applications/examples/views/images_examples/index.html +++ applications/examples/views/images_examples/index.html @@ -0,0 +1,5 @@ +{{extend 'layout.html'}} +

    Upload page

    +{{=form}} + +{{block sidebar end}} ADDED applications/examples/views/layout.html Index: applications/examples/views/layout.html ================================================================== --- applications/examples/views/layout.html +++ applications/examples/views/layout.html @@ -0,0 +1,138 @@ + + + + {{=response.title or request.application}} + + + {{ + #---- (CSS) -----) + import random + response.files.append(URL('static','css/menu.css')) + response.files.append(URL('static','css/home.css')) + + #------ include web2py specific js code (jquery, calendar, form stuff) ------ + }} + {{include 'web2py_ajax.html'}} + {{=toggle_menuclass()}} + + + +
    +
    {{=response.flash or ''}}
    +
    + + + + ADDED applications/examples/views/layout_examples/basic.html Index: applications/examples/views/layout_examples/basic.html ================================================================== --- applications/examples/views/layout_examples/basic.html +++ applications/examples/views/layout_examples/basic.html @@ -0,0 +1,3 @@ +{{extend 'layout.html'}} +

    {{=message}}

    +{{for i in range(1000):}}bla {{pass}} ADDED applications/examples/views/layout_examples/civilized.html Index: applications/examples/views/layout_examples/civilized.html ================================================================== --- applications/examples/views/layout_examples/civilized.html +++ applications/examples/views/layout_examples/civilized.html @@ -0,0 +1,3 @@ +{{extend 'layout_examples/layout_civilized.html'}} +

    {{=message}}

    +

    {{for i in range(1000):}}bla {{pass}} 

    ADDED applications/examples/views/layout_examples/layout_civilized.html Index: applications/examples/views/layout_examples/layout_civilized.html ================================================================== --- applications/examples/views/layout_examples/layout_civilized.html +++ applications/examples/views/layout_examples/layout_civilized.html @@ -0,0 +1,290 @@ + + + + + +{{=request.application}} + + + + + +
    + + {{if response.menu:}} + + {{pass}} +
    +
    +
    + {{if response.flash:}}

    FLASH: {{=response.flash}}

    {{pass}} + {{include}} +
    +
    +
    +
    +
    +
    + +
    + + ADDED applications/examples/views/layout_examples/layout_sleek.html Index: applications/examples/views/layout_examples/layout_sleek.html ================================================================== --- applications/examples/views/layout_examples/layout_sleek.html +++ applications/examples/views/layout_examples/layout_sleek.html @@ -0,0 +1,252 @@ + + + + + +{{=request.application}} + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    +
    + +
    +{{if response.flash:}}

    FLASH: {{=response.flash}}

    {{pass}} +{{include}} +
    + +
    + + + + ADDED applications/examples/views/layout_examples/slick.html Index: applications/examples/views/layout_examples/slick.html ================================================================== --- applications/examples/views/layout_examples/slick.html +++ applications/examples/views/layout_examples/slick.html @@ -0,0 +1,3 @@ +{{extend 'layout_examples/layout_sleek.html'}} +

    {{=message}}

    +{{for i in range(1000):}}bla {{pass}} ADDED applications/examples/views/session_examples/counter.html Index: applications/examples/views/session_examples/counter.html ================================================================== --- applications/examples/views/session_examples/counter.html +++ applications/examples/views/session_examples/counter.html @@ -0,0 +1,8 @@ +{{extend 'layout.html'}} +

    session counter

    + +

    {{for i in range(counter):}}{{=i}}... {{pass}}

    + +{{=T('click me to count')}} + +{{block sidebar}} {{end}} ADDED applications/examples/views/simple_examples/hello3.html Index: applications/examples/views/simple_examples/hello3.html ================================================================== --- applications/examples/views/simple_examples/hello3.html +++ applications/examples/views/simple_examples/hello3.html @@ -0,0 +1,2 @@ +{{extend 'layout.html'}} +

    {{=message}}

    ADDED applications/examples/views/spreadsheet/index.html Index: applications/examples/views/spreadsheet/index.html ================================================================== --- applications/examples/views/spreadsheet/index.html +++ applications/examples/views/spreadsheet/index.html @@ -0,0 +1,7 @@ +{{extend 'layout.html'}} + +

    Excel-like spreadsheet widget

    + +Try insert "=r0c1+1" in cell r0c0 and "2" in r0c1. Formulas start with "=" as in Excel. You can use a subset of python commands and math function, and reference cells by r[row]c[col]. All computations are performed serverside via Ajax (input is validated for security). Cell values and formulas can be set and locked serverside. The shape of the spreadsheet can be modifed serverside and does not need to be tabular (think of it as a graph of css-friendly widgets you can place where you want). Cells can be given arbistrary names. This example is distributed with web2py so look at the source code of the example to learn more. + +{{=sheet}} ADDED applications/examples/views/template_examples/beautify.html Index: applications/examples/views/template_examples/beautify.html ================================================================== --- applications/examples/views/template_examples/beautify.html +++ applications/examples/views/template_examples/beautify.html @@ -0,0 +1,5 @@ +{{extend 'layout.html'}} +

    BEAUTIFY

    + +

    Message is

    +{{=message}} ADDED applications/examples/views/template_examples/escape.html Index: applications/examples/views/template_examples/escape.html ================================================================== --- applications/examples/views/template_examples/escape.html +++ applications/examples/views/template_examples/escape.html @@ -0,0 +1,5 @@ +{{extend 'layout.html'}} +

    Strings are automatically escaped

    + +

    Message is

    +{{=message}} ADDED applications/examples/views/template_examples/test_def.html Index: applications/examples/views/template_examples/test_def.html ================================================================== --- applications/examples/views/template_examples/test_def.html +++ applications/examples/views/template_examples/test_def.html @@ -0,0 +1,7 @@ +{{extend 'layout.html'}} +{{def itemlink(name):}}
  • {{=A(name,_href=name)}}
  • {{return}} +
      +{{itemlink('http://www.google.com')}} +{{itemlink('http://www.yahoo.com')}} +{{itemlink('http://www.nyt.com')}} +
    ADDED applications/examples/views/template_examples/test_for.html Index: applications/examples/views/template_examples/test_for.html ================================================================== --- applications/examples/views/template_examples/test_for.html +++ applications/examples/views/template_examples/test_for.html @@ -0,0 +1,6 @@ +{{extend 'layout.html'}} +

    For loop

    + +{{for number in ['one','two','three']:}} +

    {{=number.capitalize()}}

    +{{pass}} ADDED applications/examples/views/template_examples/test_if.html Index: applications/examples/views/template_examples/test_if.html ================================================================== --- applications/examples/views/template_examples/test_if.html +++ applications/examples/views/template_examples/test_if.html @@ -0,0 +1,12 @@ +{{extend 'layout.html'}} +

    If statement

    + +{{ +a=10 +}} + +{{if a%2==0:}} +

    {{=a}} is even

    +{{else:}} +

    {{=a}} is odd

    +{{pass}} ADDED applications/examples/views/template_examples/test_try.html Index: applications/examples/views/template_examples/test_try.html ================================================================== --- applications/examples/views/template_examples/test_try.html +++ applications/examples/views/template_examples/test_try.html @@ -0,0 +1,8 @@ +{{extend 'layout.html'}} +

    Try... except

    + +{{try:}} +

    a={{=1/0}}

    +{{except:}} + infinity

    +{{pass}} ADDED applications/examples/views/template_examples/variables.html Index: applications/examples/views/template_examples/variables.html ================================================================== --- applications/examples/views/template_examples/variables.html +++ applications/examples/views/template_examples/variables.html @@ -0,0 +1,4 @@ +{{extend 'layout.html'}} +

    Your variables

    +

    a={{=a}}

    +

    a={{=b}}

    ADDED applications/examples/views/template_examples/xml.html Index: applications/examples/views/template_examples/xml.html ================================================================== --- applications/examples/views/template_examples/xml.html +++ applications/examples/views/template_examples/xml.html @@ -0,0 +1,5 @@ +{{extend 'layout.html'}} +

    XML

    + +

    Message is

    +{{=message}} ADDED applications/examples/views/web2py_ajax.html Index: applications/examples/views/web2py_ajax.html ================================================================== --- applications/examples/views/web2py_ajax.html +++ applications/examples/views/web2py_ajax.html @@ -0,0 +1,27 @@ +{{ +response.files.insert(0,URL('static','js/jquery.js')) +response.files.insert(1,URL('static','css/calendar.css')) +response.files.insert(2,URL('static','js/calendar.js')) +for _item in response.meta or []:}} + {{ +pass +for _k,_file in enumerate(response.files or []): + if _file in response.files[:_k]: + continue + _file0=_file.lower().split('?')[0] + if _file0.endswith('.css'):}} + {{ + elif _file0.endswith('.js'):}} + {{ + pass +pass +}} + + + + ADDED applications/mobileblur/ABOUT Index: applications/mobileblur/ABOUT ================================================================== --- applications/mobileblur/ABOUT +++ applications/mobileblur/ABOUT @@ -0,0 +1,2 @@ +Write something about this app. +Developed with web2py. ADDED applications/mobileblur/LICENSE Index: applications/mobileblur/LICENSE ================================================================== --- applications/mobileblur/LICENSE +++ applications/mobileblur/LICENSE @@ -0,0 +1,4 @@ +The web2py welcome app is licensed under public domain +(except for the css and js files that it includes, which have their own third party licenses). + +You can modify this license when you add your own code. ADDED applications/mobileblur/__init__.py Index: applications/mobileblur/__init__.py ================================================================== --- applications/mobileblur/__init__.py +++ applications/mobileblur/__init__.py ADDED applications/mobileblur/controllers/appadmin.py Index: applications/mobileblur/controllers/appadmin.py ================================================================== --- applications/mobileblur/controllers/appadmin.py +++ applications/mobileblur/controllers/appadmin.py @@ -0,0 +1,408 @@ +# -*- coding: utf-8 -*- + +# ########################################################## +# ## make sure administrator is on localhost +# ########################################################### + +import os +import socket +import datetime +import copy +import gluon.contenttype +import gluon.fileutils + +# ## critical --- make a copy of the environment + +global_env = copy.copy(globals()) +global_env['datetime'] = datetime + +http_host = request.env.http_host.split(':')[0] +remote_addr = request.env.remote_addr +try: + hosts = (http_host, socket.gethostname(), + socket.gethostbyname(http_host), + '::1','127.0.0.1','::ffff:127.0.0.1') +except: + hosts = (http_host, ) + +if request.env.http_x_forwarded_for or request.env.wsgi_url_scheme\ + in ['https', 'HTTPS']: + session.secure() +elif (remote_addr not in hosts) and (remote_addr != "127.0.0.1"): + raise HTTP(200, T('appadmin is disabled because insecure channel')) + +if (request.application=='admin' and not session.authorized) or \ + (request.application!='admin' and not gluon.fileutils.check_credentials(request)): + redirect(URL('admin', 'default', 'index')) + +ignore_rw = True +response.view = 'appadmin.html' +response.menu = [[T('design'), False, URL('admin', 'default', 'design', + args=[request.application])], [T('db'), False, + URL('index')], [T('state'), False, + URL('state')], [T('cache'), False, + URL('ccache')]] + +# ########################################################## +# ## auxiliary functions +# ########################################################### + + +def get_databases(request): + dbs = {} + for (key, value) in global_env.items(): + cond = False + try: + cond = isinstance(value, GQLDB) + except: + cond = isinstance(value, SQLDB) + if cond: + dbs[key] = value + return dbs + + +databases = get_databases(None) + + +def eval_in_global_env(text): + exec ('_ret=%s' % text, {}, global_env) + return global_env['_ret'] + + +def get_database(request): + if request.args and request.args[0] in databases: + return eval_in_global_env(request.args[0]) + else: + session.flash = T('invalid request') + redirect(URL('index')) + + +def get_table(request): + db = get_database(request) + if len(request.args) > 1 and request.args[1] in db.tables: + return (db, request.args[1]) + else: + session.flash = T('invalid request') + redirect(URL('index')) + + +def get_query(request): + try: + return eval_in_global_env(request.vars.query) + except Exception: + return None + + +def query_by_table_type(tablename,db,request=request): + keyed = hasattr(db[tablename],'_primarykey') + if keyed: + firstkey = db[tablename][db[tablename]._primarykey[0]] + cond = '>0' + if firstkey.type in ['string', 'text']: + cond = '!=""' + qry = '%s.%s.%s%s' % (request.args[0], request.args[1], firstkey.name, cond) + else: + qry = '%s.%s.id>0' % tuple(request.args[:2]) + return qry + + + +# ########################################################## +# ## list all databases and tables +# ########################################################### + + +def index(): + return dict(databases=databases) + + +# ########################################################## +# ## insert a new record +# ########################################################### + + +def insert(): + (db, table) = get_table(request) + form = SQLFORM(db[table], ignore_rw=ignore_rw) + if form.accepts(request.vars, session): + response.flash = T('new record inserted') + return dict(form=form,table=db[table]) + + +# ########################################################## +# ## list all records in table and insert new record +# ########################################################### + + +def download(): + import os + db = get_database(request) + return response.download(request,db) + +def csv(): + import gluon.contenttype + response.headers['Content-Type'] = \ + gluon.contenttype.contenttype('.csv') + db = get_database(request) + query = get_query(request) + if not query: + return None + response.headers['Content-disposition'] = 'attachment; filename=%s_%s.csv'\ + % tuple(request.vars.query.split('.')[:2]) + return str(db(query).select()) + + +def import_csv(table, file): + table.import_from_csv_file(file) + +def select(): + import re + db = get_database(request) + dbname = request.args[0] + regex = re.compile('(?P\w+)\.(?P\w+)=(?P\d+)') + if len(request.args)>1 and hasattr(db[request.args[1]],'_primarykey'): + regex = re.compile('(?P
    \w+)\.(?P\w+)=(?P.+)') + if request.vars.query: + match = regex.match(request.vars.query) + if match: + request.vars.query = '%s.%s.%s==%s' % (request.args[0], + match.group('table'), match.group('field'), + match.group('value')) + else: + request.vars.query = session.last_query + query = get_query(request) + if request.vars.start: + start = int(request.vars.start) + else: + start = 0 + nrows = 0 + stop = start + 100 + table = None + rows = [] + orderby = request.vars.orderby + if orderby: + orderby = dbname + '.' + orderby + if orderby == session.last_orderby: + if orderby[0] == '~': + orderby = orderby[1:] + else: + orderby = '~' + orderby + session.last_orderby = orderby + session.last_query = request.vars.query + form = FORM(TABLE(TR(T('Query:'), '', INPUT(_style='width:400px', + _name='query', _value=request.vars.query or '', + requires=IS_NOT_EMPTY(error_message=T("Cannot be empty")))), TR(T('Update:'), + INPUT(_name='update_check', _type='checkbox', + value=False), INPUT(_style='width:400px', + _name='update_fields', _value=request.vars.update_fields + or '')), TR(T('Delete:'), INPUT(_name='delete_check', + _class='delete', _type='checkbox', value=False), ''), + TR('', '', INPUT(_type='submit', _value='submit'))), + _action=URL(r=request,args=request.args)) + if request.vars.csvfile != None: + try: + import_csv(db[request.vars.table], + request.vars.csvfile.file) + response.flash = T('data uploaded') + except Exception, e: + response.flash = DIV(T('unable to parse csv file'),PRE(str(e))) + if form.accepts(request.vars, formname=None): +# regex = re.compile(request.args[0] + '\.(?P
    \w+)\.id\>0') + regex = re.compile(request.args[0] + '\.(?P
    \w+)\..+') + + match = regex.match(form.vars.query.strip()) + if match: + table = match.group('table') + try: + nrows = db(query).count() + if form.vars.update_check and form.vars.update_fields: + db(query).update(**eval_in_global_env('dict(%s)' + % form.vars.update_fields)) + response.flash = T('%s rows updated', nrows) + elif form.vars.delete_check: + db(query).delete() + response.flash = T('%s rows deleted', nrows) + nrows = db(query).count() + if orderby: + rows = db(query).select(limitby=(start, stop), + orderby=eval_in_global_env(orderby)) + else: + rows = db(query).select(limitby=(start, stop)) + except Exception, e: + (rows, nrows) = ([], 0) + response.flash = DIV(T('Invalid Query'),PRE(str(e))) + return dict( + form=form, + table=table, + start=start, + stop=stop, + nrows=nrows, + rows=rows, + query=request.vars.query, + ) + + +# ########################################################## +# ## edit delete one record +# ########################################################### + + +def update(): + (db, table) = get_table(request) + keyed = hasattr(db[table],'_primarykey') + record = None + if keyed: + key = [f for f in request.vars if f in db[table]._primarykey] + if key: + record = db(db[table][key[0]] == request.vars[key[0]]).select().first() + else: + record = db(db[table].id == request.args(2)).select().first() + + if not record: + qry = query_by_table_type(table, db) + session.flash = T('record does not exist') + redirect(URL('select', args=request.args[:1], + vars=dict(query=qry))) + + if keyed: + for k in db[table]._primarykey: + db[table][k].writable=False + + form = SQLFORM(db[table], record, deletable=True, delete_label=T('Check to delete'), + ignore_rw=ignore_rw and not keyed, + linkto=URL('select', + args=request.args[:1]), upload=URL(r=request, + f='download', args=request.args[:1])) + + if form.accepts(request.vars, session): + session.flash = T('done!') + qry = query_by_table_type(table, db) + redirect(URL('select', args=request.args[:1], + vars=dict(query=qry))) + return dict(form=form,table=db[table]) + + +# ########################################################## +# ## get global variables +# ########################################################### + + +def state(): + return dict() + +def ccache(): + form = FORM( + P(TAG.BUTTON("Clear CACHE?", _type="submit", _name="yes", _value="yes")), + P(TAG.BUTTON("Clear RAM", _type="submit", _name="ram", _value="ram")), + P(TAG.BUTTON("Clear DISK", _type="submit", _name="disk", _value="disk")), + ) + + if form.accepts(request.vars, session): + clear_ram = False + clear_disk = False + session.flash = "" + if request.vars.yes: + clear_ram = clear_disk = True + if request.vars.ram: + clear_ram = True + if request.vars.disk: + clear_disk = True + + if clear_ram: + cache.ram.clear() + session.flash += "Ram Cleared " + if clear_disk: + cache.disk.clear() + session.flash += "Disk Cleared" + + redirect(URL(r=request)) + + try: + from guppy import hpy; hp=hpy() + except ImportError: + hp = False + + import shelve, os, copy, time, math + from gluon import portalocker + + ram = { + 'bytes': 0, + 'objects': 0, + 'hits': 0, + 'misses': 0, + 'ratio': 0, + 'oldest': time.time() + } + disk = copy.copy(ram) + total = copy.copy(ram) + + for key, value in cache.ram.storage.items(): + if isinstance(value, dict): + ram['hits'] = value['hit_total'] - value['misses'] + ram['misses'] = value['misses'] + try: + ram['ratio'] = ram['hits'] * 100 / value['hit_total'] + except (KeyError, ZeroDivisionError): + ram['ratio'] = 0 + else: + if hp: + ram['bytes'] += hp.iso(value[1]).size + ram['objects'] += hp.iso(value[1]).count + + if value[0] < ram['oldest']: + ram['oldest'] = value[0] + + locker = open(os.path.join(request.folder, + 'cache/cache.lock'), 'a') + portalocker.lock(locker, portalocker.LOCK_EX) + disk_storage = shelve.open(os.path.join(request.folder, 'cache/cache.shelve')) + try: + for key, value in disk_storage.items(): + if isinstance(value, dict): + disk['hits'] = value['hit_total'] - value['misses'] + disk['misses'] = value['misses'] + try: + disk['ratio'] = disk['hits'] * 100 / value['hit_total'] + except (KeyError, ZeroDivisionError): + disk['ratio'] = 0 + else: + if hp: + disk['bytes'] += hp.iso(value[1]).size + disk['objects'] += hp.iso(value[1]).count + if value[0] < disk['oldest']: + disk['oldest'] = value[0] + finally: + portalocker.unlock(locker) + locker.close() + disk_storage.close() + + total['bytes'] = ram['bytes'] + disk['bytes'] + total['objects'] = ram['objects'] + disk['objects'] + total['hits'] = ram['hits'] + disk['hits'] + total['misses'] = ram['misses'] + disk['misses'] + try: + total['ratio'] = total['hits'] * 100 / (total['hits'] + total['misses']) + except (KeyError, ZeroDivisionError): + total['ratio'] = 0 + + if disk['oldest'] < ram['oldest']: + total['oldest'] = disk['oldest'] + else: + total['oldest'] = ram['oldest'] + + def GetInHMS(seconds): + hours = math.floor(seconds / 3600) + seconds -= hours * 3600 + minutes = math.floor(seconds / 60) + seconds -= minutes * 60 + seconds = math.floor(seconds) + + return (hours, minutes, seconds) + + ram['oldest'] = GetInHMS(time.time() - ram['oldest']) + disk['oldest'] = GetInHMS(time.time() - disk['oldest']) + total['oldest'] = GetInHMS(time.time() - total['oldest']) + + return dict(form=form, total=total, + ram=ram, disk=disk) + ADDED applications/mobileblur/controllers/default.py Index: applications/mobileblur/controllers/default.py ================================================================== --- applications/mobileblur/controllers/default.py +++ applications/mobileblur/controllers/default.py @@ -0,0 +1,13 @@ +from pprint import pprint + +def index(): + raw_feeds = newsblur.feeds(flat=True)["feeds"] + feeds = {} + for feed in raw_feeds.itervalues(): + for i in range(threshold, 2): + if feed[thresholds[i]] > 0: + feeds[feed["feed_title"]] = feed + break + + pprint(feeds) + return dict(feeds=feeds, threshold=threshold) ADDED applications/mobileblur/controllers/default.py~ Index: applications/mobileblur/controllers/default.py~ ================================================================== --- applications/mobileblur/controllers/default.py~ +++ applications/mobileblur/controllers/default.py~ @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# this file is released under public domain and you can use without limitations + +from pprint import pprint +import simplejson +import urllib + +base = "http://newsblur.com/" +username = "spiffytech" +password = "JYRKJM9UuQg9" +threshold = 0 + +def index(): + login() + data = urllib.urlencode({"flat": "true"}) + u = urllib.urlopen(base + "reader/feeds?" % data) + print u.read() + raw_feeds = simplejson.loads(u.read()) + pprint(raw_feeds) + feeds = {} + for feed in raw_feeds: + if not (feed["ng"] == 0 and feed["nt"] == 0 and feed["ps"] == 0): + feeds[feed["feed_title"]] = feed + + return feeds + +def login(): + data = urllib.urlencode({"login_username": username, "login_password": password}) + u = urllib.urlopen(base + "api/login", data) + print u.read() ADDED applications/mobileblur/controllers/feeds.py Index: applications/mobileblur/controllers/feeds.py ================================================================== --- applications/mobileblur/controllers/feeds.py +++ applications/mobileblur/controllers/feeds.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- + +from pprint import pprint + +def view(): + stories = newsblur.feed(request.args[0])["stories"] + feeds = newsblur.feeds(flat=True)["feeds"] + feed = [feed for feed in feeds.itervalues() if feed["id"]==int(request.args[0])][0] + return dict(stories=stories, feed=feed) + +def mark_read(): + newsblur.mark_feed_as_read(request.vars["feed"]) + redirect(URL("default", "index")) ADDED applications/mobileblur/controllers/stories.py Index: applications/mobileblur/controllers/stories.py ================================================================== --- applications/mobileblur/controllers/stories.py +++ applications/mobileblur/controllers/stories.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- + +from pprint import pprint + +def view(): + stories = newsblur.feed(request.vars["feed_id"])["stories"] + story = [story for story in stories if story["id"]==request.vars["story"]][0] + return dict(story=story) ADDED applications/mobileblur/cron/crontab Index: applications/mobileblur/cron/crontab ================================================================== --- applications/mobileblur/cron/crontab +++ applications/mobileblur/cron/crontab @@ -0,0 +1,1 @@ +#crontab ADDED applications/mobileblur/languages/es-es.py Index: applications/mobileblur/languages/es-es.py ================================================================== --- applications/mobileblur/languages/es-es.py +++ applications/mobileblur/languages/es-es.py @@ -0,0 +1,259 @@ +# coding: utf8 +{ +'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"actualice" es una expresión opcional como "campo1=\'nuevo_valor\'". No se puede actualizar o eliminar resultados de un JOIN', +'%Y-%m-%d': '%Y-%m-%d', +'%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S', +'%s rows deleted': '%s filas eliminadas', +'%s rows updated': '%s filas actualizadas', +'(something like "it-it")': '(algo como "it-it")', +'A new version of web2py is available': 'Hay una nueva versión de web2py disponible', +'A new version of web2py is available: %s': 'Hay una nueva versión de web2py disponible: %s', +'ATTENTION: Login requires a secure (HTTPS) connection or running on localhost.': 'ATENCION: Inicio de sesión requiere una conexión segura (HTTPS) o localhost.', +'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': 'ATENCION: NO EJECUTE VARIAS PRUEBAS SIMULTANEAMENTE, NO SON THREAD SAFE.', +'ATTENTION: you cannot edit the running application!': 'ATENCION: no puede modificar la aplicación que se ejecuta!', +'About': 'Acerca de', +'About application': 'Acerca de la aplicación', +'Admin is disabled because insecure channel': 'Admin deshabilitado, el canal no es seguro', +'Admin is disabled because unsecure channel': 'Admin deshabilitado, el canal no es seguro', +'Administrator Password:': 'Contraseña del Administrador:', +'Are you sure you want to delete file "%s"?': '¿Está seguro que desea eliminar el archivo "%s"?', +'Are you sure you want to uninstall application "%s"': '¿Está seguro que desea desinstalar la aplicación "%s"', +'Are you sure you want to uninstall application "%s"?': '¿Está seguro que desea desinstalar la aplicación "%s"?', +'Authentication': 'Autenticación', +'Available databases and tables': 'Bases de datos y tablas disponibles', +'Cannot be empty': 'No puede estar vacío', +'Cannot compile: there are errors in your app. Debug it, correct errors and try again.': 'No se puede compilar: hay errores en su aplicación. Depure, corrija errores y vuelva a intentarlo.', +'Change Password': 'Cambie Contraseña', +'Check to delete': 'Marque para eliminar', +'Client IP': 'IP del Cliente', +'Controller': 'Controlador', +'Controllers': 'Controladores', +'Copyright': 'Derechos de autor', +'Create new application': 'Cree una nueva aplicación', +'Current request': 'Solicitud en curso', +'Current response': 'Respuesta en curso', +'Current session': 'Sesión en curso', +'DB Model': 'Modelo "db"', +'DESIGN': 'DISEÑO', +'Database': 'Base de datos', +'Date and Time': 'Fecha y Hora', +'Delete': 'Elimine', +'Delete:': 'Elimine:', +'Deploy on Google App Engine': 'Instale en Google App Engine', +'Description': 'Descripción', +'Design for': 'Diseño para', +'E-mail': 'Correo electrónico', +'EDIT': 'EDITAR', +'Edit': 'Editar', +'Edit Profile': 'Editar Perfil', +'Edit This App': 'Edite esta App', +'Edit application': 'Editar aplicación', +'Edit current record': 'Edite el registro actual', +'Editing file': 'Editando archivo', +'Editing file "%s"': 'Editando archivo "%s"', +'Error logs for "%(app)s"': 'Bitácora de errores en "%(app)s"', +'First name': 'Nombre', +'Functions with no doctests will result in [passed] tests.': 'Funciones sin doctests equivalen a pruebas [aceptadas].', +'Group ID': 'ID de Grupo', +'Hello World': 'Hola Mundo', +'Import/Export': 'Importar/Exportar', +'Index': 'Indice', +'Installed applications': 'Aplicaciones instaladas', +'Internal State': 'Estado Interno', +'Invalid Query': 'Consulta inválida', +'Invalid action': 'Acción inválida', +'Invalid email': 'Correo inválido', +'Language files (static strings) updated': 'Archivos de lenguaje (cadenas estáticas) actualizados', +'Languages': 'Lenguajes', +'Last name': 'Apellido', +'Last saved on:': 'Guardado en:', +'Layout': 'Diseño de página', +'License for': 'Licencia para', +'Login': 'Inicio de sesión', +'Login to the Administrative Interface': 'Inicio de sesión para la Interfaz Administrativa', +'Logout': 'Fin de sesión', +'Lost Password': 'Contraseña perdida', +'Main Menu': 'Menú principal', +'Menu Model': 'Modelo "menu"', +'Models': 'Modelos', +'Modules': 'Módulos', +'NO': 'NO', +'Name': 'Nombre', +'New Record': 'Registro nuevo', +'No databases in this application': 'No hay bases de datos en esta aplicación', +'Origin': 'Origen', +'Original/Translation': 'Original/Traducción', +'Password': 'Contraseña', +'Peeking at file': 'Visualizando archivo', +'Powered by': 'Este sitio usa', +'Query:': 'Consulta:', +'Record ID': 'ID de Registro', +'Register': 'Registrese', +'Registration key': 'Contraseña de Registro', +'Reset Password key': 'Reset Password key', +'Resolve Conflict file': 'archivo Resolución de Conflicto', +'Role': 'Rol', +'Rows in table': 'Filas en la tabla', +'Rows selected': 'Filas seleccionadas', +'Saved file hash:': 'Hash del archivo guardado:', +'Static files': 'Archivos estáticos', +'Stylesheet': 'Hoja de estilo', +'Sure you want to delete this object?': '¿Está seguro que desea eliminar este objeto?', +'Table name': 'Nombre de la tabla', +'Testing application': 'Probando aplicación', +'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'La "consulta" es una condición como "db.tabla1.campo1==\'valor\'". Algo como "db.tabla1.campo1==db.tabla2.campo2" resulta en un JOIN SQL.', +'The output of the file is a dictionary that was rendered by the view': 'La salida del archivo es un diccionario escenificado por la vista', +'There are no controllers': 'No hay controladores', +'There are no models': 'No hay modelos', +'There are no modules': 'No hay módulos', +'There are no static files': 'No hay archivos estáticos', +'There are no translators, only default language is supported': 'No hay traductores, sólo el lenguaje por defecto es soportado', +'There are no views': 'No hay vistas', +'This is a copy of the scaffolding application': 'Esta es una copia de la aplicación de andamiaje', +'This is the %(filename)s template': 'Esta es la plantilla %(filename)s', +'Ticket': 'Tiquete', +'Timestamp': 'Timestamp', +'Unable to check for upgrades': 'No es posible verificar la existencia de actualizaciones', +'Unable to download': 'No es posible la descarga', +'Unable to download app': 'No es posible descarga la aplicación', +'Update:': 'Actualice:', +'Upload existing application': 'Suba esta aplicación', +'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Use (...)&(...) para AND, (...)|(...) para OR, y ~(...) para NOT, para crear consultas más complejas.', +'User ID': 'ID de Usuario', +'View': 'Vista', +'Views': 'Vistas', +'Welcome': 'Welcome', +'Welcome %s': 'Bienvenido %s', +'Welcome to web2py': 'Bienvenido a web2py', +'Which called the function': 'La cual llamó la función', +'YES': 'SI', +'You are successfully running web2py': 'Usted está ejecutando web2py exitosamente', +'You can modify this application and adapt it to your needs': 'Usted puede modificar esta aplicación y adaptarla a sus necesidades', +'You visited the url': 'Usted visitó la url', +'about': 'acerca de', +'additional code for your application': 'código adicional para su aplicación', +'admin disabled because no admin password': ' por falta de contraseña', +'admin disabled because not supported on google app engine': 'admin deshabilitado, no es soportado en GAE', +'admin disabled because unable to access password file': 'admin deshabilitado, imposible acceder al archivo con la contraseña', +'and rename it (required):': 'y renombrela (requerido):', +'and rename it:': ' y renombrelo:', +'appadmin': 'appadmin', +'appadmin is disabled because insecure channel': 'admin deshabilitado, el canal no es seguro', +'application "%s" uninstalled': 'aplicación "%s" desinstalada', +'application compiled': 'aplicación compilada', +'application is compiled and cannot be designed': 'la aplicación está compilada y no puede ser modificada', +'cache': 'cache', +'cache, errors and sessions cleaned': 'cache, errores y sesiones eliminados', +'cannot create file': 'no es posible crear archivo', +'cannot upload file "%(filename)s"': 'no es posible subir archivo "%(filename)s"', +'change password': 'cambie contraseña', +'check all': 'marcar todos', +'clean': 'limpiar', +'Online examples': 'Ejemplos en línea', +'Administrative interface': 'Interfaz administrativa', +'click to check for upgrades': 'haga clic para buscar actualizaciones', +'compile': 'compilar', +'compiled application removed': 'aplicación compilada removida', +'controllers': 'controladores', +'create file with filename:': 'cree archivo con nombre:', +'create new application:': 'nombre de la nueva aplicación:', +'crontab': 'crontab', +'currently saved or': 'actualmente guardado o', +'customize me!': 'Adaptame!', +'data uploaded': 'datos subidos', +'database': 'base de datos', +'database %s select': 'selección en base de datos %s', +'database administration': 'administración base de datos', +'db': 'db', +'defines tables': 'define tablas', +'delete': 'eliminar', +'delete all checked': 'eliminar marcados', +'design': 'modificar', +'Documentation': 'Documentación', +'done!': 'listo!', +'edit': 'editar', +'edit controller': 'editar controlador', +'edit profile': 'editar perfil', +'errors': 'errores', +'export as csv file': 'exportar como archivo CSV', +'exposes': 'expone', +'extends': 'extiende', +'failed to reload module': 'recarga del módulo ha fallado', +'file "%(filename)s" created': 'archivo "%(filename)s" creado', +'file "%(filename)s" deleted': 'archivo "%(filename)s" eliminado', +'file "%(filename)s" uploaded': 'archivo "%(filename)s" subido', +'file "%(filename)s" was not deleted': 'archivo "%(filename)s" no fué eliminado', +'file "%s" of %s restored': 'archivo "%s" de %s restaurado', +'file changed on disk': 'archivo modificado en el disco', +'file does not exist': 'archivo no existe', +'file saved on %(time)s': 'archivo guardado %(time)s', +'file saved on %s': 'archivo guardado %s', +'help': 'ayuda', +'htmledit': 'htmledit', +'includes': 'incluye', +'insert new': 'inserte nuevo', +'insert new %s': 'inserte nuevo %s', +'internal error': 'error interno', +'invalid password': 'contraseña inválida', +'invalid request': 'solicitud inválida', +'invalid ticket': 'tiquete inválido', +'language file "%(filename)s" created/updated': 'archivo de lenguaje "%(filename)s" creado/actualizado', +'languages': 'lenguajes', +'languages updated': 'lenguajes actualizados', +'loading...': 'cargando...', +'located in the file': 'localizada en el archivo', +'login': 'inicio de sesión', +'logout': 'fin de sesión', +'lost password?': '¿olvido la contraseña?', +'merge': 'combinar', +'models': 'modelos', +'modules': 'módulos', +'new application "%s" created': 'nueva aplicación "%s" creada', +'new record inserted': 'nuevo registro insertado', +'next 100 rows': '100 filas siguientes', +'or import from csv file': 'o importar desde archivo CSV', +'or provide application url:': 'o provea URL de la aplicación:', +'pack all': 'empaquetar todo', +'pack compiled': 'empaquete compiladas', +'previous 100 rows': '100 filas anteriores', +'record': 'registro', +'record does not exist': 'el registro no existe', +'record id': 'id de registro', +'register': 'registrese', +'remove compiled': 'eliminar compiladas', +'restore': 'restaurar', +'revert': 'revertir', +'save': 'guardar', +'selected': 'seleccionado(s)', +'session expired': 'sesión expirada', +'shell': 'shell', +'site': 'sitio', +'some files could not be removed': 'algunos archivos no pudieron ser removidos', +'state': 'estado', +'static': 'estáticos', +'table': 'tabla', +'test': 'probar', +'the application logic, each URL path is mapped in one exposed function in the controller': 'la lógica de la aplicación, cada ruta URL se mapea en una función expuesta en el controlador', +'the data representation, define database tables and sets': 'la representación de datos, define tablas y conjuntos de base de datos', +'the presentations layer, views are also known as templates': 'la capa de presentación, las vistas también son llamadas plantillas', +'these files are served without processing, your images go here': 'estos archivos son servidos sin procesar, sus imágenes van aquí', +'to previous version.': 'a la versión previa.', +'translation strings for the application': 'cadenas de caracteres de traducción para la aplicación', +'try': 'intente', +'try something like': 'intente algo como', +'unable to create application "%s"': 'no es posible crear la aplicación "%s"', +'unable to delete file "%(filename)s"': 'no es posible eliminar el archivo "%(filename)s"', +'unable to parse csv file': 'no es posible analizar el archivo CSV', +'unable to uninstall "%s"': 'no es posible instalar "%s"', +'uncheck all': 'desmarcar todos', +'uninstall': 'desinstalar', +'update': 'actualizar', +'update all languages': 'actualizar todos los lenguajes', +'upload application:': 'subir aplicación:', +'upload file:': 'suba archivo:', +'versioning': 'versiones', +'view': 'vista', +'views': 'vistas', +'web2py Recent Tweets': 'Tweets Recientes de web2py', +'web2py is up to date': 'web2py está actualizado', +} ADDED applications/mobileblur/languages/fr-ca.py Index: applications/mobileblur/languages/fr-ca.py ================================================================== --- applications/mobileblur/languages/fr-ca.py +++ applications/mobileblur/languages/fr-ca.py @@ -0,0 +1,167 @@ +# coding: utf8 +{ +'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"update" est une expression optionnelle comme "champ1=\'nouvellevaleur\'". Vous ne pouvez mettre à jour ou supprimer les résultats d\'un JOIN', +'%Y-%m-%d': '%Y-%m-%d', +'%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S', +'%s rows deleted': '%s rangées supprimées', +'%s rows updated': '%s rangées mises à jour', +'About': 'À propos', +'Access Control': "Contrôle d'accès", +'Administrative interface': "Interface d'administration", +'Ajax Recipes': 'Recettes Ajax', +'Are you sure you want to delete this object?': 'Êtes-vous sûr de vouloir supprimer cet objet?', +'Authentication': 'Authentification', +'Available databases and tables': 'Bases de données et tables disponibles', +'Buy this book': 'Acheter ce livre', +'Cannot be empty': 'Ne peut pas être vide', +'Check to delete': 'Cliquez pour supprimer', +'Check to delete:': 'Cliquez pour supprimer:', +'Client IP': 'IP client', +'Community': 'Communauté', +'Controller': 'Contrôleur', +'Copyright': "Droit d'auteur", +'Current request': 'Demande actuelle', +'Current response': 'Réponse actuelle', +'Current session': 'Session en cours', +'DB Model': 'Modèle DB', +'Database': 'Base de données', +'Delete:': 'Supprimer:', +'Demo': 'Démo', +'Deployment Recipes': 'Recettes de déploiement ', +'Description': 'Descriptif', +'Documentation': 'Documentation', +'Download': 'Téléchargement', +'E-mail': 'Courriel', +'Edit': 'Éditer', +'Edit This App': 'Modifier cette application', +'Edit current record': "Modifier l'enregistrement courant", +'Errors': 'Erreurs', +'FAQ': 'faq', +'First name': 'Prénom', +'Forms and Validators': 'Formulaires et Validateurs', +'Free Applications': 'Applications gratuites', +'Function disabled': 'Fonction désactivée', +'Group %(group_id)s created': '%(group_id)s groupe créé', +'Group ID': 'Groupe ID', +'Group uniquely assigned to user %(id)s': "Groupe unique attribué à l'utilisateur %(id)s", +'Groups': 'Groupes', +'Hello World': 'Bonjour le monde', +'Home': 'Accueil', +'Import/Export': 'Importer/Exporter', +'Index': 'Index', +'Internal State': 'État interne', +'Introduction': 'Présentation', +'Invalid Query': 'Requête Invalide', +'Invalid email': 'Courriel invalide', +'Last name': 'Nom', +'Layout': 'Mise en page', +'Layouts': 'layouts', +'Live chat': 'Clavardage en direct', +'Logged in': 'Connecté', +'Login': 'Connectez-vous', +'Lost Password': 'Mot de passe perdu', +'Main Menu': 'Menu principal', +'Menu Model': 'Menu modèle', +'Name': 'Nom', +'New Record': 'Nouvel enregistrement', +'No databases in this application': "Cette application n'a pas de bases de données", +'Online examples': 'Exemples en ligne', +'Origin': 'Origine', +'Other Recipes': 'Autres recettes', +'Overview': 'Présentation', +'Password': 'Mot de passe', +"Password fields don't match": 'Les mots de passe ne correspondent pas', +'Plugins': 'Plugiciels', +'Powered by': 'Alimenté par', +'Preface': 'Préface', +'Python': 'Python', +'Query:': 'Requête:', +'Quick Examples': 'Examples Rapides', +'Readme': 'Lisez-moi', +'Recipes': 'Recettes', +'Record %(id)s created': 'Record %(id)s created', +'Record %(id)s updated': 'Record %(id)s updated', +'Record Created': 'Record Created', +'Record ID': "ID d'enregistrement", +'Record Updated': 'Record Updated', +'Register': "S'inscrire", +'Registration key': "Clé d'enregistrement", +'Registration successful': 'Inscription réussie', +'Remember me (for 30 days)': 'Se souvenir de moi (pendant 30 jours)', +'Request reset password': 'Demande de réinitialiser le mot clé', +'Reset Password key': 'Réinitialiser le mot clé', +'Resources': 'Ressources', +'Role': 'Rôle', +'Rows in table': 'Lignes du tableau', +'Rows selected': 'Lignes sélectionnées', +'Semantic': 'Sémantique', +'Services': 'Services', +'Stylesheet': 'Feuille de style', +'Submit': 'Soumettre', +'Support': 'Soutien', +'Sure you want to delete this object?': 'Êtes-vous sûr de vouloir supprimer cet objet?', +'Table name': 'Nom du tableau', +'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'La "query" est une condition comme "db.table1.champ1==\'valeur\'". Quelque chose comme "db.table1.champ1==db.table2.champ2" résulte en un JOIN SQL.', +'The Core': 'Le noyau', +'The Views': 'Les Vues', +'The output of the file is a dictionary that was rendered by the view': 'La sortie de ce fichier est un dictionnaire qui été restitué par la vue', +'This App': 'Cette Appli', +'This is a copy of the scaffolding application': "Ceci est une copie de l'application échafaudage", +'Timestamp': 'Horodatage', +'Twitter': 'Twitter', +'Update:': 'Mise à jour:', +'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Employez (...)&(...) pour AND, (...)|(...) pour OR, and ~(...) pour NOT pour construire des requêtes plus complexes.', +'User %(id)s Logged-in': 'Utilisateur %(id)s connecté', +'User %(id)s Registered': 'Utilisateur %(id)s enregistré', +'User ID': 'ID utilisateur', +'User Voice': 'User Voice', +'Verify Password': 'Vérifiez le mot de passe', +'Videos': 'Vidéos', +'View': 'Présentation', +'Web2py': 'Web2py', +'Welcome': 'Bienvenu', +'Welcome %s': 'Bienvenue %s', +'Welcome to web2py': 'Bienvenue à web2py', +'Which called the function': 'Qui a appelé la fonction', +'You are successfully running web2py': 'Vous roulez avec succès web2py', +'You can modify this application and adapt it to your needs': "Vous pouvez modifier cette application et l'adapter à vos besoins", +'You visited the url': "Vous avez visité l'URL", +'about': 'à propos', +'appadmin is disabled because insecure channel': "appadmin est désactivée parce que le canal n'est pas sécurisé", +'cache': 'cache', +'change password': 'changer le mot de passe', +'customize me!': 'personnalisez-moi!', +'data uploaded': 'données téléchargées', +'database': 'base de données', +'database %s select': 'base de données %s select', +'db': 'db', +'design': 'design', +'done!': 'fait!', +'edit profile': 'modifier le profil', +'enter an integer between %(min)g and %(max)g': 'entrer un entier compris entre %(min)g et %(max)g', +'export as csv file': 'exporter sous forme de fichier csv', +'insert new': 'insérer un nouveau', +'insert new %s': 'insérer un nouveau %s', +'invalid request': 'requête invalide', +'located in the file': 'se trouvant dans le fichier', +'login': 'connectez-vous', +'logout': 'déconnectez-vous', +'lost password': 'mot de passe perdu', +'lost password?': 'mot de passe perdu?', +'new record inserted': 'nouvel enregistrement inséré', +'next 100 rows': '100 prochaines lignes', +'or import from csv file': "ou importer d'un fichier CSV", +'password': 'mot de passe', +'please input your password again': "S'il vous plaît entrer votre mot de passe", +'previous 100 rows': '100 lignes précédentes', +'profile': 'profile', +'record': 'enregistrement', +'record does not exist': "l'archive n'existe pas", +'record id': "id d'enregistrement", +'register': "s'inscrire", +'selected': 'sélectionné', +'state': 'état', +'table': 'tableau', +'unable to parse csv file': "incapable d'analyser le fichier cvs", +'value already in database or empty': 'valeur déjà dans la base ou vide', +} ADDED applications/mobileblur/languages/fr-fr.py Index: applications/mobileblur/languages/fr-fr.py ================================================================== --- applications/mobileblur/languages/fr-fr.py +++ applications/mobileblur/languages/fr-fr.py @@ -0,0 +1,155 @@ +# coding: utf8 +{ +'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"update" est une expression optionnelle comme "champ1=\'nouvellevaleur\'". Vous ne pouvez mettre à jour ou supprimer les résultats d\'un JOIN', +'%Y-%m-%d': '%Y-%m-%d', +'%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S', +'%s rows deleted': '%s rangées supprimées', +'%s rows updated': '%s rangées mises à jour', +'About': 'À propos', +'Access Control': 'Contrôle d\'accès', +'Ajax Recipes': 'Recettes Ajax', +'Are you sure you want to delete this object?': 'Êtes-vous sûr de vouloir supprimer cet objet?', +'Authentication': 'Authentification', +'Available databases and tables': 'Bases de données et tables disponibles', +'Buy this book': 'Acheter ce livre', +'Cannot be empty': 'Ne peut pas être vide', +'Check to delete': 'Cliquez pour supprimer', +'Check to delete:': 'Cliquez pour supprimer:', +'Client IP': 'IP client', +'Community': 'Communauté', +'Controller': 'Contrôleur', +'Copyright': 'Copyright', +'Current request': 'Demande actuelle', +'Current response': 'Réponse actuelle', +'Current session': 'Session en cours', +'DB Model': 'Modèle DB', +'Database': 'Base de données', +'Delete:': 'Supprimer:', +'Demo': 'Démo', +'Deployment Recipes': 'Recettes de déploiement', +'Description': 'Description', +'Documentation': 'Documentation', +'Download': 'Téléchargement', +'E-mail': 'E-mail', +'Edit': 'Éditer', +'Edit This App': 'Modifier cette application', +'Edit current record': "Modifier l'enregistrement courant", +'Errors': 'Erreurs', +'FAQ': 'FAQ', +'First name': 'Prénom', +'Forms and Validators': 'Formulaires et Validateurs', +'Free Applications': 'Applications gratuites', +'Function disabled': 'Fonction désactivée', +'Group ID': 'Groupe ID', +'Groups': 'Groups', +'Hello World': 'Bonjour le monde', +'Home': 'Accueil', +'Import/Export': 'Importer/Exporter', +'Index': 'Index', +'Internal State': 'État interne', +'Introduction': 'Introduction', +'Invalid Query': 'Requête Invalide', +'Invalid email': 'E-mail invalide', +'Last name': 'Nom', +'Layout': 'Mise en page', +'Layouts': 'Layouts', +'Live chat': 'Chat live', +'Login': 'Connectez-vous', +'Lost Password': 'Mot de passe perdu', +'Main Menu': 'Menu principal', +'Menu Model': 'Menu modèle', +'Name': 'Nom', +'New Record': 'Nouvel enregistrement', +'No databases in this application': "Cette application n'a pas de bases de données", +'Origin': 'Origine', +'Other Recipes': 'Autres recettes', +'Overview': 'Présentation', +'Password': 'Mot de passe', +"Password fields don't match": 'Les mots de passe ne correspondent pas', +'Plugins': 'Plugiciels', +'Powered by': 'Alimenté par', +'Preface': 'Préface', +'Python': 'Python', +'Query:': 'Requête:', +'Quick Examples': 'Examples Rapides', +'Recipes': 'Recettes', +'Record ID': 'ID d\'enregistrement', +'Register': "S'inscrire", +'Registration key': "Clé d'enregistrement", +'Remember me (for 30 days)': 'Se souvenir de moi (pendant 30 jours)', +'Request reset password': 'Demande de réinitialiser le mot clé', +'Reset Password key': 'Réinitialiser le mot clé', +'Resources': 'Ressources', +'Role': 'Rôle', +'Rows in table': 'Lignes du tableau', +'Rows selected': 'Lignes sélectionnées', +'Semantic': 'Sémantique', +'Services': 'Services', +'Stylesheet': 'Feuille de style', +'Submit': 'Soumettre', +'Support': 'Support', +'Sure you want to delete this object?': 'Êtes-vous sûr de vouloir supprimer cet objet?', +'Table name': 'Nom du tableau', +'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'La "query" est une condition comme "db.table1.champ1==\'valeur\'". Quelque chose comme "db.table1.champ1==db.table2.champ2" résulte en un JOIN SQL.', +'The Core': 'Le noyau', +'The Views': 'Les Vues', +'The output of the file is a dictionary that was rendered by the view': 'La sortie de ce fichier est un dictionnaire qui été restitué par la vue', +'This App': 'Cette Appli', +'This is a copy of the scaffolding application': 'Ceci est une copie de l\'application échafaudage', +'Timestamp': 'Horodatage', +'Twitter': 'Twitter', +'Update:': 'Mise à jour:', +'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Employez (...)&(...) pour AND, (...)|(...) pour OR, and ~(...) pour NOT pour construire des requêtes plus complexes.', +'User %(id)s Logged-in': 'Utilisateur %(id)s connecté', +'User %(id)s Registered': 'Utilisateur %(id)s enregistré', +'User ID': 'ID utilisateur', +'User Voice': 'User Voice', +'Verify Password': 'Vérifiez le mot de passe', +'Videos': 'Vidéos', +'View': 'Présentation', +'Web2py': 'Web2py', +'Welcome': 'Bienvenu', +'Welcome %s': 'Bienvenue %s', +'Welcome to web2py': 'Bienvenue à web2py', +'Which called the function': 'Qui a appelé la fonction', +'You are successfully running web2py': 'Vous roulez avec succès web2py', +'You can modify this application and adapt it to your needs': 'Vous pouvez modifier cette application et l\'adapter à vos besoins', +'You visited the url': 'Vous avez visité l\'URL', +'appadmin is disabled because insecure channel': "appadmin est désactivée parce que le canal n'est pas sécurisé", +'cache': 'cache', +'change password': 'changer le mot de passe', +'Online examples': 'Exemples en ligne', +'Administrative interface': "Interface d'administration", +'customize me!': 'personnalisez-moi!', +'data uploaded': 'données téléchargées', +'database': 'base de données', +'database %s select': 'base de données %s select', +'db': 'db', +'design': 'design', +'Documentation': 'Documentation', +'done!': 'fait!', +'edit profile': 'modifier le profil', +'enter an integer between %(min)g and %(max)g': 'enter an integer between %(min)g and %(max)g', +'export as csv file': 'exporter sous forme de fichier csv', +'insert new': 'insérer un nouveau', +'insert new %s': 'insérer un nouveau %s', +'invalid request': 'requête invalide', +'located in the file': 'se trouvant dans le fichier', +'login': 'connectez-vous', +'logout': 'déconnectez-vous', +'lost password': 'mot de passe perdu', +'lost password?': 'mot de passe perdu?', +'new record inserted': 'nouvel enregistrement inséré', +'next 100 rows': '100 prochaines lignes', +'or import from csv file': "ou importer d'un fichier CSV", +'previous 100 rows': '100 lignes précédentes', +'record': 'enregistrement', +'record does not exist': "l'archive n'existe pas", +'record id': "id d'enregistrement", +'register': "s'inscrire", +'selected': 'sélectionné', +'state': 'état', +'table': 'tableau', +'unable to parse csv file': "incapable d'analyser le fichier cvs", +'Readme': "Lisez-moi", +} ADDED applications/mobileblur/languages/hi-hi.py Index: applications/mobileblur/languages/hi-hi.py ================================================================== --- applications/mobileblur/languages/hi-hi.py +++ applications/mobileblur/languages/hi-hi.py @@ -0,0 +1,82 @@ +# coding: utf8 +{ +'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN', +'%Y-%m-%d': '%Y-%m-%d', +'%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S', +'%s rows deleted': '%s \xe0\xa4\xaa\xe0\xa4\x82\xe0\xa4\x95\xe0\xa5\x8d\xe0\xa4\xa4\xe0\xa4\xbf\xe0\xa4\xaf\xe0\xa4\xbe\xe0\xa4\x81 \xe0\xa4\xae\xe0\xa4\xbf\xe0\xa4\x9f\xe0\xa4\xbe\xe0\xa4\x8f\xe0\xa4\x81', +'%s rows updated': '%s \xe0\xa4\xaa\xe0\xa4\x82\xe0\xa4\x95\xe0\xa5\x8d\xe0\xa4\xa4\xe0\xa4\xbf\xe0\xa4\xaf\xe0\xa4\xbe\xe0\xa4\x81 \xe0\xa4\x85\xe0\xa4\xa6\xe0\xa5\x8d\xe0\xa4\xaf\xe0\xa4\xa4\xe0\xa4\xa8', +'Available databases and tables': '\xe0\xa4\x89\xe0\xa4\xaa\xe0\xa4\xb2\xe0\xa4\xac\xe0\xa5\x8d\xe0\xa4\xa7 \xe0\xa4\xa1\xe0\xa5\x87\xe0\xa4\x9f\xe0\xa4\xbe\xe0\xa4\xac\xe0\xa5\x87\xe0\xa4\xb8 \xe0\xa4\x94\xe0\xa4\xb0 \xe0\xa4\xa4\xe0\xa4\xbe\xe0\xa4\xb2\xe0\xa4\xbf\xe0\xa4\x95\xe0\xa4\xbe', +'Cannot be empty': '\xe0\xa4\x96\xe0\xa4\xbe\xe0\xa4\xb2\xe0\xa5\x80 \xe0\xa4\xa8\xe0\xa4\xb9\xe0\xa5\x80\xe0\xa4\x82 \xe0\xa4\xb9\xe0\xa5\x8b \xe0\xa4\xb8\xe0\xa4\x95\xe0\xa4\xa4\xe0\xa4\xbe', +'Change Password': '\xe0\xa4\xaa\xe0\xa4\xbe\xe0\xa4\xb8\xe0\xa4\xb5\xe0\xa4\xb0\xe0\xa5\x8d\xe0\xa4\xa1 \xe0\xa4\xac\xe0\xa4\xa6\xe0\xa4\xb2\xe0\xa5\x87\xe0\xa4\x82', +'Check to delete': '\xe0\xa4\xb9\xe0\xa4\x9f\xe0\xa4\xbe\xe0\xa4\xa8\xe0\xa5\x87 \xe0\xa4\x95\xe0\xa5\x87 \xe0\xa4\xb2\xe0\xa4\xbf\xe0\xa4\x8f \xe0\xa4\x9a\xe0\xa5\x81\xe0\xa4\xa8\xe0\xa5\x87\xe0\xa4\x82', +'Controller': 'Controller', +'Copyright': 'Copyright', +'Current request': '\xe0\xa4\xb5\xe0\xa4\xb0\xe0\xa5\x8d\xe0\xa4\xa4\xe0\xa4\xae\xe0\xa4\xbe\xe0\xa4\xa8 \xe0\xa4\x85\xe0\xa4\xa8\xe0\xa5\x81\xe0\xa4\xb0\xe0\xa5\x8b\xe0\xa4\xa7', +'Current response': '\xe0\xa4\xb5\xe0\xa4\xb0\xe0\xa5\x8d\xe0\xa4\xa4\xe0\xa4\xae\xe0\xa4\xbe\xe0\xa4\xa8 \xe0\xa4\xaa\xe0\xa5\x8d\xe0\xa4\xb0\xe0\xa4\xa4\xe0\xa4\xbf\xe0\xa4\x95\xe0\xa5\x8d\xe0\xa4\xb0\xe0\xa4\xbf\xe0\xa4\xaf\xe0\xa4\xbe', +'Current session': '\xe0\xa4\xb5\xe0\xa4\xb0\xe0\xa5\x8d\xe0\xa4\xa4\xe0\xa4\xae\xe0\xa4\xbe\xe0\xa4\xa8 \xe0\xa4\xb8\xe0\xa5\x87\xe0\xa4\xb6\xe0\xa4\xa8', +'DB Model': 'DB Model', +'Database': 'Database', +'Delete:': '\xe0\xa4\xae\xe0\xa4\xbf\xe0\xa4\x9f\xe0\xa4\xbe\xe0\xa4\xa8\xe0\xa4\xbe:', +'Edit': 'Edit', +'Edit Profile': '\xe0\xa4\xaa\xe0\xa5\x8d\xe0\xa4\xb0\xe0\xa5\x8b\xe0\xa4\xab\xe0\xa4\xbc\xe0\xa4\xbe\xe0\xa4\x87\xe0\xa4\xb2 \xe0\xa4\xb8\xe0\xa4\x82\xe0\xa4\xaa\xe0\xa4\xbe\xe0\xa4\xa6\xe0\xa4\xbf\xe0\xa4\xa4 \xe0\xa4\x95\xe0\xa4\xb0\xe0\xa5\x87\xe0\xa4\x82', +'Edit This App': 'Edit This App', +'Edit current record': '\xe0\xa4\xb5\xe0\xa4\xb0\xe0\xa5\x8d\xe0\xa4\xa4\xe0\xa4\xae\xe0\xa4\xbe\xe0\xa4\xa8 \xe0\xa4\xb0\xe0\xa5\x87\xe0\xa4\x95\xe0\xa5\x89\xe0\xa4\xb0\xe0\xa5\x8d\xe0\xa4\xa1 \xe0\xa4\xb8\xe0\xa4\x82\xe0\xa4\xaa\xe0\xa4\xbe\xe0\xa4\xa6\xe0\xa4\xbf\xe0\xa4\xa4 \xe0\xa4\x95\xe0\xa4\xb0\xe0\xa5\x87\xe0\xa4\x82 ', +'Hello World': 'Hello World', +'Hello from MyApp': 'Hello from MyApp', +'Import/Export': '\xe0\xa4\x86\xe0\xa4\xaf\xe0\xa4\xbe\xe0\xa4\xa4 / \xe0\xa4\xa8\xe0\xa4\xbf\xe0\xa4\xb0\xe0\xa5\x8d\xe0\xa4\xaf\xe0\xa4\xbe\xe0\xa4\xa4', +'Index': 'Index', +'Internal State': '\xe0\xa4\x86\xe0\xa4\x82\xe0\xa4\xa4\xe0\xa4\xb0\xe0\xa4\xbf\xe0\xa4\x95 \xe0\xa4\xb8\xe0\xa5\x8d\xe0\xa4\xa5\xe0\xa4\xbf\xe0\xa4\xa4\xe0\xa4\xbf', +'Invalid Query': '\xe0\xa4\x85\xe0\xa4\xae\xe0\xa4\xbe\xe0\xa4\xa8\xe0\xa5\x8d\xe0\xa4\xaf \xe0\xa4\xaa\xe0\xa5\x8d\xe0\xa4\xb0\xe0\xa4\xb6\xe0\xa5\x8d\xe0\xa4\xa8', +'Layout': 'Layout', +'Login': '\xe0\xa4\xb2\xe0\xa5\x89\xe0\xa4\x97 \xe0\xa4\x87\xe0\xa4\xa8', +'Logout': '\xe0\xa4\xb2\xe0\xa5\x89\xe0\xa4\x97 \xe0\xa4\x86\xe0\xa4\x89\xe0\xa4\x9f', +'Lost Password': '\xe0\xa4\xaa\xe0\xa4\xbe\xe0\xa4\xb8\xe0\xa4\xb5\xe0\xa4\xb0\xe0\xa5\x8d\xe0\xa4\xa1 \xe0\xa4\x96\xe0\xa5\x8b \xe0\xa4\x97\xe0\xa4\xaf\xe0\xa4\xbe', +'Main Menu': 'Main Menu', +'Menu Model': 'Menu Model', +'New Record': '\xe0\xa4\xa8\xe0\xa4\xaf\xe0\xa4\xbe \xe0\xa4\xb0\xe0\xa5\x87\xe0\xa4\x95\xe0\xa5\x89\xe0\xa4\xb0\xe0\xa5\x8d\xe0\xa4\xa1', +'No databases in this application': '\xe0\xa4\x87\xe0\xa4\xb8 \xe0\xa4\x85\xe0\xa4\xa8\xe0\xa5\x81\xe0\xa4\xaa\xe0\xa5\x8d\xe0\xa4\xb0\xe0\xa4\xaf\xe0\xa5\x8b\xe0\xa4\x97 \xe0\xa4\xae\xe0\xa5\x87\xe0\xa4\x82 \xe0\xa4\x95\xe0\xa5\x8b\xe0\xa4\x88 \xe0\xa4\xa1\xe0\xa5\x87\xe0\xa4\x9f\xe0\xa4\xbe\xe0\xa4\xac\xe0\xa5\x87\xe0\xa4\xb8 \xe0\xa4\xa8\xe0\xa4\xb9\xe0\xa5\x80\xe0\xa4\x82 \xe0\xa4\xb9\xe0\xa5\x88\xe0\xa4\x82', +'Powered by': 'Powered by', +'Query:': '\xe0\xa4\xaa\xe0\xa5\x8d\xe0\xa4\xb0\xe0\xa4\xb6\xe0\xa5\x8d\xe0\xa4\xa8:', +'Register': '\xe0\xa4\xaa\xe0\xa4\x82\xe0\xa4\x9c\xe0\xa5\x80\xe0\xa4\x95\xe0\xa5\x83\xe0\xa4\xa4 (\xe0\xa4\xb0\xe0\xa4\x9c\xe0\xa4\xbf\xe0\xa4\xb8\xe0\xa5\x8d\xe0\xa4\x9f\xe0\xa4\xb0) \xe0\xa4\x95\xe0\xa4\xb0\xe0\xa4\xa8\xe0\xa4\xbe ', +'Rows in table': '\xe0\xa4\xa4\xe0\xa4\xbe\xe0\xa4\xb2\xe0\xa4\xbf\xe0\xa4\x95\xe0\xa4\xbe \xe0\xa4\xae\xe0\xa5\x87\xe0\xa4\x82 \xe0\xa4\xaa\xe0\xa4\x82\xe0\xa4\x95\xe0\xa5\x8d\xe0\xa4\xa4\xe0\xa4\xbf\xe0\xa4\xaf\xe0\xa4\xbe\xe0\xa4\x81 ', +'Rows selected': '\xe0\xa4\x9a\xe0\xa4\xaf\xe0\xa4\xa8\xe0\xa4\xbf\xe0\xa4\xa4 (\xe0\xa4\x9a\xe0\xa5\x81\xe0\xa4\xa8\xe0\xa5\x87 \xe0\xa4\x97\xe0\xa4\xaf\xe0\xa5\x87) \xe0\xa4\xaa\xe0\xa4\x82\xe0\xa4\x95\xe0\xa5\x8d\xe0\xa4\xa4\xe0\xa4\xbf\xe0\xa4\xaf\xe0\xa4\xbe\xe0\xa4\x81 ', +'Stylesheet': 'Stylesheet', +'Sure you want to delete this object?': '\xe0\xa4\xb8\xe0\xa5\x81\xe0\xa4\xa8\xe0\xa4\xbf\xe0\xa4\xb6\xe0\xa5\x8d\xe0\xa4\x9a\xe0\xa4\xbf\xe0\xa4\xa4 \xe0\xa4\xb9\xe0\xa5\x88\xe0\xa4\x82 \xe0\xa4\x95\xe0\xa4\xbf \xe0\xa4\x86\xe0\xa4\xaa \xe0\xa4\x87\xe0\xa4\xb8 \xe0\xa4\xb5\xe0\xa4\xb8\xe0\xa5\x8d\xe0\xa4\xa4\xe0\xa5\x81 \xe0\xa4\x95\xe0\xa5\x8b \xe0\xa4\xb9\xe0\xa4\x9f\xe0\xa4\xbe\xe0\xa4\xa8\xe0\xa4\xbe \xe0\xa4\x9a\xe0\xa4\xbe\xe0\xa4\xb9\xe0\xa4\xa4\xe0\xa5\x87 \xe0\xa4\xb9\xe0\xa5\x88\xe0\xa4\x82?', +'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.', +'Update:': '\xe0\xa4\x85\xe0\xa4\xa6\xe0\xa5\x8d\xe0\xa4\xaf\xe0\xa4\xa4\xe0\xa4\xa8 \xe0\xa4\x95\xe0\xa4\xb0\xe0\xa4\xa8\xe0\xa4\xbe:', +'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.', +'View': 'View', +'Welcome %s': 'Welcome %s', +'Welcome to web2py': '\xe0\xa4\xb5\xe0\xa5\x87\xe0\xa4\xac\xe0\xa5\xa8\xe0\xa4\xaa\xe0\xa4\xbe\xe0\xa4\x87 (web2py) \xe0\xa4\xae\xe0\xa5\x87\xe0\xa4\x82 \xe0\xa4\x86\xe0\xa4\xaa\xe0\xa4\x95\xe0\xa4\xbe \xe0\xa4\xb8\xe0\xa5\x8d\xe0\xa4\xb5\xe0\xa4\xbe\xe0\xa4\x97\xe0\xa4\xa4 \xe0\xa4\xb9\xe0\xa5\x88', +'appadmin is disabled because insecure channel': '\xe0\xa4\x85\xe0\xa4\xaa \xe0\xa4\x86\xe0\xa4\xa1\xe0\xa4\xae\xe0\xa4\xbf\xe0\xa4\xa8 (appadmin) \xe0\xa4\x85\xe0\xa4\x95\xe0\xa5\x8d\xe0\xa4\xb7\xe0\xa4\xae \xe0\xa4\xb9\xe0\xa5\x88 \xe0\xa4\x95\xe0\xa5\x8d\xe0\xa4\xaf\xe0\xa5\x8b\xe0\xa4\x82\xe0\xa4\x95\xe0\xa4\xbf \xe0\xa4\x85\xe0\xa4\xb8\xe0\xa5\x81\xe0\xa4\xb0\xe0\xa4\x95\xe0\xa5\x8d\xe0\xa4\xb7\xe0\xa4\xbf\xe0\xa4\xa4 \xe0\xa4\x9a\xe0\xa5\x88\xe0\xa4\xa8\xe0\xa4\xb2', +'cache': 'cache', +'change password': 'change password', +'Online examples': '\xe0\xa4\x91\xe0\xa4\xa8\xe0\xa4\xb2\xe0\xa4\xbe\xe0\xa4\x87\xe0\xa4\xa8 \xe0\xa4\x89\xe0\xa4\xa6\xe0\xa4\xbe\xe0\xa4\xb9\xe0\xa4\xb0\xe0\xa4\xa3 \xe0\xa4\x95\xe0\xa5\x87 \xe0\xa4\xb2\xe0\xa4\xbf\xe0\xa4\x8f \xe0\xa4\xaf\xe0\xa4\xb9\xe0\xa4\xbe\xe0\xa4\x81 \xe0\xa4\x95\xe0\xa5\x8d\xe0\xa4\xb2\xe0\xa4\xbf\xe0\xa4\x95 \xe0\xa4\x95\xe0\xa4\xb0\xe0\xa5\x87\xe0\xa4\x82', +'Administrative interface': '\xe0\xa4\xaa\xe0\xa5\x8d\xe0\xa4\xb0\xe0\xa4\xb6\xe0\xa4\xbe\xe0\xa4\xb8\xe0\xa4\xa8\xe0\xa4\xbf\xe0\xa4\x95 \xe0\xa4\x87\xe0\xa4\x82\xe0\xa4\x9f\xe0\xa4\xb0\xe0\xa4\xab\xe0\xa5\x87\xe0\xa4\xb8 \xe0\xa4\x95\xe0\xa5\x87 \xe0\xa4\xb2\xe0\xa4\xbf\xe0\xa4\x8f \xe0\xa4\xaf\xe0\xa4\xb9\xe0\xa4\xbe\xe0\xa4\x81 \xe0\xa4\x95\xe0\xa5\x8d\xe0\xa4\xb2\xe0\xa4\xbf\xe0\xa4\x95 \xe0\xa4\x95\xe0\xa4\xb0\xe0\xa5\x87\xe0\xa4\x82', +'customize me!': '\xe0\xa4\xae\xe0\xa5\x81\xe0\xa4\x9d\xe0\xa5\x87 \xe0\xa4\x85\xe0\xa4\xa8\xe0\xa5\x81\xe0\xa4\x95\xe0\xa5\x82\xe0\xa4\xb2\xe0\xa4\xbf\xe0\xa4\xa4 (\xe0\xa4\x95\xe0\xa4\xb8\xe0\xa5\x8d\xe0\xa4\x9f\xe0\xa4\xae\xe0\xa4\xbe\xe0\xa4\x87\xe0\xa4\x9c\xe0\xa4\xbc) \xe0\xa4\x95\xe0\xa4\xb0\xe0\xa5\x87\xe0\xa4\x82!', +'data uploaded': '\xe0\xa4\xa1\xe0\xa4\xbe\xe0\xa4\x9f\xe0\xa4\xbe \xe0\xa4\x85\xe0\xa4\xaa\xe0\xa4\xb2\xe0\xa5\x8b\xe0\xa4\xa1 \xe0\xa4\xb8\xe0\xa4\xae\xe0\xa5\x8d\xe0\xa4\xaa\xe0\xa4\xa8\xe0\xa5\x8d\xe0\xa4\xa8 ', +'database': '\xe0\xa4\xa1\xe0\xa5\x87\xe0\xa4\x9f\xe0\xa4\xbe\xe0\xa4\xac\xe0\xa5\x87\xe0\xa4\xb8', +'database %s select': '\xe0\xa4\xa1\xe0\xa5\x87\xe0\xa4\x9f\xe0\xa4\xbe\xe0\xa4\xac\xe0\xa5\x87\xe0\xa4\xb8 %s \xe0\xa4\x9a\xe0\xa5\x81\xe0\xa4\xa8\xe0\xa5\x80 \xe0\xa4\xb9\xe0\xa5\x81\xe0\xa4\x88', +'db': 'db', +'design': '\xe0\xa4\xb0\xe0\xa4\x9a\xe0\xa4\xa8\xe0\xa4\xbe \xe0\xa4\x95\xe0\xa4\xb0\xe0\xa5\x87\xe0\xa4\x82', +'done!': '\xe0\xa4\xb9\xe0\xa5\x8b \xe0\xa4\x97\xe0\xa4\xaf\xe0\xa4\xbe!', +'edit profile': 'edit profile', +'export as csv file': 'csv \xe0\xa4\xab\xe0\xa4\xbc\xe0\xa4\xbe\xe0\xa4\x87\xe0\xa4\xb2 \xe0\xa4\x95\xe0\xa5\x87 \xe0\xa4\xb0\xe0\xa5\x82\xe0\xa4\xaa \xe0\xa4\xae\xe0\xa5\x87\xe0\xa4\x82 \xe0\xa4\xa8\xe0\xa4\xbf\xe0\xa4\xb0\xe0\xa5\x8d\xe0\xa4\xaf\xe0\xa4\xbe\xe0\xa4\xa4', +'insert new': '\xe0\xa4\xa8\xe0\xa4\xaf\xe0\xa4\xbe \xe0\xa4\xa1\xe0\xa4\xbe\xe0\xa4\xb2\xe0\xa5\x87\xe0\xa4\x82', +'insert new %s': '\xe0\xa4\xa8\xe0\xa4\xaf\xe0\xa4\xbe %s \xe0\xa4\xa1\xe0\xa4\xbe\xe0\xa4\xb2\xe0\xa5\x87\xe0\xa4\x82', +'invalid request': '\xe0\xa4\x85\xe0\xa4\xb5\xe0\xa5\x88\xe0\xa4\xa7 \xe0\xa4\x85\xe0\xa4\xa8\xe0\xa5\x81\xe0\xa4\xb0\xe0\xa5\x8b\xe0\xa4\xa7', +'login': 'login', +'logout': 'logout', +'new record inserted': '\xe0\xa4\xa8\xe0\xa4\xaf\xe0\xa4\xbe \xe0\xa4\xb0\xe0\xa5\x87\xe0\xa4\x95\xe0\xa5\x89\xe0\xa4\xb0\xe0\xa5\x8d\xe0\xa4\xa1 \xe0\xa4\xa1\xe0\xa4\xbe\xe0\xa4\xb2\xe0\xa4\xbe', +'next 100 rows': '\xe0\xa4\x85\xe0\xa4\x97\xe0\xa4\xb2\xe0\xa5\x87 100 \xe0\xa4\xaa\xe0\xa4\x82\xe0\xa4\x95\xe0\xa5\x8d\xe0\xa4\xa4\xe0\xa4\xbf\xe0\xa4\xaf\xe0\xa4\xbe\xe0\xa4\x81', +'or import from csv file': '\xe0\xa4\xaf\xe0\xa4\xbe csv \xe0\xa4\xab\xe0\xa4\xbc\xe0\xa4\xbe\xe0\xa4\x87\xe0\xa4\xb2 \xe0\xa4\xb8\xe0\xa5\x87 \xe0\xa4\x86\xe0\xa4\xaf\xe0\xa4\xbe\xe0\xa4\xa4', +'previous 100 rows': '\xe0\xa4\xaa\xe0\xa4\xbf\xe0\xa4\x9b\xe0\xa4\xb2\xe0\xa5\x87 100 \xe0\xa4\xaa\xe0\xa4\x82\xe0\xa4\x95\xe0\xa5\x8d\xe0\xa4\xa4\xe0\xa4\xbf\xe0\xa4\xaf\xe0\xa4\xbe\xe0\xa4\x81', +'record': 'record', +'record does not exist': '\xe0\xa4\xb0\xe0\xa4\xbf\xe0\xa4\x95\xe0\xa5\x89\xe0\xa4\xb0\xe0\xa5\x8d\xe0\xa4\xa1 \xe0\xa4\xae\xe0\xa5\x8c\xe0\xa4\x9c\xe0\xa5\x82\xe0\xa4\xa6 \xe0\xa4\xa8\xe0\xa4\xb9\xe0\xa5\x80\xe0\xa4\x82 \xe0\xa4\xb9\xe0\xa5\x88', +'record id': '\xe0\xa4\xb0\xe0\xa4\xbf\xe0\xa4\x95\xe0\xa5\x89\xe0\xa4\xb0\xe0\xa5\x8d\xe0\xa4\xa1 \xe0\xa4\xaa\xe0\xa4\xb9\xe0\xa4\x9a\xe0\xa4\xbe\xe0\xa4\xa8\xe0\xa4\x95\xe0\xa4\xb0\xe0\xa5\x8d\xe0\xa4\xa4\xe0\xa4\xbe (\xe0\xa4\x86\xe0\xa4\x88\xe0\xa4\xa1\xe0\xa5\x80)', +'register': 'register', +'selected': '\xe0\xa4\x9a\xe0\xa5\x81\xe0\xa4\xa8\xe0\xa4\xbe \xe0\xa4\xb9\xe0\xa5\x81\xe0\xa4\x86', +'state': '\xe0\xa4\xb8\xe0\xa5\x8d\xe0\xa4\xa5\xe0\xa4\xbf\xe0\xa4\xa4\xe0\xa4\xbf', +'table': '\xe0\xa4\xa4\xe0\xa4\xbe\xe0\xa4\xb2\xe0\xa4\xbf\xe0\xa4\x95\xe0\xa4\xbe', +'unable to parse csv file': 'csv \xe0\xa4\xab\xe0\xa4\xbc\xe0\xa4\xbe\xe0\xa4\x87\xe0\xa4\xb2 \xe0\xa4\xaa\xe0\xa4\xbe\xe0\xa4\xb0\xe0\xa5\x8d\xe0\xa4\xb8 \xe0\xa4\x95\xe0\xa4\xb0\xe0\xa4\xa8\xe0\xa5\x87 \xe0\xa4\xae\xe0\xa5\x87\xe0\xa4\x82 \xe0\xa4\x85\xe0\xa4\xb8\xe0\xa4\xae\xe0\xa4\xb0\xe0\xa5\x8d\xe0\xa4\xa5', +} ADDED applications/mobileblur/languages/hu-hu.py Index: applications/mobileblur/languages/hu-hu.py ================================================================== --- applications/mobileblur/languages/hu-hu.py +++ applications/mobileblur/languages/hu-hu.py @@ -0,0 +1,93 @@ +# coding: utf8 +{ +'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN', +'%Y-%m-%d': '%Y.%m.%d.', +'%Y-%m-%d %H:%M:%S': '%Y.%m.%d. %H:%M:%S', +'%s rows deleted': '%s sorok t\xc3\xb6rl\xc5\x91dtek', +'%s rows updated': '%s sorok friss\xc3\xadt\xc5\x91dtek', +'Available databases and tables': 'El\xc3\xa9rhet\xc5\x91 adatb\xc3\xa1zisok \xc3\xa9s t\xc3\xa1bl\xc3\xa1k', +'Cannot be empty': 'Nem lehet \xc3\xbcres', +'Check to delete': 'T\xc3\xb6rl\xc3\xa9shez v\xc3\xa1laszd ki', +'Client IP': 'Client IP', +'Controller': 'Controller', +'Copyright': 'Copyright', +'Current request': 'Jelenlegi lek\xc3\xa9rdez\xc3\xa9s', +'Current response': 'Jelenlegi v\xc3\xa1lasz', +'Current session': 'Jelenlegi folyamat', +'DB Model': 'DB Model', +'Database': 'Adatb\xc3\xa1zis', +'Delete:': 'T\xc3\xb6r\xc3\xb6l:', +'Description': 'Description', +'E-mail': 'E-mail', +'Edit': 'Szerkeszt', +'Edit This App': 'Alkalmaz\xc3\xa1st szerkeszt', +'Edit current record': 'Aktu\xc3\xa1lis bejegyz\xc3\xa9s szerkeszt\xc3\xa9se', +'First name': 'First name', +'Group ID': 'Group ID', +'Hello World': 'Hello Vil\xc3\xa1g', +'Import/Export': 'Import/Export', +'Index': 'Index', +'Internal State': 'Internal State', +'Invalid Query': 'Hib\xc3\xa1s lek\xc3\xa9rdez\xc3\xa9s', +'Invalid email': 'Invalid email', +'Last name': 'Last name', +'Layout': 'Szerkezet', +'Main Menu': 'F\xc5\x91men\xc3\xbc', +'Menu Model': 'Men\xc3\xbc model', +'Name': 'Name', +'New Record': '\xc3\x9aj bejegyz\xc3\xa9s', +'No databases in this application': 'Nincs adatb\xc3\xa1zis ebben az alkalmaz\xc3\xa1sban', +'Origin': 'Origin', +'Password': 'Password', +'Powered by': 'Powered by', +'Query:': 'Lek\xc3\xa9rdez\xc3\xa9s:', +'Record ID': 'Record ID', +'Registration key': 'Registration key', +'Reset Password key': 'Reset Password key', +'Role': 'Role', +'Rows in table': 'Sorok a t\xc3\xa1bl\xc3\xa1ban', +'Rows selected': 'Kiv\xc3\xa1lasztott sorok', +'Stylesheet': 'Stylesheet', +'Sure you want to delete this object?': 'Biztos t\xc3\xb6rli ezt az objektumot?', +'Table name': 'Table name', +'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.', +'Timestamp': 'Timestamp', +'Update:': 'Friss\xc3\xadt:', +'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.', +'User ID': 'User ID', +'View': 'N\xc3\xa9zet', +'Welcome %s': 'Welcome %s', +'Welcome to web2py': 'Isten hozott a web2py-ban', +'appadmin is disabled because insecure channel': 'az appadmin a biztons\xc3\xa1gtalan csatorna miatt letiltva', +'cache': 'gyors\xc3\xadt\xc3\xb3t\xc3\xa1r', +'change password': 'jelsz\xc3\xb3 megv\xc3\xa1ltoztat\xc3\xa1sa', +'Online examples': 'online p\xc3\xa9ld\xc3\xa1k\xc3\xa9rt kattints ide', +'Administrative interface': 'az adminisztr\xc3\xa1ci\xc3\xb3s fel\xc3\xbclet\xc3\xa9rt kattints ide', +'customize me!': 'v\xc3\xa1ltoztass meg!', +'data uploaded': 'adat felt\xc3\xb6ltve', +'database': 'adatb\xc3\xa1zis', +'database %s select': 'adatb\xc3\xa1zis %s kiv\xc3\xa1laszt\xc3\xa1s', +'db': 'db', +'design': 'design', +'done!': 'k\xc3\xa9sz!', +'edit profile': 'profil szerkeszt\xc3\xa9se', +'export as csv file': 'export\xc3\xa1l csv f\xc3\xa1jlba', +'insert new': '\xc3\xbaj beilleszt\xc3\xa9se', +'insert new %s': '\xc3\xbaj beilleszt\xc3\xa9se %s', +'invalid request': 'hib\xc3\xa1s k\xc3\xa9r\xc3\xa9s', +'login': 'bel\xc3\xa9p', +'logout': 'kil\xc3\xa9p', +'lost password': 'elveszett jelsz\xc3\xb3', +'new record inserted': '\xc3\xbaj bejegyz\xc3\xa9s felv\xc3\xa9ve', +'next 100 rows': 'k\xc3\xb6vetkez\xc5\x91 100 sor', +'or import from csv file': 'vagy bet\xc3\xb6lt\xc3\xa9s csv f\xc3\xa1jlb\xc3\xb3l', +'previous 100 rows': 'el\xc5\x91z\xc5\x91 100 sor', +'record': 'bejegyz\xc3\xa9s', +'record does not exist': 'bejegyz\xc3\xa9s nem l\xc3\xa9tezik', +'record id': 'bejegyz\xc3\xa9s id', +'register': 'regisztr\xc3\xa1ci\xc3\xb3', +'selected': 'kiv\xc3\xa1lasztott', +'state': '\xc3\xa1llapot', +'table': 't\xc3\xa1bla', +'unable to parse csv file': 'nem lehet a csv f\xc3\xa1jlt beolvasni', +} ADDED applications/mobileblur/languages/hu.py Index: applications/mobileblur/languages/hu.py ================================================================== --- applications/mobileblur/languages/hu.py +++ applications/mobileblur/languages/hu.py @@ -0,0 +1,93 @@ +# coding: utf8 +{ +'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN', +'%Y-%m-%d': '%Y.%m.%d.', +'%Y-%m-%d %H:%M:%S': '%Y.%m.%d. %H:%M:%S', +'%s rows deleted': '%s sorok t\xc3\xb6rl\xc5\x91dtek', +'%s rows updated': '%s sorok friss\xc3\xadt\xc5\x91dtek', +'Available databases and tables': 'El\xc3\xa9rhet\xc5\x91 adatb\xc3\xa1zisok \xc3\xa9s t\xc3\xa1bl\xc3\xa1k', +'Cannot be empty': 'Nem lehet \xc3\xbcres', +'Check to delete': 'T\xc3\xb6rl\xc3\xa9shez v\xc3\xa1laszd ki', +'Client IP': 'Client IP', +'Controller': 'Controller', +'Copyright': 'Copyright', +'Current request': 'Jelenlegi lek\xc3\xa9rdez\xc3\xa9s', +'Current response': 'Jelenlegi v\xc3\xa1lasz', +'Current session': 'Jelenlegi folyamat', +'DB Model': 'DB Model', +'Database': 'Adatb\xc3\xa1zis', +'Delete:': 'T\xc3\xb6r\xc3\xb6l:', +'Description': 'Description', +'E-mail': 'E-mail', +'Edit': 'Szerkeszt', +'Edit This App': 'Alkalmaz\xc3\xa1st szerkeszt', +'Edit current record': 'Aktu\xc3\xa1lis bejegyz\xc3\xa9s szerkeszt\xc3\xa9se', +'First name': 'First name', +'Group ID': 'Group ID', +'Hello World': 'Hello Vil\xc3\xa1g', +'Import/Export': 'Import/Export', +'Index': 'Index', +'Internal State': 'Internal State', +'Invalid Query': 'Hib\xc3\xa1s lek\xc3\xa9rdez\xc3\xa9s', +'Invalid email': 'Invalid email', +'Last name': 'Last name', +'Layout': 'Szerkezet', +'Main Menu': 'F\xc5\x91men\xc3\xbc', +'Menu Model': 'Men\xc3\xbc model', +'Name': 'Name', +'New Record': '\xc3\x9aj bejegyz\xc3\xa9s', +'No databases in this application': 'Nincs adatb\xc3\xa1zis ebben az alkalmaz\xc3\xa1sban', +'Origin': 'Origin', +'Password': 'Password', +'Powered by': 'Powered by', +'Query:': 'Lek\xc3\xa9rdez\xc3\xa9s:', +'Record ID': 'Record ID', +'Registration key': 'Registration key', +'Reset Password key': 'Reset Password key', +'Role': 'Role', +'Rows in table': 'Sorok a t\xc3\xa1bl\xc3\xa1ban', +'Rows selected': 'Kiv\xc3\xa1lasztott sorok', +'Stylesheet': 'Stylesheet', +'Sure you want to delete this object?': 'Biztos t\xc3\xb6rli ezt az objektumot?', +'Table name': 'Table name', +'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.', +'Timestamp': 'Timestamp', +'Update:': 'Friss\xc3\xadt:', +'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.', +'User ID': 'User ID', +'View': 'N\xc3\xa9zet', +'Welcome %s': 'Welcome %s', +'Welcome to web2py': 'Isten hozott a web2py-ban', +'appadmin is disabled because insecure channel': 'az appadmin a biztons\xc3\xa1gtalan csatorna miatt letiltva', +'cache': 'gyors\xc3\xadt\xc3\xb3t\xc3\xa1r', +'change password': 'jelsz\xc3\xb3 megv\xc3\xa1ltoztat\xc3\xa1sa', +'Online examples': 'online p\xc3\xa9ld\xc3\xa1k\xc3\xa9rt kattints ide', +'Administrative interface': 'az adminisztr\xc3\xa1ci\xc3\xb3s fel\xc3\xbclet\xc3\xa9rt kattints ide', +'customize me!': 'v\xc3\xa1ltoztass meg!', +'data uploaded': 'adat felt\xc3\xb6ltve', +'database': 'adatb\xc3\xa1zis', +'database %s select': 'adatb\xc3\xa1zis %s kiv\xc3\xa1laszt\xc3\xa1s', +'db': 'db', +'design': 'design', +'done!': 'k\xc3\xa9sz!', +'edit profile': 'profil szerkeszt\xc3\xa9se', +'export as csv file': 'export\xc3\xa1l csv f\xc3\xa1jlba', +'insert new': '\xc3\xbaj beilleszt\xc3\xa9se', +'insert new %s': '\xc3\xbaj beilleszt\xc3\xa9se %s', +'invalid request': 'hib\xc3\xa1s k\xc3\xa9r\xc3\xa9s', +'login': 'bel\xc3\xa9p', +'logout': 'kil\xc3\xa9p', +'lost password': 'elveszett jelsz\xc3\xb3', +'new record inserted': '\xc3\xbaj bejegyz\xc3\xa9s felv\xc3\xa9ve', +'next 100 rows': 'k\xc3\xb6vetkez\xc5\x91 100 sor', +'or import from csv file': 'vagy bet\xc3\xb6lt\xc3\xa9s csv f\xc3\xa1jlb\xc3\xb3l', +'previous 100 rows': 'el\xc5\x91z\xc5\x91 100 sor', +'record': 'bejegyz\xc3\xa9s', +'record does not exist': 'bejegyz\xc3\xa9s nem l\xc3\xa9tezik', +'record id': 'bejegyz\xc3\xa9s id', +'register': 'regisztr\xc3\xa1ci\xc3\xb3', +'selected': 'kiv\xc3\xa1lasztott', +'state': '\xc3\xa1llapot', +'table': 't\xc3\xa1bla', +'unable to parse csv file': 'nem lehet a csv f\xc3\xa1jlt beolvasni', +} ADDED applications/mobileblur/languages/it-it.py Index: applications/mobileblur/languages/it-it.py ================================================================== --- applications/mobileblur/languages/it-it.py +++ applications/mobileblur/languages/it-it.py @@ -0,0 +1,104 @@ +# coding: utf8 +{ +'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"update" è un\'espressione opzionale come "campo1=\'nuovo valore\'". Non si può fare "update" o "delete" dei risultati di un JOIN ', +'%Y-%m-%d': '%d/%m/%Y', +'%Y-%m-%d %H:%M:%S': '%d/%m/%Y %H:%M:%S', +'%s rows deleted': '%s righe ("record") cancellate', +'%s rows updated': '%s righe ("record") modificate', +'Available databases and tables': 'Database e tabelle disponibili', +'Cannot be empty': 'Non può essere vuoto', +'Check to delete': 'Seleziona per cancellare', +'Client IP': 'Client IP', +'Controller': 'Controller', +'Copyright': 'Copyright', +'Current request': 'Richiesta (request) corrente', +'Current response': 'Risposta (response) corrente', +'Current session': 'Sessione (session) corrente', +'DB Model': 'Modello di DB', +'Database': 'Database', +'Delete:': 'Cancella:', +'Description': 'Descrizione', +'E-mail': 'E-mail', +'Edit': 'Modifica', +'Edit This App': 'Modifica questa applicazione', +'Edit current record': 'Modifica record corrente', +'First name': 'Nome', +'Group ID': 'ID Gruppo', +'Hello World': 'Salve Mondo', +'Hello World in a flash!': 'Salve Mondo in un flash!', +'Import/Export': 'Importa/Esporta', +'Index': 'Indice', +'Internal State': 'Stato interno', +'Invalid Query': 'Richiesta (query) non valida', +'Invalid email': 'Email non valida', +'Last name': 'Cognome', +'Layout': 'Layout', +'Main Menu': 'Menu principale', +'Menu Model': 'Menu Modelli', +'Name': 'Nome', +'New Record': 'Nuovo elemento (record)', +'No databases in this application': 'Nessun database presente in questa applicazione', +'Origin': 'Origine', +'Password': 'Password', +'Powered by': 'Powered by', +'Query:': 'Richiesta (query):', +'Record ID': 'Record ID', +'Registration key': 'Chiave di Registazione', +'Reset Password key': 'Resetta chiave Password ', +'Role': 'Ruolo', +'Rows in table': 'Righe nella tabella', +'Rows selected': 'Righe selezionate', +'Stylesheet': 'Foglio di stile (stylesheet)', +'Sure you want to delete this object?': 'Vuoi veramente cancellare questo oggetto?', +'Table name': 'Nome tabella', +'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'La richiesta (query) è una condizione come ad esempio "db.tabella1.campo1==\'valore\'". Una condizione come "db.tabella1.campo1==db.tabella2.campo2" produce un "JOIN" SQL.', +'The output of the file is a dictionary that was rendered by the view': 'L\'output del file è un "dictionary" che è stato visualizzato dalla vista', +'This is a copy of the scaffolding application': "Questa è una copia dell'applicazione di base (scaffold)", +'Timestamp': 'Ora (timestamp)', +'Update:': 'Aggiorna:', +'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Per costruire richieste (query) più complesse si usano (...)&(...) come "e" (AND), (...)|(...) come "o" (OR), e ~(...) come negazione (NOT).', +'User ID': 'ID Utente', +'View': 'Vista', +'Welcome %s': 'Benvenuto %s', +'Welcome to web2py': 'Benvenuto su web2py', +'Which called the function': 'che ha chiamato la funzione', +'You are successfully running web2py': 'Stai eseguendo web2py con successo', +'You can modify this application and adapt it to your needs': 'Puoi modificare questa applicazione adattandola alle tue necessità', +'You visited the url': "Hai visitato l'URL", +'appadmin is disabled because insecure channel': 'Amministrazione (appadmin) disabilitata: comunicazione non sicura', +'cache': 'cache', +'change password': 'Cambia password', +'Online examples': 'Vedere gli esempi', +'Administrative interface': "Interfaccia amministrativa", +'customize me!': 'Personalizzami!', +'data uploaded': 'dati caricati', +'database': 'database', +'database %s select': 'database %s select', +'db': 'db', +'design': 'progetta', +'Documentation': 'Documentazione', +'done!': 'fatto!', +'edit profile': 'modifica profilo', +'export as csv file': 'esporta come file CSV', +'hello world': 'salve mondo', +'insert new': 'inserisci nuovo', +'insert new %s': 'inserisci nuovo %s', +'invalid request': 'richiesta non valida', +'located in the file': 'presente nel file', +'login': 'accesso', +'logout': 'uscita', +'lost password?': 'dimenticato la password?', +'new record inserted': 'nuovo record inserito', +'next 100 rows': 'prossime 100 righe', +'not authorized': 'non autorizzato', +'or import from csv file': 'oppure importa da file CSV', +'previous 100 rows': '100 righe precedenti', +'record': 'record', +'record does not exist': 'il record non esiste', +'record id': 'record id', +'register': 'registrazione', +'selected': 'selezionato', +'state': 'stato', +'table': 'tabella', +'unable to parse csv file': 'non riesco a decodificare questo file CSV', +} ADDED applications/mobileblur/languages/it.py Index: applications/mobileblur/languages/it.py ================================================================== --- applications/mobileblur/languages/it.py +++ applications/mobileblur/languages/it.py @@ -0,0 +1,104 @@ +# coding: utf8 +{ +'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"update" è un\'espressione opzionale come "campo1=\'nuovo valore\'". Non si può fare "update" o "delete" dei risultati di un JOIN ', +'%Y-%m-%d': '%d/%m/%Y', +'%Y-%m-%d %H:%M:%S': '%d/%m/%Y %H:%M:%S', +'%s rows deleted': '%s righe ("record") cancellate', +'%s rows updated': '%s righe ("record") modificate', +'Available databases and tables': 'Database e tabelle disponibili', +'Cannot be empty': 'Non può essere vuoto', +'Check to delete': 'Seleziona per cancellare', +'Client IP': 'Client IP', +'Controller': 'Controller', +'Copyright': 'Copyright', +'Current request': 'Richiesta (request) corrente', +'Current response': 'Risposta (response) corrente', +'Current session': 'Sessione (session) corrente', +'DB Model': 'Modello di DB', +'Database': 'Database', +'Delete:': 'Cancella:', +'Description': 'Descrizione', +'E-mail': 'E-mail', +'Edit': 'Modifica', +'Edit This App': 'Modifica questa applicazione', +'Edit current record': 'Modifica record corrente', +'First name': 'Nome', +'Group ID': 'ID Gruppo', +'Hello World': 'Salve Mondo', +'Hello World in a flash!': 'Salve Mondo in un flash!', +'Import/Export': 'Importa/Esporta', +'Index': 'Indice', +'Internal State': 'Stato interno', +'Invalid Query': 'Richiesta (query) non valida', +'Invalid email': 'Email non valida', +'Last name': 'Cognome', +'Layout': 'Layout', +'Main Menu': 'Menu principale', +'Menu Model': 'Menu Modelli', +'Name': 'Nome', +'New Record': 'Nuovo elemento (record)', +'No databases in this application': 'Nessun database presente in questa applicazione', +'Origin': 'Origine', +'Password': 'Password', +'Powered by': 'Powered by', +'Query:': 'Richiesta (query):', +'Record ID': 'Record ID', +'Registration key': 'Chiave di Registazione', +'Reset Password key': 'Resetta chiave Password ', +'Role': 'Ruolo', +'Rows in table': 'Righe nella tabella', +'Rows selected': 'Righe selezionate', +'Stylesheet': 'Foglio di stile (stylesheet)', +'Sure you want to delete this object?': 'Vuoi veramente cancellare questo oggetto?', +'Table name': 'Nome tabella', +'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'La richiesta (query) è una condizione come ad esempio "db.tabella1.campo1==\'valore\'". Una condizione come "db.tabella1.campo1==db.tabella2.campo2" produce un "JOIN" SQL.', +'The output of the file is a dictionary that was rendered by the view': 'L\'output del file è un "dictionary" che è stato visualizzato dalla vista', +'This is a copy of the scaffolding application': "Questa è una copia dell'applicazione di base (scaffold)", +'Timestamp': 'Ora (timestamp)', +'Update:': 'Aggiorna:', +'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Per costruire richieste (query) più complesse si usano (...)&(...) come "e" (AND), (...)|(...) come "o" (OR), e ~(...) come negazione (NOT).', +'User ID': 'ID Utente', +'View': 'Vista', +'Welcome %s': 'Benvenuto %s', +'Welcome to web2py': 'Benvenuto su web2py', +'Which called the function': 'che ha chiamato la funzione', +'You are successfully running web2py': 'Stai eseguendo web2py con successo', +'You can modify this application and adapt it to your needs': 'Puoi modificare questa applicazione adattandola alle tue necessità', +'You visited the url': "Hai visitato l'URL", +'appadmin is disabled because insecure channel': 'Amministrazione (appadmin) disabilitata: comunicazione non sicura', +'cache': 'cache', +'change password': 'Cambia password', +'Online examples': 'Vedere gli esempi', +'Administrative interface': "Interfaccia amministrativa", +'customize me!': 'Personalizzami!', +'data uploaded': 'dati caricati', +'database': 'database', +'database %s select': 'database %s select', +'db': 'db', +'design': 'progetta', +'Documentation': 'Documentazione', +'done!': 'fatto!', +'edit profile': 'modifica profilo', +'export as csv file': 'esporta come file CSV', +'hello world': 'salve mondo', +'insert new': 'inserisci nuovo', +'insert new %s': 'inserisci nuovo %s', +'invalid request': 'richiesta non valida', +'located in the file': 'presente nel file', +'login': 'accesso', +'logout': 'uscita', +'lost password?': 'dimenticato la password?', +'new record inserted': 'nuovo record inserito', +'next 100 rows': 'prossime 100 righe', +'not authorized': 'non autorizzato', +'or import from csv file': 'oppure importa da file CSV', +'previous 100 rows': '100 righe precedenti', +'record': 'record', +'record does not exist': 'il record non esiste', +'record id': 'record id', +'register': 'registrazione', +'selected': 'selezionato', +'state': 'stato', +'table': 'tabella', +'unable to parse csv file': 'non riesco a decodificare questo file CSV', +} ADDED applications/mobileblur/languages/pl-pl.py Index: applications/mobileblur/languages/pl-pl.py ================================================================== --- applications/mobileblur/languages/pl-pl.py +++ applications/mobileblur/languages/pl-pl.py @@ -0,0 +1,81 @@ +# coding: utf8 +{ +'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"Uaktualnij" jest dodatkowym wyra\xc5\xbceniem postaci "pole1=\'nowawarto\xc5\x9b\xc4\x87\'". Nie mo\xc5\xbcesz uaktualni\xc4\x87 lub usun\xc4\x85\xc4\x87 wynik\xc3\xb3w z JOIN:', +'%Y-%m-%d': '%Y-%m-%d', +'%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S', +'%s rows deleted': 'Wierszy usuni\xc4\x99tych: %s', +'%s rows updated': 'Wierszy uaktualnionych: %s', +'Available databases and tables': 'Dost\xc4\x99pne bazy danych i tabele', +'Cannot be empty': 'Nie mo\xc5\xbce by\xc4\x87 puste', +'Change Password': 'Change Password', +'Check to delete': 'Zaznacz aby usun\xc4\x85\xc4\x87', +'Controller': 'Controller', +'Copyright': 'Copyright', +'Current request': 'Aktualne \xc5\xbc\xc4\x85danie', +'Current response': 'Aktualna odpowied\xc5\xba', +'Current session': 'Aktualna sesja', +'DB Model': 'DB Model', +'Database': 'Database', +'Delete:': 'Usu\xc5\x84:', +'Edit': 'Edit', +'Edit Profile': 'Edit Profile', +'Edit This App': 'Edit This App', +'Edit current record': 'Edytuj aktualny rekord', +'Hello World': 'Witaj \xc5\x9awiecie', +'Import/Export': 'Importuj/eksportuj', +'Index': 'Index', +'Internal State': 'Stan wewn\xc4\x99trzny', +'Invalid Query': 'B\xc5\x82\xc4\x99dne zapytanie', +'Layout': 'Layout', +'Login': 'Zaloguj', +'Logout': 'Logout', +'Lost Password': 'Przypomnij has\xc5\x82o', +'Main Menu': 'Main Menu', +'Menu Model': 'Menu Model', +'New Record': 'Nowy rekord', +'No databases in this application': 'Brak baz danych w tej aplikacji', +'Powered by': 'Powered by', +'Query:': 'Zapytanie:', +'Register': 'Zarejestruj', +'Rows in table': 'Wiersze w tabeli', +'Rows selected': 'Wybrane wiersze', +'Stylesheet': 'Stylesheet', +'Sure you want to delete this object?': 'Czy na pewno chcesz usun\xc4\x85\xc4\x87 ten obiekt?', +'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': '"Zapytanie" jest warunkiem postaci "db.tabela1.pole1==\'warto\xc5\x9b\xc4\x87\'". Takie co\xc5\x9b jak "db.tabela1.pole1==db.tabela2.pole2" oznacza SQL JOIN.', +'Update:': 'Uaktualnij:', +'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'U\xc5\xbcyj (...)&(...) jako AND, (...)|(...) jako OR oraz ~(...) jako NOT do tworzenia bardziej skomplikowanych zapyta\xc5\x84.', +'View': 'View', +'Welcome %s': 'Welcome %s', +'Welcome to web2py': 'Witaj w web2py', +'appadmin is disabled because insecure channel': 'appadmin is disabled because insecure channel', +'cache': 'cache', +'change password': 'change password', +'Online examples': 'Kliknij aby przej\xc5\x9b\xc4\x87 do interaktywnych przyk\xc5\x82ad\xc3\xb3w', +'Administrative interface': 'Kliknij aby przej\xc5\x9b\xc4\x87 do panelu administracyjnego', +'customize me!': 'dostosuj mnie!', +'data uploaded': 'dane wys\xc5\x82ane', +'database': 'baza danych', +'database %s select': 'wyb\xc3\xb3r z bazy danych %s', +'db': 'baza danych', +'design': 'projektuj', +'done!': 'zrobione!', +'edit profile': 'edit profile', +'export as csv file': 'eksportuj jako plik csv', +'insert new': 'wstaw nowy rekord tabeli', +'insert new %s': 'wstaw nowy rekord do tabeli %s', +'invalid request': 'B\xc5\x82\xc4\x99dne \xc5\xbc\xc4\x85danie', +'login': 'login', +'logout': 'logout', +'new record inserted': 'nowy rekord zosta\xc5\x82 wstawiony', +'next 100 rows': 'nast\xc4\x99pne 100 wierszy', +'or import from csv file': 'lub zaimportuj z pliku csv', +'previous 100 rows': 'poprzednie 100 wierszy', +'record': 'record', +'record does not exist': 'rekord nie istnieje', +'record id': 'id rekordu', +'register': 'register', +'selected': 'wybranych', +'state': 'stan', +'table': 'tabela', +'unable to parse csv file': 'nie mo\xc5\xbcna sparsowa\xc4\x87 pliku csv', +} ADDED applications/mobileblur/languages/pl.py Index: applications/mobileblur/languages/pl.py ================================================================== --- applications/mobileblur/languages/pl.py +++ applications/mobileblur/languages/pl.py @@ -0,0 +1,104 @@ +# coding: utf8 +{ +'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"Uaktualnij" jest dodatkowym wyra\xc5\xbceniem postaci "pole1=\'nowawarto\xc5\x9b\xc4\x87\'". Nie mo\xc5\xbcesz uaktualni\xc4\x87 lub usun\xc4\x85\xc4\x87 wynik\xc3\xb3w z JOIN:', +'%Y-%m-%d': '%Y-%m-%d', +'%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S', +'%s rows deleted': 'Wierszy usuni\xc4\x99tych: %s', +'%s rows updated': 'Wierszy uaktualnionych: %s', +'Authentication': 'Uwierzytelnienie', +'Available databases and tables': 'Dost\xc4\x99pne bazy danych i tabele', +'Cannot be empty': 'Nie mo\xc5\xbce by\xc4\x87 puste', +'Change Password': 'Zmie\xc5\x84 has\xc5\x82o', +'Check to delete': 'Zaznacz aby usun\xc4\x85\xc4\x87', +'Check to delete:': 'Zaznacz aby usun\xc4\x85\xc4\x87:', +'Client IP': 'IP klienta', +'Controller': 'Kontroler', +'Copyright': 'Copyright', +'Current request': 'Aktualne \xc5\xbc\xc4\x85danie', +'Current response': 'Aktualna odpowied\xc5\xba', +'Current session': 'Aktualna sesja', +'DB Model': 'Model bazy danych', +'Database': 'Baza danych', +'Delete:': 'Usu\xc5\x84:', +'Description': 'Opis', +'E-mail': 'Adres e-mail', +'Edit': 'Edycja', +'Edit Profile': 'Edytuj profil', +'Edit This App': 'Edytuj t\xc4\x99 aplikacj\xc4\x99', +'Edit current record': 'Edytuj obecny rekord', +'First name': 'Imi\xc4\x99', +'Function disabled': 'Funkcja wy\xc5\x82\xc4\x85czona', +'Group ID': 'ID grupy', +'Hello World': 'Witaj \xc5\x9awiecie', +'Import/Export': 'Importuj/eksportuj', +'Index': 'Indeks', +'Internal State': 'Stan wewn\xc4\x99trzny', +'Invalid Query': 'B\xc5\x82\xc4\x99dne zapytanie', +'Invalid email': 'B\xc5\x82\xc4\x99dny adres email', +'Last name': 'Nazwisko', +'Layout': 'Uk\xc5\x82ad', +'Login': 'Zaloguj', +'Logout': 'Wyloguj', +'Lost Password': 'Przypomnij has\xc5\x82o', +'Main Menu': 'Menu g\xc5\x82\xc3\xb3wne', +'Menu Model': 'Model menu', +'Name': 'Nazwa', +'New Record': 'Nowy rekord', +'No databases in this application': 'Brak baz danych w tej aplikacji', +'Origin': '\xc5\xb9r\xc3\xb3d\xc5\x82o', +'Password': 'Has\xc5\x82o', +"Password fields don't match": 'Pola has\xc5\x82a nie s\xc4\x85 zgodne ze sob\xc4\x85', +'Powered by': 'Zasilane przez', +'Query:': 'Zapytanie:', +'Record ID': 'ID rekordu', +'Register': 'Zarejestruj', +'Registration key': 'Klucz rejestracji', +'Role': 'Rola', +'Rows in table': 'Wiersze w tabeli', +'Rows selected': 'Wybrane wiersze', +'Stylesheet': 'Arkusz styl\xc3\xb3w', +'Submit': 'Wy\xc5\x9blij', +'Sure you want to delete this object?': 'Czy na pewno chcesz usun\xc4\x85\xc4\x87 ten obiekt?', +'Table name': 'Nazwa tabeli', +'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': '"Zapytanie" jest warunkiem postaci "db.tabela1.pole1==\'warto\xc5\x9b\xc4\x87\'". Takie co\xc5\x9b jak "db.tabela1.pole1==db.tabela2.pole2" oznacza SQL JOIN.', +'Timestamp': 'Znacznik czasu', +'Update:': 'Uaktualnij:', +'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'U\xc5\xbcyj (...)&(...) jako AND, (...)|(...) jako OR oraz ~(...) jako NOT do tworzenia bardziej skomplikowanych zapyta\xc5\x84.', +'User %(id)s Registered': 'U\xc5\xbcytkownik %(id)s zosta\xc5\x82 zarejestrowany', +'User ID': 'ID u\xc5\xbcytkownika', +'Verify Password': 'Potwierd\xc5\xba has\xc5\x82o', +'View': 'Widok', +'Welcome %s': 'Welcome %s', +'Welcome to web2py': 'Witaj w web2py', +'appadmin is disabled because insecure channel': 'administracja aplikacji wy\xc5\x82\xc4\x85czona z powodu braku bezpiecznego po\xc5\x82\xc4\x85czenia', +'cache': 'cache', +'change password': 'change password', +'Online examples': 'Kliknij aby przej\xc5\x9b\xc4\x87 do interaktywnych przyk\xc5\x82ad\xc3\xb3w', +'Administrative interface': 'Kliknij aby przej\xc5\x9b\xc4\x87 do panelu administracyjnego', +'customize me!': 'dostosuj mnie!', +'data uploaded': 'dane wys\xc5\x82ane', +'database': 'baza danych', +'database %s select': 'wyb\xc3\xb3r z bazy danych %s', +'db': 'baza danych', +'design': 'projektuj', +'done!': 'zrobione!', +'edit profile': 'edit profile', +'export as csv file': 'eksportuj jako plik csv', +'insert new': 'wstaw nowy rekord tabeli', +'insert new %s': 'wstaw nowy rekord do tabeli %s', +'invalid request': 'B\xc5\x82\xc4\x99dne \xc5\xbc\xc4\x85danie', +'login': 'login', +'logout': 'logout', +'new record inserted': 'nowy rekord zosta\xc5\x82 wstawiony', +'next 100 rows': 'nast\xc4\x99pne 100 wierszy', +'or import from csv file': 'lub zaimportuj z pliku csv', +'previous 100 rows': 'poprzednie 100 wierszy', +'record': 'rekord', +'record does not exist': 'rekord nie istnieje', +'record id': 'id rekordu', +'register': 'register', +'selected': 'wybranych', +'state': 'stan', +'table': 'tabela', +'unable to parse csv file': 'nie mo\xc5\xbcna sparsowa\xc4\x87 pliku csv', +} ADDED applications/mobileblur/languages/pt-br.py Index: applications/mobileblur/languages/pt-br.py ================================================================== --- applications/mobileblur/languages/pt-br.py +++ applications/mobileblur/languages/pt-br.py @@ -0,0 +1,142 @@ +# coding: utf8 +{ +'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"update" é uma expressão opcional como "campo1=\'novovalor\'". Você não pode atualizar ou apagar os resultados de um JOIN', +'%Y-%m-%d': '%d-%m-%Y', +'%Y-%m-%d %H:%M:%S': '%d-%m-%Y %H:%M:%S', +'%s rows deleted': '%s linhas apagadas', +'%s rows updated': '%s linhas atualizadas', +'About': 'About', +'Access Control': 'Access Control', +'Ajax Recipes': 'Ajax Recipes', +'Available databases and tables': 'Bancos de dados e tabelas disponíveis', +'Buy this book': 'Buy this book', +'Cannot be empty': 'Não pode ser vazio', +'Check to delete': 'Marque para apagar', +'Client IP': 'Client IP', +'Community': 'Community', +'Controller': 'Controlador', +'Copyright': 'Copyright', +'Current request': 'Requisição atual', +'Current response': 'Resposta atual', +'Current session': 'Sessão atual', +'DB Model': 'Modelo BD', +'Database': 'Banco de dados', +'Delete:': 'Apagar:', +'Demo': 'Demo', +'Deployment Recipes': 'Deployment Recipes', +'Description': 'Description', +'Documentation': 'Documentation', +'Download': 'Download', +'E-mail': 'E-mail', +'Edit': 'Editar', +'Edit This App': 'Edit This App', +'Edit current record': 'Editar o registro atual', +'Errors': 'Errors', +'FAQ': 'FAQ', +'First name': 'First name', +'Forms and Validators': 'Forms and Validators', +'Free Applications': 'Free Applications', +'Group ID': 'Group ID', +'Groups': 'Groups', +'Hello World': 'Olá Mundo', +'Home': 'Home', +'Import/Export': 'Importar/Exportar', +'Index': 'Início', +'Internal State': 'Estado Interno', +'Introduction': 'Introduction', +'Invalid Query': 'Consulta Inválida', +'Invalid email': 'Invalid email', +'Last name': 'Last name', +'Layout': 'Layout', +'Layouts': 'Layouts', +'Live chat': 'Live chat', +'Login': 'Autentique-se', +'Lost Password': 'Esqueceu sua senha?', +'Main Menu': 'Menu Principal', +'Menu Model': 'Modelo de Menu', +'Name': 'Name', +'New Record': 'Novo Registro', +'No databases in this application': 'Sem bancos de dados nesta aplicação', +'Origin': 'Origin', +'Other Recipes': 'Other Recipes', +'Overview': 'Overview', +'Password': 'Password', +'Plugins': 'Plugins', +'Powered by': 'Powered by', +'Preface': 'Preface', +'Python': 'Python', +'Query:': 'Consulta:', +'Quick Examples': 'Quick Examples', +'Recipes': 'Recipes', +'Record ID': 'Record ID', +'Register': 'Registre-se', +'Registration key': 'Registration key', +'Reset Password key': 'Reset Password key', +'Resources': 'Resources', +'Role': 'Role', +'Rows in table': 'Linhas na tabela', +'Rows selected': 'Linhas selecionadas', +'Semantic': 'Semantic', +'Services': 'Services', +'Stylesheet': 'Stylesheet', +'Support': 'Support', +'Sure you want to delete this object?': 'Está certo(a) que deseja apagar esse objeto ?', +'Table name': 'Table name', +'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'Uma "consulta" é uma condição como "db.tabela1.campo1==\'valor\'". Expressões como "db.tabela1.campo1==db.tabela2.campo2" resultam em um JOIN SQL.', +'The Core': 'The Core', +'The Views': 'The Views', +'The output of the file is a dictionary that was rendered by the view': 'The output of the file is a dictionary that was rendered by the view', +'This App': 'This App', +'This is a copy of the scaffolding application': 'This is a copy of the scaffolding application', +'Timestamp': 'Timestamp', +'Twitter': 'Twitter', +'Update:': 'Atualizar:', +'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Use (...)&(...) para AND, (...)|(...) para OR, e ~(...) para NOT para construir consultas mais complexas.', +'User ID': 'User ID', +'User Voice': 'User Voice', +'Videos': 'Videos', +'View': 'Visualização', +'Web2py': 'Web2py', +'Welcome': 'Welcome', +'Welcome %s': 'Vem vindo %s', +'Welcome to web2py': 'Bem vindo ao web2py', +'Which called the function': 'Which called the function', +'You are successfully running web2py': 'You are successfully running web2py', +'You are successfully running web2py.': 'You are successfully running web2py.', +'You can modify this application and adapt it to your needs': 'You can modify this application and adapt it to your needs', +'You visited the url': 'You visited the url', +'appadmin is disabled because insecure channel': 'Administração desativada devido ao canal inseguro', +'cache': 'cache', +'change password': 'modificar senha', +'Online examples': 'Alguns exemplos', +'Administrative interface': 'Interface administrativa', +'customize me!': 'Personalize-me!', +'data uploaded': 'dados enviados', +'database': 'banco de dados', +'database %s select': 'Selecionar banco de dados %s', +'db': 'bd', +'design': 'design', +'Documentation': 'Documentation', +'done!': 'concluído!', +'edit profile': 'editar perfil', +'export as csv file': 'exportar como um arquivo csv', +'insert new': 'inserir novo', +'insert new %s': 'inserir novo %s', +'invalid request': 'requisição inválida', +'located in the file': 'located in the file', +'login': 'Entrar', +'logout': 'Sair', +'lost password?': 'lost password?', +'new record inserted': 'novo registro inserido', +'next 100 rows': 'próximas 100 linhas', +'or import from csv file': 'ou importar de um arquivo csv', +'previous 100 rows': '100 linhas anteriores', +'record': 'registro', +'record does not exist': 'registro não existe', +'record id': 'id do registro', +'register': 'Registre-se', +'selected': 'selecionado', +'state': 'estado', +'table': 'tabela', +'unable to parse csv file': 'não foi possível analisar arquivo csv', +} ADDED applications/mobileblur/languages/pt-pt.py Index: applications/mobileblur/languages/pt-pt.py ================================================================== --- applications/mobileblur/languages/pt-pt.py +++ applications/mobileblur/languages/pt-pt.py @@ -0,0 +1,116 @@ +# coding: utf8 +{ +'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"update" é uma expressão opcional como "field1=\'newvalue\'". Não pode actualizar ou eliminar os resultados de um JOIN', +'%Y-%m-%d': '%Y-%m-%d', +'%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S', +'%s rows deleted': '%s linhas eliminadas', +'%s rows updated': '%s linhas actualizadas', +'About': 'About', +'Author Reference Auth User': 'Author Reference Auth User', +'Author Reference Auth User.username': 'Author Reference Auth User.username', +'Available databases and tables': 'bases de dados e tabelas disponíveis', +'Cannot be empty': 'não pode ser vazio', +'Category Create': 'Category Create', +'Category Select': 'Category Select', +'Check to delete': 'seleccione para eliminar', +'Comment Create': 'Comment Create', +'Comment Select': 'Comment Select', +'Content': 'Content', +'Controller': 'Controlador', +'Copyright': 'Direitos de cópia', +'Created By': 'Created By', +'Created On': 'Created On', +'Current request': 'pedido currente', +'Current response': 'resposta currente', +'Current session': 'sessão currente', +'DB Model': 'Modelo de BD', +'Database': 'Base de dados', +'Delete:': 'Eliminar:', +'Edit': 'Editar', +'Edit This App': 'Edite esta aplicação', +'Edit current record': 'Edição de registo currente', +'Email': 'Email', +'First Name': 'First Name', +'For %s #%s': 'For %s #%s', +'Hello World': 'Olá Mundo', +'Import/Export': 'Importar/Exportar', +'Index': 'Índice', +'Internal State': 'Estado interno', +'Invalid Query': 'Consulta Inválida', +'Last Name': 'Last Name', +'Layout': 'Esboço', +'Main Menu': 'Menu Principal', +'Menu Model': 'Menu do Modelo', +'Modified By': 'Modified By', +'Modified On': 'Modified On', +'Name': 'Name', +'New Record': 'Novo Registo', +'No Data': 'No Data', +'No databases in this application': 'Não há bases de dados nesta aplicação', +'Password': 'Password', +'Post Create': 'Post Create', +'Post Select': 'Post Select', +'Powered by': 'Suportado por', +'Query:': 'Interrogação:', +'Replyto Reference Post': 'Replyto Reference Post', +'Rows in table': 'Linhas numa tabela', +'Rows selected': 'Linhas seleccionadas', +'Stylesheet': 'Folha de estilo', +'Sure you want to delete this object?': 'Tem a certeza que deseja eliminar este objecto?', +'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'A "query" é uma condição do tipo "db.table1.field1==\'value\'". Algo como "db.table1.field1==db.table2.field2" resultaria num SQL JOIN.', +'Title': 'Title', +'Update:': 'Actualização:', +'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Utilize (...)&(...) para AND, (...)|(...) para OR, e ~(...) para NOT para construir interrogações mais complexas.', +'Username': 'Username', +'View': 'Vista', +'Welcome %s': 'Bem-vindo(a) %s', +'Welcome to Gluonization': 'Bem vindo ao Web2py', +'Welcome to web2py': 'Bem-vindo(a) ao web2py', +'When': 'When', +'appadmin is disabled because insecure channel': 'appadmin está desactivada pois o canal é inseguro', +'cache': 'cache', +'change password': 'alterar palavra-chave', +'Online examples': 'Exemplos online', +'Administrative interface': 'Painel administrativo', +'create new category': 'create new category', +'create new comment': 'create new comment', +'create new post': 'create new post', +'customize me!': 'Personaliza-me!', +'data uploaded': 'informação enviada', +'database': 'base de dados', +'database %s select': 'selecção de base de dados %s', +'db': 'bd', +'design': 'design', +'done!': 'concluído!', +'edit category': 'edit category', +'edit comment': 'edit comment', +'edit post': 'edit post', +'edit profile': 'Editar perfil', +'export as csv file': 'exportar como ficheiro csv', +'insert new': 'inserir novo', +'insert new %s': 'inserir novo %s', +'invalid request': 'Pedido Inválido', +'login': 'login', +'logout': 'logout', +'new record inserted': 'novo registo inserido', +'next 100 rows': 'próximas 100 linhas', +'or import from csv file': 'ou importe a partir de ficheiro csv', +'previous 100 rows': '100 linhas anteriores', +'record': 'registo', +'record does not exist': 'registo inexistente', +'record id': 'id de registo', +'register': 'register', +'search category': 'search category', +'search comment': 'search comment', +'search post': 'search post', +'select category': 'select category', +'select comment': 'select comment', +'select post': 'select post', +'selected': 'seleccionado(s)', +'show category': 'show category', +'show comment': 'show comment', +'show post': 'show post', +'state': 'estado', +'table': 'tabela', +'unable to parse csv file': 'não foi possível carregar ficheiro csv', +} ADDED applications/mobileblur/languages/pt.py Index: applications/mobileblur/languages/pt.py ================================================================== --- applications/mobileblur/languages/pt.py +++ applications/mobileblur/languages/pt.py @@ -0,0 +1,116 @@ +# coding: utf8 +{ +'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"update" é uma expressão opcional como "field1=\'newvalue\'". Não pode actualizar ou eliminar os resultados de um JOIN', +'%Y-%m-%d': '%Y-%m-%d', +'%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S', +'%s rows deleted': '%s linhas eliminadas', +'%s rows updated': '%s linhas actualizadas', +'About': 'About', +'Author Reference Auth User': 'Author Reference Auth User', +'Author Reference Auth User.username': 'Author Reference Auth User.username', +'Available databases and tables': 'bases de dados e tabelas disponíveis', +'Cannot be empty': 'não pode ser vazio', +'Category Create': 'Category Create', +'Category Select': 'Category Select', +'Check to delete': 'seleccione para eliminar', +'Comment Create': 'Comment Create', +'Comment Select': 'Comment Select', +'Content': 'Content', +'Controller': 'Controlador', +'Copyright': 'Direitos de cópia', +'Created By': 'Created By', +'Created On': 'Created On', +'Current request': 'pedido currente', +'Current response': 'resposta currente', +'Current session': 'sessão currente', +'DB Model': 'Modelo de BD', +'Database': 'Base de dados', +'Delete:': 'Eliminar:', +'Edit': 'Editar', +'Edit This App': 'Edite esta aplicação', +'Edit current record': 'Edição de registo currente', +'Email': 'Email', +'First Name': 'First Name', +'For %s #%s': 'For %s #%s', +'Hello World': 'Olá Mundo', +'Import/Export': 'Importar/Exportar', +'Index': 'Índice', +'Internal State': 'Estado interno', +'Invalid Query': 'Consulta Inválida', +'Last Name': 'Last Name', +'Layout': 'Esboço', +'Main Menu': 'Menu Principal', +'Menu Model': 'Menu do Modelo', +'Modified By': 'Modified By', +'Modified On': 'Modified On', +'Name': 'Name', +'New Record': 'Novo Registo', +'No Data': 'No Data', +'No databases in this application': 'Não há bases de dados nesta aplicação', +'Password': 'Password', +'Post Create': 'Post Create', +'Post Select': 'Post Select', +'Powered by': 'Suportado por', +'Query:': 'Interrogação:', +'Replyto Reference Post': 'Replyto Reference Post', +'Rows in table': 'Linhas numa tabela', +'Rows selected': 'Linhas seleccionadas', +'Stylesheet': 'Folha de estilo', +'Sure you want to delete this object?': 'Tem a certeza que deseja eliminar este objecto?', +'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'A "query" é uma condição do tipo "db.table1.field1==\'value\'". Algo como "db.table1.field1==db.table2.field2" resultaria num SQL JOIN.', +'Title': 'Title', +'Update:': 'Actualização:', +'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Utilize (...)&(...) para AND, (...)|(...) para OR, e ~(...) para NOT para construir interrogações mais complexas.', +'Username': 'Username', +'View': 'Vista', +'Welcome %s': 'Bem-vindo(a) %s', +'Welcome to Gluonization': 'Bem vindo ao Web2py', +'Welcome to web2py': 'Bem-vindo(a) ao web2py', +'When': 'When', +'appadmin is disabled because insecure channel': 'appadmin está desactivada pois o canal é inseguro', +'cache': 'cache', +'change password': 'alterar palavra-chave', +'Online examples': 'Exemplos online', +'Administrative interface': 'Painel administrativo', +'create new category': 'create new category', +'create new comment': 'create new comment', +'create new post': 'create new post', +'customize me!': 'Personaliza-me!', +'data uploaded': 'informação enviada', +'database': 'base de dados', +'database %s select': 'selecção de base de dados %s', +'db': 'bd', +'design': 'design', +'done!': 'concluído!', +'edit category': 'edit category', +'edit comment': 'edit comment', +'edit post': 'edit post', +'edit profile': 'Editar perfil', +'export as csv file': 'exportar como ficheiro csv', +'insert new': 'inserir novo', +'insert new %s': 'inserir novo %s', +'invalid request': 'Pedido Inválido', +'login': 'login', +'logout': 'logout', +'new record inserted': 'novo registo inserido', +'next 100 rows': 'próximas 100 linhas', +'or import from csv file': 'ou importe a partir de ficheiro csv', +'previous 100 rows': '100 linhas anteriores', +'record': 'registo', +'record does not exist': 'registo inexistente', +'record id': 'id de registo', +'register': 'register', +'search category': 'search category', +'search comment': 'search comment', +'search post': 'search post', +'select category': 'select category', +'select comment': 'select comment', +'select post': 'select post', +'selected': 'seleccionado(s)', +'show category': 'show category', +'show comment': 'show comment', +'show post': 'show post', +'state': 'estado', +'table': 'tabela', +'unable to parse csv file': 'não foi possível carregar ficheiro csv', +} ADDED applications/mobileblur/languages/ru-ru.py Index: applications/mobileblur/languages/ru-ru.py ================================================================== --- applications/mobileblur/languages/ru-ru.py +++ applications/mobileblur/languages/ru-ru.py @@ -0,0 +1,96 @@ +# coding: utf8 +{ +'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"Изменить" - необязательное выражение вида "field1=\'новое значение\'". Результаты операции JOIN нельзя изменить или удалить.', +'%Y-%m-%d': '%Y-%m-%d', +'%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S', +'%s rows deleted': '%s строк удалено', +'%s rows updated': '%s строк изменено', +'Available databases and tables': 'Базы данных и таблицы', +'Cannot be empty': 'Пустое значение недопустимо', +'Change Password': 'Смените пароль', +'Check to delete': 'Удалить', +'Check to delete:': 'Удалить:', +'Client IP': 'Client IP', +'Current request': 'Текущий запрос', +'Current response': 'Текущий ответ', +'Current session': 'Текущая сессия', +'Delete:': 'Удалить:', +'Description': 'Описание', +'E-mail': 'E-mail', +'Edit Profile': 'Редактировать профиль', +'Edit current record': 'Редактировать текущую запись', +'First name': 'Имя', +'Group ID': 'Group ID', +'Hello World': 'Заработало!', +'Import/Export': 'Импорт/экспорт', +'Internal State': 'Внутренне состояние', +'Invalid Query': 'Неверный запрос', +'Invalid email': 'Неверный email', +'Invalid login': 'Неверный логин', +'Invalid password': 'Неверный пароль', +'Last name': 'Фамилия', +'Logged in': 'Вход выполнен', +'Logged out': 'Выход выполнен', +'Login': 'Вход', +'Logout': 'Выход', +'Lost Password': 'Забыли пароль?', +'Name': 'Name', +'New Record': 'Новая запись', +'New password': 'Новый пароль', +'No databases in this application': 'В приложении нет баз данных', +'Old password': 'Старый пароль', +'Origin': 'Происхождение', +'Password': 'Пароль', +"Password fields don't match": 'Пароли не совпадают', +'Query:': 'Запрос:', +'Record ID': 'ID записи', +'Register': 'Зарегистрироваться', +'Registration key': 'Ключ регистрации', +'Remember me (for 30 days)': 'Запомнить меня (на 30 дней)', +'Reset Password key': 'Сбросить ключ пароля', +'Role': 'Роль', +'Rows in table': 'Строк в таблице', +'Rows selected': 'Выделено строк', +'Submit': 'Отправить', +'Sure you want to delete this object?': 'Подтвердите удаление объекта', +'Table name': 'Имя таблицы', +'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': '"Запрос" - это условие вида "db.table1.field1==\'значение\'". Выражение вида "db.table1.field1==db.table2.field2" формирует SQL JOIN.', +'Timestamp': 'Отметка времени', +'Update:': 'Изменить:', +'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Для построение сложных запросов используйте операторы "И": (...)&(...), "ИЛИ": (...)|(...), "НЕ": ~(...).', +'User %(id)s Logged-in': 'Пользователь %(id)s вошёл', +'User %(id)s Logged-out': 'Пользователь %(id)s вышел', +'User %(id)s Password changed': 'Пользователь %(id)s сменил пароль', +'User %(id)s Profile updated': 'Пользователь %(id)s обновил профиль', +'User %(id)s Registered': 'Пользователь %(id)s зарегистрировался', +'User ID': 'ID пользователя', +'Verify Password': 'Повторите пароль', +'Welcome to web2py': 'Добро пожаловать в web2py', +'Online examples': 'примеры он-лайн', +'Administrative interface': 'административный интерфейс', +'customize me!': 'настройте внешний вид!', +'data uploaded': 'данные загружены', +'database': 'база данных', +'database %s select': 'выбор базы данных %s', +'db': 'БД', +'design': 'дизайн', +'done!': 'готово!', +'export as csv file': 'экспорт в csv-файл', +'insert new': 'добавить', +'insert new %s': 'добавить %s', +'invalid request': 'неверный запрос', +'login': 'вход', +'logout': 'выход', +'new record inserted': 'новая запись добавлена', +'next 100 rows': 'следующие 100 строк', +'or import from csv file': 'или импорт из csv-файла', +'password': 'пароль', +'previous 100 rows': 'предыдущие 100 строк', +'profile': 'профиль', +'record does not exist': 'запись не найдена', +'record id': 'id записи', +'selected': 'выбрано', +'state': 'состояние', +'table': 'таблица', +'unable to parse csv file': 'нечитаемый csv-файл', +} ADDED applications/mobileblur/languages/sk-sk.py Index: applications/mobileblur/languages/sk-sk.py ================================================================== --- applications/mobileblur/languages/sk-sk.py +++ applications/mobileblur/languages/sk-sk.py @@ -0,0 +1,111 @@ +# coding: utf8 +{ +'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"update" je voliteľný výraz ako "field1=\'newvalue\'". Nemôžete upravovať alebo zmazať výsledky JOINu', +'%Y-%m-%d': '%d.%m.%Y', +'%Y-%m-%d %H:%M:%S': '%d.%m.%Y %H:%M:%S', +'%s rows deleted': '%s zmazaných záznamov', +'%s rows updated': '%s upravených záznamov', +'Available databases and tables': 'Dostupné databázy a tabuľky', +'Cannot be empty': 'Nemôže byť prázdne', +'Check to delete': 'Označiť na zmazanie', +'Controller': 'Controller', +'Copyright': 'Copyright', +'Current request': 'Aktuálna požiadavka', +'Current response': 'Aktuálna odpoveď', +'Current session': 'Aktuálne sedenie', +'DB Model': 'DB Model', +'Database': 'Databáza', +'Delete:': 'Zmazať:', +'Description': 'Popis', +'Edit': 'Upraviť', +'Edit Profile': 'Upraviť profil', +'Edit current record': 'Upraviť aktuálny záznam', +'First name': 'Krstné meno', +'Group ID': 'ID skupiny', +'Hello World': 'Ahoj svet', +'Import/Export': 'Import/Export', +'Index': 'Index', +'Internal State': 'Vnútorný stav', +'Invalid email': 'Neplatný email', +'Invalid Query': 'Neplatná otázka', +'Invalid password': 'Nesprávne heslo', +'Last name': 'Priezvisko', +'Layout': 'Layout', +'Logged in': 'Prihlásený', +'Logged out': 'Odhlásený', +'Lost Password': 'Stratené heslo?', +'Menu Model': 'Menu Model', +'Name': 'Meno', +'New Record': 'Nový záznam', +'New password': 'Nové heslo', +'No databases in this application': 'V tejto aplikácii nie sú databázy', +'Old password': 'Staré heslo', +'Origin': 'Pôvod', +'Password': 'Heslo', +'Powered by': 'Powered by', +'Query:': 'Otázka:', +'Record ID': 'ID záznamu', +'Register': 'Zaregistrovať sa', +'Registration key': 'Registračný kľúč', +'Remember me (for 30 days)': 'Zapamätaj si ma (na 30 dní)', +'Reset Password key': 'Nastaviť registračný kľúč', +'Role': 'Rola', +'Rows in table': 'riadkov v tabuľke', +'Rows selected': 'označených riadkov', +'Submit': 'Odoslať', +'Stylesheet': 'Stylesheet', +'Sure you want to delete this object?': 'Ste si istí, že chcete zmazať tento objekt?', +'Table name': 'Názov tabuľky', +'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': '"query" je podmienka ako "db.table1.field1==\'value\'". Niečo ako "db.table1.field1==db.table2.field2" má za výsledok SQL JOIN.', +'The output of the file is a dictionary that was rendered by the view': 'Výstup zo súboru je slovník, ktorý bol zobrazený vo view', +'This is a copy of the scaffolding application': 'Toto je kópia skeletu aplikácie', +'Timestamp': 'Časová pečiatka', +'Update:': 'Upraviť:', +'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Použite (...)&(...) pre AND, (...)|(...) pre OR a ~(...) pre NOT na poskladanie komplexnejších otázok.', +'User %(id)s Logged-in': 'Používateľ %(id)s prihlásený', +'User %(id)s Logged-out': 'Používateľ %(id)s odhlásený', +'User %(id)s Password changed': 'Používateľ %(id)s zmenil heslo', +'User %(id)s Profile updated': 'Používateľ %(id)s upravil profil', +'User %(id)s Registered': 'Používateľ %(id)s sa zaregistroval', +'User ID': 'ID používateľa', +'Verify Password': 'Zopakujte heslo', +'View': 'Zobraziť', +'Welcome to web2py': 'Vitajte vo web2py', +'Which called the function': 'Ktorý zavolal funkciu', +'You are successfully running web2py': 'Úspešne ste spustili web2py', +'You can modify this application and adapt it to your needs': 'Môžete upraviť túto aplikáciu a prispôsobiť ju svojim potrebám', +'You visited the url': 'Navštívili ste URL', +'appadmin is disabled because insecure channel': 'appadmin je zakázaný bez zabezpečeného spojenia', +'cache': 'cache', +'Online examples': 'pre online príklady kliknite sem', +'Administrative interface': 'pre administrátorské rozhranie kliknite sem', +'customize me!': 'prispôsob ma!', +'data uploaded': 'údaje naplnené', +'database': 'databáza', +'database %s select': 'databáza %s výber', +'db': 'db', +'design': 'návrh', +'Documentation': 'Dokumentácia', +'done!': 'hotovo!', +'export as csv file': 'exportovať do csv súboru', +'insert new': 'vložiť nový záznam ', +'insert new %s': 'vložiť nový záznam %s', +'invalid request': 'Neplatná požiadavka', +'located in the file': 'nachádzajúci sa v súbore ', +'login': 'prihlásiť', +'logout': 'odhlásiť', +'lost password?': 'stratené heslo?', +'new record inserted': 'nový záznam bol vložený', +'next 100 rows': 'ďalších 100 riadkov', +'or import from csv file': 'alebo naimportovať z csv súboru', +'password': 'heslo', +'previous 100 rows': 'predchádzajúcich 100 riadkov', +'record': 'záznam', +'record does not exist': 'záznam neexistuje', +'record id': 'id záznamu', +'register': 'registrovať', +'selected': 'označených', +'state': 'stav', +'table': 'tabuľka', +'unable to parse csv file': 'nedá sa načítať csv súbor', +} ADDED applications/mobileblur/languages/zh-tw.py Index: applications/mobileblur/languages/zh-tw.py ================================================================== --- applications/mobileblur/languages/zh-tw.py +++ applications/mobileblur/languages/zh-tw.py @@ -0,0 +1,165 @@ +# coding: utf8 +{ +'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"更新" 是選擇性的條件式, 格式就像 "欄位1=\'值\'". 但是 JOIN 的資料不可以使用 update 或是 delete"', +'%Y-%m-%d': '%Y-%m-%d', +'%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S', +'%s rows deleted': '已刪除 %s 筆', +'%s rows updated': '已更新 %s 筆', +'(something like "it-it")': '(格式類似 "zh-tw")', +'A new version of web2py is available': '新版的 web2py 已發行', +'A new version of web2py is available: %s': '新版的 web2py 已發行: %s', +'ATTENTION: Login requires a secure (HTTPS) connection or running on localhost.': '注意: 登入管理帳號需要安全連線(HTTPS)或是在本機連線(localhost).', +'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': '注意: 因為在測試模式不保證多執行緒安全性,也就是說不可以同時執行多個測試案例', +'ATTENTION: you cannot edit the running application!': '注意:不可編輯正在執行的應用程式!', +'About': '關於', +'About application': '關於本應用程式', +'Admin is disabled because insecure channel': '管理功能(Admin)在不安全連線環境下自動關閉', +'Admin is disabled because unsecure channel': '管理功能(Admin)在不安全連線環境下自動關閉', +'Administrator Password:': '管理員密碼:', +'Are you sure you want to delete file "%s"?': '確定要刪除檔案"%s"?', +'Are you sure you want to uninstall application "%s"': '確定要移除應用程式 "%s"', +'Are you sure you want to uninstall application "%s"?': '確定要移除應用程式 "%s"', +'Authentication': '驗證', +'Available databases and tables': '可提供的資料庫和資料表', +'Cannot be empty': '不可空白', +'Cannot compile: there are errors in your app. Debug it, correct errors and try again.': '無法編譯:應用程式中含有錯誤,請除錯後再試一次.', +'Change Password': '變更密碼', +'Check to delete': '打勾代表刪除', +'Check to delete:': '點選以示刪除:', +'Client IP': '客戶端網址(IP)', +'Controller': '控件', +'Controllers': '控件', +'Copyright': '版權所有', +'Create new application': '創建應用程式', +'Current request': '目前網路資料要求(request)', +'Current response': '目前網路資料回應(response)', +'Current session': '目前網路連線資訊(session)', +'DB Model': '資料庫模組', +'DESIGN': '設計', +'Database': '資料庫', +'Date and Time': '日期和時間', +'Delete': '刪除', +'Delete:': '刪除:', +'Deploy on Google App Engine': '配置到 Google App Engine', +'Description': '描述', +'Design for': '設計為了', +'E-mail': '電子郵件', +'EDIT': '編輯', +'Edit': '編輯', +'Edit Profile': '編輯設定檔', +'Edit This App': '編輯本應用程式', +'Edit application': '編輯應用程式', +'Edit current record': '編輯當前紀錄', +'Editing file': '編輯檔案', +'Editing file "%s"': '編輯檔案"%s"', +'Error logs for "%(app)s"': '"%(app)s"的錯誤紀錄', +'First name': '名', +'Functions with no doctests will result in [passed] tests.': '沒有 doctests 的函式會顯示 [passed].', +'Group ID': '群組編號', +'Hello World': '嗨! 世界', +'Import/Export': '匯入/匯出', +'Index': '索引', +'Installed applications': '已安裝應用程式', +'Internal State': '內部狀態', +'Invalid Query': '不合法的查詢', +'Invalid action': '不合法的動作(action)', +'Invalid email': '不合法的電子郵件', +'Language files (static strings) updated': '語言檔已更新', +'Languages': '各國語言', +'Last name': '姓', +'Last saved on:': '最後儲存時間:', +'Layout': '網頁配置', +'License for': '軟體版權為', +'Login': '登入', +'Login to the Administrative Interface': '登入到管理員介面', +'Logout': '登出', +'Lost Password': '密碼遺忘', +'Main Menu': '主選單', +'Menu Model': '選單模組(menu)', +'Models': '資料模組', +'Modules': '程式模組', +'NO': '否', +'Name': '名字', +'New Record': '新紀錄', +'No databases in this application': '這應用程式不含資料庫', +'Origin': '原文', +'Original/Translation': '原文/翻譯', +'Password': '密碼', +"Password fields don't match": '密碼欄不匹配', +'Peeking at file': '選擇檔案', +'Powered by': '基於以下技術構建:', +'Query:': '查詢:', +'Record ID': '紀錄編號', +'Register': '註冊', +'Registration key': '註冊金鑰', +'Remember me (for 30 days)': '記住我(30 天)', +'Reset Password key': '重設密碼', +'Resolve Conflict file': '解決衝突檔案', +'Role': '角色', +'Rows in table': '在資料表裏的資料', +'Rows selected': '筆資料被選擇', +'Saved file hash:': '檔案雜湊值已紀錄:', +'Static files': '靜態檔案', +'Stylesheet': '網頁風格檔', +'Submit': '傳送', +'Sure you want to delete this object?': '確定要刪除此物件?', +'Table name': '資料表名稱', +'Testing application': '測試中的應用程式', +'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': '"查詢"是一個像 "db.表1.欄位1==\'值\'" 的條件式. 以"db.表1.欄位1==db.表2.欄位2"方式則相當於執行 JOIN SQL.', +'There are no controllers': '沒有控件(controllers)', +'There are no models': '沒有資料庫模組(models)', +'There are no modules': '沒有程式模組(modules)', +'There are no static files': '沒有靜態檔案', +'There are no translators, only default language is supported': '沒有翻譯檔,只支援原始語言', +'There are no views': '沒有視圖', +'This is the %(filename)s template': '這是%(filename)s檔案的樣板(template)', +'Ticket': '問題單', +'Timestamp': '時間標記', +'Unable to check for upgrades': '無法做升級檢查', +'Unable to download': '無法下載', +'Unable to download app': '無法下載應用程式', +'Update:': '更新:', +'Upload existing application': '更新存在的應用程式', +'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': '使用下列方式來組合更複雜的條件式, (...)&(...) 代表同時存在的條件, (...)|(...) 代表擇一的條件, ~(...)則代表反向條件.', +'User %(id)s Logged-in': '使用者 %(id)s 已登入', +'User %(id)s Registered': '使用者 %(id)s 已註冊', +'User ID': '使用者編號', +'Verify Password': '驗證密碼', +'View': '視圖', +'Views': '視圖', +'Welcome %s': '歡迎 %s', +'Welcome to web2py': '歡迎使用 web2py', +'YES': '是', +'about': '關於', +'appadmin is disabled because insecure channel': '因為來自非安全通道,管理介面關閉', +'cache': '快取記憶體', +'change password': '變更密碼', +'Online examples': '點此處進入線上範例', +'Administrative interface': '點此處進入管理介面', +'customize me!': '請調整我!', +'data uploaded': '資料已上傳', +'database': '資料庫', +'database %s select': '已選擇 %s 資料庫', +'db': 'db', +'design': '設計', +'done!': '完成!', +'edit profile': '編輯設定檔', +'export as csv file': '以逗號分隔檔(csv)格式匯出', +'insert new': '插入新資料', +'insert new %s': '插入新資料 %s', +'invalid request': '不合法的網路要求(request)', +'login': '登入', +'logout': '登出', +'new record inserted': '已插入新紀錄', +'next 100 rows': '往後 100 筆', +'or import from csv file': '或是從逗號分隔檔(CSV)匯入', +'previous 100 rows': '往前 100 筆', +'record': '紀錄', +'record does not exist': '紀錄不存在', +'record id': '紀錄編號', +'register': '註冊', +'selected': '已選擇', +'state': '狀態', +'table': '資料表', +'unable to parse csv file': '無法解析逗號分隔檔(csv)', +} ADDED applications/mobileblur/models/0_helpers.py Index: applications/mobileblur/models/0_helpers.py ================================================================== --- applications/mobileblur/models/0_helpers.py +++ applications/mobileblur/models/0_helpers.py @@ -0,0 +1,9 @@ +newsblur = local_import("newsblur") + +threshold = 0 +thresholds = ["nt", "ps", "ng"] # indices -1, 0, 1 for negative, neutral, and positive intelligence filters + +def login(username="spiffytech"): + user = db(db.users.username==username).select().first() + if user["cookie"] is None: + results = newsblur.login(user["username"], user["password"]) ADDED applications/mobileblur/models/db.py Index: applications/mobileblur/models/db.py ================================================================== --- applications/mobileblur/models/db.py +++ applications/mobileblur/models/db.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- +# this file is released under public domain and you can use without limitations +from gluon.custom_import import track_changes; track_changes(True) + +######################################################################### +## This scaffolding model makes your app work on Google App Engine too +######################################################################### + +if request.env.web2py_runtime_gae: # if running on Google App Engine + db = DAL('google:datastore') # connect to Google BigTable + # optional DAL('gae://namespace') + session.connect(request, response, db = db) # and store sessions and tickets there + ### or use the following lines to store sessions in Memcache + # from gluon.contrib.memdb import MEMDB + # from google.appengine.api.memcache import Client + # session.connect(request, response, db = MEMDB(Client())) +else: # else use a normal relational database + db = DAL('sqlite://storage.sqlite') # if not, use SQLite or other DB + +# by default give a view/generic.extension to all actions from localhost +# none otherwise. a pattern can be 'controller/function.extension' +response.generic_patterns = ['*'] if request.is_local else [] + +######################################################################### +## Here is sample code if you need for +## - email capabilities +## - authentication (registration, login, logout, ... ) +## - authorization (role based authorization) +## - services (xml, csv, json, xmlrpc, jsonrpc, amf, rss) +## - crud actions +## (more options discussed in gluon/tools.py) +######################################################################### + +from gluon.tools import Mail, Auth, Crud, Service, PluginManager, prettydate +mail = Mail() # mailer +auth = Auth(db) # authentication/authorization +crud = Crud(db) # for CRUD helpers using auth +service = Service() # for json, xml, jsonrpc, xmlrpc, amfrpc +plugins = PluginManager() # for configuring plugins + +mail.settings.server = 'logging' or 'smtp.gmail.com:587' # your SMTP server +mail.settings.sender = 'you@gmail.com' # your email +mail.settings.login = 'username:password' # your credentials or None + +auth.settings.hmac_key = '' # before define_tables() +auth.define_tables(username=True) # creates all needed tables +auth.settings.mailer = mail # for user email verification +auth.settings.registration_requires_verification = False +auth.settings.registration_requires_approval = False +auth.messages.verify_email = 'Click on the link http://'+request.env.http_host+URL('default','user',args=['verify_email'])+'/%(key)s to verify your email' +auth.settings.reset_password_requires_verification = True +auth.messages.reset_password = 'Click on the link http://'+request.env.http_host+URL('default','user',args=['reset_password'])+'/%(key)s to reset your password' + +######################################################################### +## If you need to use OpenID, Facebook, MySpace, Twitter, Linkedin, etc. +## register with janrain.com, uncomment and customize following +# from gluon.contrib.login_methods.rpx_account import RPXAccount +# auth.settings.actions_disabled = \ +# ['register','change_password','request_reset_password'] +# auth.settings.login_form = RPXAccount(request, api_key='...',domain='...', +# url = "http://localhost:8000/%s/default/user/login" % request.application) +## other login methods are in gluon/contrib/login_methods +######################################################################### + +crud.settings.auth = None # =auth to enforce authorization on crud + +######################################################################### +## Define your tables below (or better in another model file) for example +## +## >>> db.define_table('mytable',Field('myfield','string')) +## +## Fields can be 'string','text','password','integer','double','boolean' +## 'date','time','datetime','blob','upload', 'reference TABLENAME' +## There is an implicit 'id integer autoincrement' field +## Consult manual for more options, validators, etc. +## +## More API examples for controllers: +## +## >>> db.mytable.insert(myfield='value') +## >>> rows=db(db.mytable.myfield=='value').select(db.mytable.ALL) +## >>> for row in rows: print row.id, row.myfield +######################################################################### + +db.define_table("users", + Field("username"), + Field("password"), + Field("cookie") +) +login() ADDED applications/mobileblur/models/menu.py Index: applications/mobileblur/models/menu.py ================================================================== --- applications/mobileblur/models/menu.py +++ applications/mobileblur/models/menu.py @@ -0,0 +1,126 @@ +# -*- coding: utf-8 -*- +# this file is released under public domain and you can use without limitations +######################################################################### +## Customize your APP title, subtitle and menus here +######################################################################### + +response.title = request.application +response.subtitle = T('customize me!') + +#http://dev.w3.org/html5/markup/meta.name.html +response.meta.author = 'you' +response.meta.description = 'Free and open source full-stack enterprise framework for agile development of fast, scalable, secure and portable database-driven web-based applications. Written and programmable in Python' +response.meta.keywords = 'web2py, python, framework' +response.meta.generator = 'Web2py Enterprise Framework' +response.meta.copyright = 'Copyright 2007-2010' + + +########################################## +## this is the main application menu +## add/remove items as required +########################################## + +response.menu = [ + (T('Home'), False, URL('default','index'), []) + ] + +########################################## +## this is here to provide shortcuts +## during development. remove in production +## +## mind that plugins may also affect menu +########################################## + +######################################### +## Make your own menus +########################################## + +response.menu+=[ + (T('This App'), False, URL('admin', 'default', 'design/%s' % request.application), + [ + (T('Controller'), False, + URL('admin', 'default', 'edit/%s/controllers/%s.py' \ + % (request.application,request.controller=='appadmin' and + 'default' or request.controller))), + (T('View'), False, + URL('admin', 'default', 'edit/%s/views/%s' \ + % (request.application,response.view))), + (T('Layout'), False, + URL('admin', 'default', 'edit/%s/views/layout.html' \ + % request.application)), + (T('Stylesheet'), False, + URL('admin', 'default', 'edit/%s/static/base.css' \ + % request.application)), + (T('DB Model'), False, + URL('admin', 'default', 'edit/%s/models/db.py' \ + % request.application)), + (T('Menu Model'), False, + URL('admin', 'default', 'edit/%s/models/menu.py' \ + % request.application)), + (T('Database'), False, + URL(request.application, 'appadmin', 'index')), + + (T('Errors'), False, + URL('admin', 'default', 'errors/%s' \ + % request.application)), + + (T('About'), False, + URL('admin', 'default', 'about/%s' \ + % request.application)), + + ] + )] + + +########################################## +## this is here to provide shortcuts to some resources +## during development. remove in production +## +## mind that plugins may also affect menu +########################################## + + +response.menu+=[(T('Resources'), False, None, + [ + (T('Documentation'), False, 'http://www.web2py.com/book', + [ + (T('Preface'), False, 'http://www.web2py.com/book/default/chapter/00'), + (T('Introduction'), False, 'http://www.web2py.com/book/default/chapter/01'), + (T('Python'), False, 'http://www.web2py.com/book/default/chapter/02'), + (T('Overview'), False, 'http://www.web2py.com/book/default/chapter/03'), + (T('The Core'), False, 'http://www.web2py.com/book/default/chapter/04'), + (T('The Views'), False, 'http://www.web2py.com/book/default/chapter/05'), + (T('Database'), False, 'http://www.web2py.com/book/default/chapter/06'), + (T('Forms and Validators'), False, 'http://www.web2py.com/book/default/chapter/07'), + (T('Access Control'), False, 'http://www.web2py.com/book/default/chapter/08'), + (T('Services'), False, 'http://www.web2py.com/book/default/chapter/09'), + (T('Ajax Recipes'), False, 'http://www.web2py.com/book/default/chapter/10'), + (T('Deployment Recipes'), False, 'http://www.web2py.com/book/default/chapter/11'), + (T('Other Recipes'), False, 'http://www.web2py.com/book/default/chapter/12'), + (T('Buy this book'), False, 'http://stores.lulu.com/web2py'), + ]), + + (T('Community'), False, None, + [ + (T('Groups'), False, 'http://www.web2py.com/examples/default/usergroups'), + (T('Twitter'), False, 'http://twitter.com/web2py'), + (T('Live chat'), False, 'http://mibbit.com/?channel=%23web2py&server=irc.mibbit.net'), + (T('User Voice'), False, 'http://web2py.uservoice.com/'), + ]), + + (T('Web2py'), False, 'http://www.web2py.com', + [ + (T('Download'), False, 'http://www.web2py.com/examples/default/download'), + (T('Support'), False, 'http://www.web2py.com/examples/default/support'), + (T('Quick Examples'), False, 'http://web2py.com/examples/default/examples'), + (T('FAQ'), False, 'http://web2py.com/AlterEgo'), + (T('Free Applications'), False, 'http://web2py.com/appliances'), + (T('Plugins'), False, 'http://web2py.com/plugins'), + (T('Recipes'), False, 'http://web2pyslices.com/'), + (T('Demo'), False, 'http://web2py.com/demo_admin'), + (T('Semantic'), False, 'http://web2py.com/semantic'), + (T('Layouts'), False, 'http://web2py.com/layouts'), + (T('Videos'), False, 'http://www.web2py.com/examples/default/videos/'), + ]), + ] + )] ADDED applications/mobileblur/modules/__init__.py Index: applications/mobileblur/modules/__init__.py ================================================================== --- applications/mobileblur/modules/__init__.py +++ applications/mobileblur/modules/__init__.py ADDED applications/mobileblur/modules/newsblur.py Index: applications/mobileblur/modules/newsblur.py ================================================================== --- applications/mobileblur/modules/newsblur.py +++ applications/mobileblur/modules/newsblur.py @@ -0,0 +1,336 @@ +#!/usr/bin/python + +"""newsblur.py - An API wrapper library for newsblur.com""" + +import simplejson + +import requests + +__author__ = 'Dananjaya Ramanayake , spiffytech ' +__version__ = "0.1" + +nb_url = "http://www.newsblur.com/" +cookies = None + +def login(username,password): + ''' + Login as an existing user. + If a user has no password set, you cannot just send any old password. + Required parameters, username and password, must be of string type. + ''' + + url = nb_url + 'api/login' + results = requests.post(url, data={"username": username, "password": password}) + global cookies + cookies = results.cookies + return simplejson.loads(results.content) + +def logout(): + ''' + Logout the currently logged in user. + ''' + + url = nb_url + 'api/logout' + results = requests.get(url, cookies=cookies) + return simplejson.loads(results.content) + +def signup(username,password,email): + ''' + Create a new user. + All three required parameters must be of type string. + ''' + + url = nb_url + 'api/signup' + payload = {'signup_username':username,'signup_password':password,'signup_email':email} + results = requests.post(url, data=payload, cookies=cookies) + return simplejson.loads(results.content) + +def search_feed(address,offset=1): + ''' + + Retrieve information about a feed from its website or RSS address. + Parameter address must be of type string while parameter offset must be an integer. + Will return a feed. + + ''' + + url = nb_url + 'rss_feeds/search_feed' + payload = {'address':address,'offset':offset} + results = results.get(url, data=payload, cookies=cookies) + return simplejson.loads(results.content) + +def feeds(include_favicons=True,flat=False): + ''' + Retrieve a list of feeds to which a user is actively subscribed. + Includes the 3 unread counts (positive, neutral, negative), as well as optional favicons. + ''' + + url = nb_url + 'reader/feeds' + payload = {'include_favicons':include_favicons,'flat':flat} + results = requests.get(url, data=payload, cookies=cookies) + return simplejson.loads(results.content) + + +def favicons(feeds=[1,2,3]): + ''' + Retrieve a list of favicons for a list of feeds. + Used when combined with /reader/feeds and include_favicons=false, so the feeds request contains far less data. + Useful for mobile devices, but requires a second request. + ''' + + url = nb_url + 'reader/favicons' + payload = {'feeds':feeds} + results = requests.get(url, data=payload, cookies=cookies) + return simplejson.loads(results.content) + +def id(id_no): + ''' + Retrieve the original page from a single feed. + ''' + + url = nb_url + 'reader/page/' % id_no + payload = {} + results = requests.get(url, data=payload, cookies=cookies) + return simplejson.loads(results.content) + +def refresh_feeds(): + ''' + Up-to-the-second unread counts for each active feed. + Poll for these counts no more than once a minute. + ''' + + url = nb_url + 'reader/refresh_feeds' + results = requests.get(url, cookies=cookies) + return simplejson.loads(results.content) + +def feeds_trainer(feed_id): + ''' + Retrieves all popular and known intelligence classifiers. + Also includes user's own classifiers. + ''' + + url = nb_url + 'reader/feeds_trainer' + payload = {'feed_id':feed_id} + results = requests.get(url, data=payload, cookies=cookies) + return simplejson.loads(results.content) + +def statistics(id_no): + ''' + If you only want a user's classifiers, use /classifiers/:id. + Omit the feed_id to get all classifiers for all subscriptions. + ''' + + url = nb_url + 'rss_feeds/statistics/%d' % id_no + results = requests.get(url, cookies=cookies) + return simplejson.loads(results.content) + +def feed_autocomplete(term): + ''' + Get a list of feeds that contain a search phrase. + Searches by feed address, feed url, and feed title, in that order. + Will only show sites with 2+ subscribers. + ''' + + url = nb_url + 'rss_feeds/feed_autocomplete?%' + payload = {'term':term} + results = requests.get(url, data=payload, cookies=cookies) + return simplejson.loads(results.content) + +def feed(id): + ''' + Retrieve stories from a single feed. + ''' + + url = nb_url + 'reader/feed/%s' % id + results = requests.get(url, cookies=cookies) + return simplejson.loads(results.content) + +def starred_stories(page=1): + ''' + Retrieve a user's starred stories. + ''' + + url = nb_url + 'reader/starred_stories' + payload = {'page':page} + results = requests.get(url, data=payload, cookies=cookies) + return simplejson.loads(results.content) + +def river_stories(feeds,page=1,read_stories_count=0): + ''' + Retrieve stories from a collection of feeds. This is known as the River of News. + Stories are ordered in reverse chronological order. + ''' + + url = nb_url + 'reader/river_stories' + payload = {'feeds':feeds,'page':page,'read_stories_count':read_stories_count} + results = urllib2.urlopen(url, data=payload, cookies=cookies) + return simplejson.loads(results.content) + +def mark_story_as_read(story_id,feed_id): + ''' + Mark stories as read. + Multiple story ids can be sent at once. + Each story must be from the same feed. + ''' + + url = nb_url + 'reader/mark_story_as_read' + payload = {'story_id':story_id,'feed_id':feed_id} + results = requests.post(url, data=payload, cookies=cookies) + return simplejson.loads(results.content) + +def mark_story_as_starred(story_id,feed_id): + ''' + Mark a story as starred (saved). + ''' + + url = nb_url + 'reader/mark_story_as_starred' + payload = {'story_id':story_id,'feed_id':feed_id} + results = requests.post(url, data=payload, cookies=cookies) + return simplejson.loads(results.content) + +def mark_all_as_read(days=0): + ''' + Mark all stories in *all* feeds read. + ''' + + url = nb_url + 'reader/mark_all_as_read' + payload = {'days':days} + results = requests.post(url, data=payload, cookies=cookies) + return simplejson.loads(results.content) + +def add_url(url,folder='[Top Level]'): + ''' + Add a feed by its URL. + Can be either the RSS feed or the website itself. + ''' + + url = nb_url + 'reader/add_url' + payload = {'url':url,'folder':folder} + results = requests.post(url, data=payload, cookies=cookies) + return simplejson.loads(results.content) + + +def add_folder(folder,parent_folder='[Top Level]'): + ''' + Add a new folder. + ''' + + url = nb_url + 'reader/add_folder' + payload = {'folder':folder,'parent_folder':parent_folder} + results = requests.post(url, data=payload, cookies=cookies) + return simplejson.loads(results.content) + +def rename_feed(feed_title,feed_id): + ''' + Rename a feed title. Only the current user will see the new title. + ''' + + url = nb_url + 'reader/rename_feed' + payload = {'feed_title':feed_title,'feed_id':feed_id} + results = requests.post(url, data=payload, cookies=cookies) + return simplejson.loads(results.content) + +def delete_feed(feed_id,in_folder): + ''' + Unsubscribe from a feed. Removes it from the folder. + Set the in_folder parameter to remove a feed from the correct folder, in case the user is subscribed to the feed in multiple folders. + ''' + + url = nb_url + 'reader/delete_feed' + payload = {'feed_id':feed_id,'in_folder':in_folder} + results = requests.post(url, data=payload, cookies=cookies) + return simplejson.loads(results.content) + +def rename_folder(folder_to_rename,new_folder_name,in_folder): + ''' + Rename a folder. + ''' + + url = nb_url + 'reader/rename_folder' + payload = {'folder_to_rename':folder_to_rename,'new_folder_name':new_folder_name,'in_folder':in_folder} + results = requests.post(url, data=payload, cookies=cookies) + return simplejson.loads(results.content) + +def delete_folder(folder_to_delete,in_folder,feed_id): + ''' + Delete a folder and unsubscribe from all feeds inside. + ''' + + url = nb_url + 'reader/delete_folder' + payload = {'folder_to_delete':folder_to_delete,'in_folder':in_folder,'feed_id':feed_id} + results = requests.post(url, data=payload, cookies=cookies) + return simplejson.loads(results.content) + + +def mark_feed_as_read(feed_id): + ''' + Mark a list of feeds as read. + ''' + + url = nb_url + 'reader/mark_feed_as_read' + payload = {'feed_id':feed_id} + results = requests.post(url, data=payload, cookies=cookies) + return simplejson.loads(results.content) + + +def save_feed_order(folders): + ''' + Reorder feeds and move them around between folders. + The entire folder structure needs to be serialized. + ''' + + url = nb_url + 'reader/save_feed_order' + payload = {'folders':folders} + results = requests.post(url, data=payload, cookies=cookies) + return simplejson.loads(results.content) + + +def classifier(id_no): + ''' + Get the intelligence classifiers for a user's site. + Only includes the user's own classifiers. + Use /reader/feeds_trainer for popular classifiers. + ''' + + url = nb_url + 'classifier/%d' % id_no + results = requests.get(url) + return simplejson.loads(results.content) + + +def classifier_save(like_type,dislike_type,remove_like_type,remove_dislike_type): + ''' + Save intelligence classifiers (tags, titles, authors, and the feed) for a feed. + ''' + + url = nb_url + 'classifier/save' + payload = {'like_[TYPE]':like_type, + 'dislike_[TYPE]':dislike_type, + 'remove_like_[TYPE]':remove_like_type, + 'remove_dislike_[TYPE]':remove_dislike_type} + results = requests.post(url, data=payload, cookies=cookies) + return simplejson.loads(results.content) + + +def opml_export(): + ''' + Download a backup of feeds and folders as an OPML file. + Contains folders and feeds in XML; useful for importing in another RSS reader. + ''' + + url = nb_url + 'import/opml_export' + results = requests.get(url) + return simplejson.loads(results.content) + + + +def opml_upload(opml_file): + ''' + Upload an OPML file. + ''' + + url = nb_url + 'import/opml_upload' + f = open(opml_file) + payload = {'file':f} + f.close() + results = requests.post(url, data=payload, cookies=cookies) + return simplejson.loads(results.content) ADDED applications/mobileblur/modules/python-newsblur/LICENSE Index: applications/mobileblur/modules/python-newsblur/LICENSE ================================================================== --- applications/mobileblur/modules/python-newsblur/LICENSE +++ applications/mobileblur/modules/python-newsblur/LICENSE @@ -0,0 +1,10 @@ +The MIT License + +Copyright (c) 2010-2011 Dananjaya Ramanayake dananjaya86@gmail.com. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + ADDED applications/mobileblur/modules/python-newsblur/MANIFEST Index: applications/mobileblur/modules/python-newsblur/MANIFEST ================================================================== --- applications/mobileblur/modules/python-newsblur/MANIFEST +++ applications/mobileblur/modules/python-newsblur/MANIFEST @@ -0,0 +1,5 @@ +LICENSE +README.rst +documentation.rst +newsblur.py +setup.py ADDED applications/mobileblur/modules/python-newsblur/README.rst Index: applications/mobileblur/modules/python-newsblur/README.rst ================================================================== --- applications/mobileblur/modules/python-newsblur/README.rst +++ applications/mobileblur/modules/python-newsblur/README.rst @@ -0,0 +1,23 @@ +=============== +python-newsblur +=============== + +A Python wrapper library for accessing API of newsblur.com. + +Copyright Dananjaya Ramanayake , released under the MIT license. + +Source: https://github.com/dananjayavr/python-newsblur + +For more information please check the newsblur.com API documentation at, http://www.newsblur.com/api + + +Installation +============ +To install run + + ``python setup.py install`` + +Usage: +====== + +Please read the documentation.rst file. ADDED applications/mobileblur/modules/python-newsblur/documentation.rst Index: applications/mobileblur/modules/python-newsblur/documentation.rst ================================================================== --- applications/mobileblur/modules/python-newsblur/documentation.rst +++ applications/mobileblur/modules/python-newsblur/documentation.rst @@ -0,0 +1,78 @@ +Login: + + ``import newsblur`` + + ``newsblur.login('samuelclay','new$blur')`` + +Output: + + ``{"code": 1, "result": "ok"}`` + + + +Logout: + + ``newsblur.logout()`` + +Output: + + ``{"code": 1, "result": "ok"}`` + + + +Signup: + + ``newsblur.signup('samuelclay','new$blur','samuel@ofbrooklyn.com')`` + +Output (Successful): + + ``{"code": 1, "result": "ok"}`` + +Output (Unsuccessful): + + ``{"code": -1, "result": "ok"}`` + + + +Search Feed: + + ``newsblur.search_feed('techcrunch.com')`` + +Output: + + ``{"feed_address": "http://www.techcrunch.com/author/mg/", "updated": "5077 hours", "subs": 1, "feed_link": "[]", "favicon_fetching": true, "feed_title": "TechCrunch \xc2\xbb MG Siegler", "exception_type": "feed", "exception_code": 404, "result": "ok", "has_exception": true, "id": 237525, "favicon_color": null}`` + + + +Feeds: + + ``newsblur.feeds()`` + +Output: + + ``{"flat_folders": {"Blogs": [{"ps": 0, "feed_link": "http://kottke.org/", "feed_title": "kottke.org", "ng": 0, "nt": 0, "id": 39}, {"ps": 0, "feed_link": "http://blog.newsblur.com/", "feed_title": "The NewsBlur Blog", "ng": 0, "nt": 0, "id": 558041}, {"ps": 0, "feed_link": "http://www.waxy.org/links/", "feed_title": "Waxy.org Links", "ng": 0, "nt": 0, "id": 3581}, {"ps": 0, "feed_link": "http://xkcd.com/", "feed_title": "xkcd.com", "ng": 0, "nt": 1, "id": 169}], "Cooking": [{"ps": 0, "feed_link": "http://americandrink.net/", "feed_title": "American Drink", "ng": 0, "nt": 0, "id": 64313}, {"ps": 0, "feed_link": "http://saltandfat.com/", "feed_title": "Salt & Fat", "ng": 0, "nt": 0, "id": 48}, {"ps": 1, "feed_link": "http://savorysweetlife.com", "feed_title": "Savory Sweet Life", "ng": 0, "nt": 0, "id": 45}, {"ps": 0, "feed_link": "http://smittenkitchen.com", "feed_title": "smitten kitchen", "ng": 0, "nt": 0, "id": 47}], "Blogs - Photoblogs": [{"ps": 0, "feed_link": "http://iconicphotos.wordpress.com", "feed_title": "Iconic Photos", "ng": 0, "nt": 0, "id": 50}, {"ps": 0, "feed_link": "http://blog.pictorymag.com/", "feed_title": "Pictory Blog", "ng": 0, "nt": 0, "id": 551953}, {"ps": 0, "feed_link": "http://theimpossiblecool.tumblr.com/", "feed_title": "the impossible cool.", "ng": 0, "nt": 1, "id": 34}], "New York": [{"ps": 2, "feed_link": "http://gothamist.com/", "feed_title": "Gothamist", "ng": 4, "nt": 17, "id": 23}, {"ps": 0, "feed_link": "http://www.scoutingny.com", "feed_title": "Scouting NY", "ng": 0, "nt": 0, "id": 27}], "Tech": [{"ps": 0, "feed_link": "http://www.codinghorror.com/blog/", "feed_title": "Coding Horror", "ng": 0, "nt": 0, "id": 2}, {"ps": 2, "feed_link": "http://news.ycombinator.com/", "feed_title": "Hacker News", "ng": 0, "nt": 133, "id": 6}, {"ps": 0, "feed_link": "http://www.macrumors.com", "feed_title": "MacRumors: Mac News and Rumors - Front Page", "ng": 0, "nt": 1, "id": 11}, {"ps": 2, "feed_link": "http://techcrunch.com", "feed_title": "TechCrunch", "ng": 0, "nt": 7, "id": 12}], "Absolute Reads": [{"ps": 0, "feed_link": "http://daringfireball.net/", "feed_title": "Daring Fireball", "ng": 0, "nt": 0, "id": 3}, {"ps": 1, "feed_link": "http://www.avc.com/a_vc/", "feed_title": "Fred Wilson: A VC", "ng": 0, "nt": 0, "id": 159}, {"ps": 0, "feed_link": "http://blog.louisgray.com/", "feed_title": "louisgray.com", "ng": 0, "nt": 0, "id": 172}, {"ps": 0, "feed_link": "http://www.marco.org/", "feed_title": "Marco.org", "ng": 0, "nt": 0, "id": 76}, {"ps": 0, "feed_link": "http://www.randsinrepose.com/", "feed_title": "Rands In Repose", "ng": 0, "nt": 0, "id": 38}]}, "user": "conesus", "result": "ok"}`` + + + +Favicons: + + ``newsblur.favicons()`` + +Output: + + Will return a list of favicons + + + +Retrieve a Page from the Feed: + + ``newsblur.id(32)`` + +Output: + + Will return the HTML data of the story. + + + + + ADDED applications/mobileblur/modules/python-newsblur/newsblur.py Index: applications/mobileblur/modules/python-newsblur/newsblur.py ================================================================== --- applications/mobileblur/modules/python-newsblur/newsblur.py +++ applications/mobileblur/modules/python-newsblur/newsblur.py @@ -0,0 +1,336 @@ +#!/usr/bin/python + +"""newsblur.py - An API wrapper library for newsblur.com""" + +import simplejson + +import requests + +__author__ = 'Dananjaya Ramanayake , spiffytech ' +__version__ = "0.1" + +nb_url = "http://www.newsblur.com/" +cookies = None + +def login(username,password): + ''' + Login as an existing user. + If a user has no password set, you cannot just send any old password. + Required parameters, username and password, must be of string type. + ''' + + url = nb_url + 'api/login' + results = requests.post(url, data={"username": username, "password": password}) + global cookies + cookies = results.cookies + return simplejson.loads(results.content) + +def logout(): + ''' + Logout the currently logged in user. + ''' + + url = nb_url + 'api/logout' + results = requests.get(url, cookies=cookies) + return simplejson.loads(results.content) + +def signup(username,password,email): + ''' + Create a new user. + All three required parameters must be of type string. + ''' + + url = nb_url + 'api/signup' + payload = {'signup_username':username,'signup_password':password,'signup_email':email} + results = requests.post(url, data=payload, cookies=cookies) + return simplejson.loads(results.content) + +def search_feed(address,offset=1): + ''' + + Retrieve information about a feed from its website or RSS address. + Parameter address must be of type string while parameter offset must be an integer. + Will return a feed. + + ''' + + url = nb_url + 'rss_feeds/search_feed' + payload = {'address':address,'offset':offset} + results = results.get(url, data=payload, cookies=cookies) + return simplejson.loads(results.content) + +def feeds(include_favicons=True,flat=False): + ''' + Retrieve a list of feeds to which a user is actively subscribed. + Includes the 3 unread counts (positive, neutral, negative), as well as optional favicons. + ''' + + url = nb_url + 'reader/feeds' + payload = {'include_favicons':include_favicons,'flat':flat} + results = requests.get(url, data=payload, cookies=cookies) + return simplejson.loads(results.content) + + +def favicons(feeds=[1,2,3]): + ''' + Retrieve a list of favicons for a list of feeds. + Used when combined with /reader/feeds and include_favicons=false, so the feeds request contains far less data. + Useful for mobile devices, but requires a second request. + ''' + + url = nb_url + 'reader/favicons' + payload = {'feeds':feeds} + results = requests.get(url, data=payload, cookies=cookies) + return simplejson.loads(results.content) + +def id(id_no): + ''' + Retrieve the original page from a single feed. + ''' + + url = nb_url + 'reader/page/' % id_no + payload = {} + results = requests.get(url, data=payload, cookies=cookies) + return simplejson.loads(results.content) + +def refresh_feeds(): + ''' + Up-to-the-second unread counts for each active feed. + Poll for these counts no more than once a minute. + ''' + + url = nb_url + 'reader/refresh_feeds' + results = requests.get(url, cookies=cookies) + return simplejson.loads(results.content) + +def feeds_trainer(feed_id): + ''' + Retrieves all popular and known intelligence classifiers. + Also includes user's own classifiers. + ''' + + url = nb_url + 'reader/feeds_trainer' + payload = {'feed_id':feed_id} + results = requests.get(url, data=payload, cookies=cookies) + return simplejson.loads(results.content) + +def statistics(id_no): + ''' + If you only want a user's classifiers, use /classifiers/:id. + Omit the feed_id to get all classifiers for all subscriptions. + ''' + + url = nb_url + 'rss_feeds/statistics/%d' % id_no + results = requests.get(url, cookies=cookies) + return simplejson.loads(results.content) + +def feed_autocomplete(term): + ''' + Get a list of feeds that contain a search phrase. + Searches by feed address, feed url, and feed title, in that order. + Will only show sites with 2+ subscribers. + ''' + + url = nb_url + 'rss_feeds/feed_autocomplete?%' + payload = {'term':term} + results = requests.get(url, data=payload, cookies=cookies) + return simplejson.loads(results.content) + +def feed(id): + ''' + Retrieve stories from a single feed. + ''' + + url = nb_url + 'reader/feed/%s' % id + results = requests.get(url, cookies=cookies) + return simplejson.loads(results.content) + +def starred_stories(page=1): + ''' + Retrieve a user's starred stories. + ''' + + url = nb_url + 'reader/starred_stories' + payload = {'page':page} + results = requests.get(url, data=payload, cookies=cookies) + return simplejson.loads(results.content) + +def river_stories(feeds,page=1,read_stories_count=0): + ''' + Retrieve stories from a collection of feeds. This is known as the River of News. + Stories are ordered in reverse chronological order. + ''' + + url = nb_url + 'reader/river_stories' + payload = {'feeds':feeds,'page':page,'read_stories_count':read_stories_count} + results = urllib2.urlopen(url, data=payload, cookies=cookies) + return simplejson.loads(results.content) + +def mark_story_as_read(story_id,feed_id): + ''' + Mark stories as read. + Multiple story ids can be sent at once. + Each story must be from the same feed. + ''' + + url = nb_url + 'reader/mark_story_as_read' + payload = {'story_id':story_id,'feed_id':feed_id} + results = requests.post(url, data=payload, cookies=cookies) + return simplejson.loads(results.content) + +def mark_story_as_starred(story_id,feed_id): + ''' + Mark a story as starred (saved). + ''' + + url = nb_url + 'reader/mark_story_as_starred' + payload = {'story_id':story_id,'feed_id':feed_id} + results = requests.post(url, data=payload, cookies=cookies) + return simplejson.loads(results.content) + +def mark_all_as_read(days=0): + ''' + Mark all stories in *all* feeds read. + ''' + + url = nb_url + 'reader/mark_all_as_read' + payload = {'days':days} + results = requests.post(url, data=payload, cookies=cookies) + return simplejson.loads(results.content) + +def add_url(url,folder='[Top Level]'): + ''' + Add a feed by its URL. + Can be either the RSS feed or the website itself. + ''' + + url = nb_url + 'reader/add_url' + payload = {'url':url,'folder':folder} + results = requests.post(url, data=payload, cookies=cookies) + return simplejson.loads(results.content) + + +def add_folder(folder,parent_folder='[Top Level]'): + ''' + Add a new folder. + ''' + + url = nb_url + 'reader/add_folder' + payload = {'folder':folder,'parent_folder':parent_folder} + results = requests.post(url, data=payload, cookies=cookies) + return simplejson.loads(results.content) + +def rename_feed(feed_title,feed_id): + ''' + Rename a feed title. Only the current user will see the new title. + ''' + + url = nb_url + 'reader/rename_feed' + payload = {'feed_title':feed_title,'feed_id':feed_id} + results = requests.post(url, data=payload, cookies=cookies) + return simplejson.loads(results.content) + +def delete_feed(feed_id,in_folder): + ''' + Unsubscribe from a feed. Removes it from the folder. + Set the in_folder parameter to remove a feed from the correct folder, in case the user is subscribed to the feed in multiple folders. + ''' + + url = nb_url + 'reader/delete_feed' + payload = {'feed_id':feed_id,'in_folder':in_folder} + results = requests.post(url, data=payload, cookies=cookies) + return simplejson.loads(results.content) + +def rename_folder(folder_to_rename,new_folder_name,in_folder): + ''' + Rename a folder. + ''' + + url = nb_url + 'reader/rename_folder' + payload = {'folder_to_rename':folder_to_rename,'new_folder_name':new_folder_name,'in_folder':in_folder} + results = requests.post(url, data=payload, cookies=cookies) + return simplejson.loads(results.content) + +def delete_folder(folder_to_delete,in_folder,feed_id): + ''' + Delete a folder and unsubscribe from all feeds inside. + ''' + + url = nb_url + 'reader/delete_folder' + payload = {'folder_to_delete':folder_to_delete,'in_folder':in_folder,'feed_id':feed_id} + results = requests.post(url, data=payload, cookies=cookies) + return simplejson.loads(results.content) + + +def mark_feed_as_read(feed_id): + ''' + Mark a list of feeds as read. + ''' + + url = nb_url + 'reader/mark_feed_as_read' + payload = {'feed_id':feed_id} + results = requests.post(url, data=payload, cookies=cookies) + return simplejson.loads(results.content) + + +def save_feed_order(folders): + ''' + Reorder feeds and move them around between folders. + The entire folder structure needs to be serialized. + ''' + + url = nb_url + 'reader/save_feed_order' + payload = {'folders':folders} + results = requests.post(url, data=payload, cookies=cookies) + return simplejson.loads(results.content) + + +def classifier(id_no): + ''' + Get the intelligence classifiers for a user's site. + Only includes the user's own classifiers. + Use /reader/feeds_trainer for popular classifiers. + ''' + + url = nb_url + 'classifier/%d' % id_no + results = requests.get(url) + return simplejson.loads(results.content) + + +def classifier_save(like_type,dislike_type,remove_like_type,remove_dislike_type): + ''' + Save intelligence classifiers (tags, titles, authors, and the feed) for a feed. + ''' + + url = nb_url + 'classifier/save' + payload = {'like_[TYPE]':like_type, + 'dislike_[TYPE]':dislike_type, + 'remove_like_[TYPE]':remove_like_type, + 'remove_dislike_[TYPE]':remove_dislike_type} + results = requests.post(url, data=payload, cookies=cookies) + return simplejson.loads(results.content) + + +def opml_export(): + ''' + Download a backup of feeds and folders as an OPML file. + Contains folders and feeds in XML; useful for importing in another RSS reader. + ''' + + url = nb_url + 'import/opml_export' + results = requests.get(url) + return simplejson.loads(results.content) + + + +def opml_upload(opml_file): + ''' + Upload an OPML file. + ''' + + url = nb_url + 'import/opml_upload' + f = open(opml_file) + payload = {'file':f} + f.close() + results = requests.post(url, data=payload, cookies=cookies) + return simplejson.loads(results.content) ADDED applications/mobileblur/modules/python-newsblur/setup.py Index: applications/mobileblur/modules/python-newsblur/setup.py ================================================================== --- applications/mobileblur/modules/python-newsblur/setup.py +++ applications/mobileblur/modules/python-newsblur/setup.py @@ -0,0 +1,23 @@ +from setuptools import setup +from newsblur import __version__ + +long_description = open('README.rst').read() + +setup(name='newsblur', + version=__version__, + py_modules=['newsblur'], + description='API Wrapper library for newsblur.com', + author='Dananjaya Ramanayake', + author_email='dananjaya86@gmail.com', + license='MIT', + url='', + long_description=long_description, + platforms=['any'], + classifiers=['Development Status :: 1 - Beta', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: MIT License', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Topic :: Internet :: WWW/HTTP :: News/RSS', + ], + ) ADDED applications/mobileblur/static/css/base.css Index: applications/mobileblur/static/css/base.css ================================================================== --- applications/mobileblur/static/css/base.css +++ applications/mobileblur/static/css/base.css @@ -0,0 +1,556 @@ +@charset "UTF-8"; + +/* This file is contains the following sections: + +Update: Revision: 20101102 by Martin Mulone + +- The new revision contains: +- Html5, good practice and normalization support. +- Diferent hacks. +- The normalization and some tags come from + diferent sites so i keep the credits and comments. + but the base of support html5 come from: + http://html5boilerplate.com/ + +- ez.css (http://www.ez-css.org/layouts) +- reset common tags +- choose default fonts +- choose link style +- add bottom line to table rows +- labels bold and occasionally centered +- make all input fields the same size +- add proper separation between h1-h6 and text +- always indent the first line and add space below paragraphs +- bullets and numbers style and indent +- form and table padding +- code blocks +- left and right padding to quoted text +- page layout alignment, width and padding (change this for spaces) +- column widths (change this to use left_sidebar and right_sidebar) +- backrgound images and colors (change this for colors) +- web2py specific (.flash, .error) + +Notice: +- even if you use a different layout/css you may need classes .flash and .error +- this is all color neutral except for #349C01 (header, links, lines) +- there are two backrgound images: images/background.png and images/header.png + +License: This file is released under BSD and MIT + +*/ + +/* + credit is left where credit is due. + additionally, much inspiration was taken from these projects: + yui.yahooapis.com/2.8.1/build/base/base.css + camendesign.com/design/ + praegnanz.de/weblog/htmlcssjs-kickstart +*/ + +/* + html5doctor.com Reset Stylesheet (Eric Meyer's Reset Reloaded + HTML5 baseline) + v1.4 2009-07-27 | Authors: Eric Meyer & Richard Clark + html5doctor.com/html-5-reset-stylesheet/ +*/ + +html, body, div, span, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +abbr, address, cite, code, +del, dfn, em, img, ins, kbd, q, samp, +small, strong, sub, sup, var, +b, i, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, figcaption, figure, +footer, header, hgroup, menu, nav, section, summary, +time, mark, audio, video { + margin:0; + padding:0; + border:0; + outline:0; + font-size:100%; + vertical-align:baseline; + background:transparent; +} + +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section { + display:block; +} + +nav ul { list-style:none; } + +blockquote, q { quotes:none; } + +blockquote:before, blockquote:after, +q:before, q:after { content:''; content:none; } + +a { margin:0; padding:0; font-size:100%; vertical-align:baseline; background:transparent; } +a:hover { text-decoration: underline } + +ins { background-color:#ff9; color:#000; text-decoration:none; } + +mark { background-color:#ff9; color:#000; font-style:italic; font-weight:bold; } + +del { text-decoration: line-through; } + +abbr[title], dfn[title] { border-bottom:1px dotted #000; cursor:help; } + +/* tables still need cellspacing="0" in the markup */ +table { border-collapse:collapse; border-spacing:0; } + +hr { display:block; height:1px; border:0; border-top:1px solid #ccc; margin:1em 0; padding:0; } + +input, select { vertical-align:middle; } +/* END RESET CSS */ + + +/* +fonts.css from the YUI Library: developer.yahoo.com/yui/ + Please refer to developer.yahoo.com/yui/fonts/ for font sizing percentages + +There are three custom edits: + * remove arial, helvetica from explicit font stack + * make the line-height relative and unit-less + * remove the pre, code styles +*/ +body { font:13px/1.231 sans-serif; *font-size:small; } /* hack retained to preserve specificity */ + +/*table { font-size:inherit; font:100%; }*/ + +select, input, textarea, button { font:99% sans-serif; } + + +/* normalize monospace sizing + * en.wikipedia.org/wiki/MediaWiki_talk:Common.css/Archive_11#Teletype_style_fix_for_Chrome + */ +pre, code, kbd, samp { font-family: monospace, sans-serif; } + +/* + * minimal base styles + */ + +/* #444 looks better than black: twitter.com/H_FJ/statuses/11800719859 */ +body, select, input, textarea { color:#444; } + +/* Headers (h1,h2,etc) have no default font-size or margin, + you'll want to define those yourself. */ + +/* www.aestheticallyloyal.com/public/optimize-legibility/ */ +h1,h2,h3,h4,h5,h6 { font-weight: bold; } + +/* always force a scrollbar in non-IE */ +html { overflow-y: scroll; } + +/* Accessible focus treatment: people.opera.com/patrickl/experiments/keyboard/test */ +a:hover, a:active { outline: none; } + +a, a:active, a:visited { color:#607890; } +a:hover { color:#036; } + +ul, ol { margin-left: 1.8em; } +ol { list-style-type: decimal; } + +/* Remove margins for navigation lists */ +nav ul, nav li { margin: 0; } + +small { font-size:85%; } +strong, th { font-weight: bold; } + +td, td img { vertical-align:top; } + +sub { vertical-align: sub; font-size: smaller; } +sup { vertical-align: super; font-size: smaller; } + +pre { + padding: 15px; + + /* www.pathf.com/blogs/2008/05/formatting-quoted-code-in-blog-posts-css21-white-space-pre-wrap/ */ + white-space: pre; /* CSS2 */ + white-space: pre-wrap; /* CSS 2.1 */ + white-space: pre-line; /* CSS 3 (and 2.1 as well, actually) */ + word-wrap: break-word; /* IE */ +} + +textarea { overflow: auto; } /* thnx ivannikolic! www.sitepoint.com/blogs/2010/08/20/ie-remove-textarea-scrollbars/ */ + +.ie6 legend, .ie7 legend { margin-left: -7px; } /* thnx ivannikolic! */ + +/* align checkboxes, radios, text inputs with their label + by: Thierry Koblentz tjkdesign.com/ez-css/css/base.css */ +input[type="radio"] { vertical-align: text-bottom; } +input[type="checkbox"] { vertical-align: bottom; } +.ie7 input[type="checkbox"] { vertical-align: baseline; } +.ie6 input { vertical-align: text-bottom; } + +/* hand cursor on clickable input elements */ +label, input[type=button], input[type=submit], button { cursor: pointer; } + +/* webkit browsers add a 2px margin outside the chrome of form elements */ +button, input, select, textarea { margin: 0; } + +/* colors for form validity */ +input:valid, textarea:valid { } +input:invalid, textarea:invalid { + border-radius: 1px; + -moz-box-shadow: 0px 0px 5px red; + -webkit-box-shadow: 0px 0px 5px red; + box-shadow: 0px 0px 5px red; +} +.no-boxshadow input:invalid, +.no-boxshadow textarea:invalid { background-color: #f0dddd; } + + +/* These selection declarations have to be separate. + No text-shadow: twitter.com/miketaylr/status/12228805301 + Also: hot pink. */ +::-moz-selection{ background: #555; color:#fff; text-shadow: none; } +::selection { background:#555; color:#fff; text-shadow: none; } + +/* j.mp/webkit-tap-highlight-color */ +a:link { -webkit-tap-highlight-color: #555; } + + +/* make buttons play nice in IE: + www.viget.com/inspire/styling-the-button-element-in-internet-explorer/ */ +button { width: auto; overflow: visible; } + +/* bicubic resizing for non-native sized IMG: + code.flickr.com/blog/2008/11/12/on-ui-quality-the-little-things-client-side-image-resizing/ */ +.ie7 img { -ms-interpolation-mode: bicubic; } + +/* + * Non-semantic helper classes + */ + +/* for image replacement */ +.ir { display:block; text-indent:-999em; overflow:hidden; background-repeat: no-repeat; } + +/* Hide for both screenreaders and browsers + css-discuss.incutio.com/wiki/Screenreader_Visibility */ +.hidden { display:none; } + +/* Hide only visually, but have it available for screenreaders + www.webaim.org/techniques/css/invisiblecontent/ + Solution from: j.mp/visuallyhidden - Thanks Jonathan Neal! */ +.visuallyhidden { position:absolute !important; + clip: rect(1px 1px 1px 1px); /* IE6, IE7 */ + clip: rect(1px, 1px, 1px, 1px); } + +/* Hide visually and from screenreaders, but maintain layout */ +.invisible { visibility: hidden; } + +/* >> The Magnificent CLEARFIX: Updated to prevent margin-collapsing on child elements << j.mp/bestclearfix */ +.clearfix:before, .clearfix:after { + content: "\0020"; display: block; height: 0; visibility: hidden; +} + +.clearfix:after { clear: both; } +/* Fix clearfix: blueprintcss.lighthouseapp.com/projects/15318/tickets/5-extra-margin-padding-bottom-of-page */ +.clearfix { zoom: 1; } + + +/*********** layout info (ez.css) ***********/ +/* 2009 -2010 (c) | ez-css.org + * ez-plug-min.css :: version 1.1 :: 01182010 + */ +.ez-wr:after,.ez-box:after{content:".";display:block;height:0;clear:both;visibility:hidden}.ez-wr,.ez-box,.ez-last{display:inline-block;min-height:0}/* \*/ * html .ez-wr,* html .ez-box,* html .ez-last{height:1%}.ez-wr,.ez-box,.ez-last{display:block}/* */.ez-oh{overflow:hidden}* html .ez-oh{overflow:visible}.ez-oa{overflow:auto}.ez-dt{display:table}.ez-it{display:inline-table}.ez-tc{display:table-cell}.ez-ib{display:inline-block}.ez-fl{float:left}* html .ez-fl{margin-right:-3px}.ez-fr{float:right}* html .ez-fr{margin-left:-3px}.ez-25{width:25%}.ez-33{width:33.33%}.ez-50{width:50%}.ez-66{width:66.66%}.ez-75{width:75%}.ez-negmr{margin-right:-1px}* html .ez-negmr{margin-right:-4px}.ez-negmx{margin-right:-1px}.ez-negml{margin-left:-1px}* html .ez-negml{margin-left:-4px} + + +/*********** add bottom line to table rows ***********/ +th, td { padding: 0.1em 0.5em 0.1em 0.5em;} + +/*********** labels bold and occasionally centered ***********/ +label { + white-space: nowrap; +} +label, b, th { + font-weight: bold; +} +thead th { + text-align: center; + border-bottom: 1px solid #444; +} +/*********** forms and table padding ***********/ +form, table { + padding: 5px 10px 5px 10px; +} + +/*********** code blocks ***********/ +code { + padding: 3px 5px; + font-family: Andale Mono, monospace; + font-size: 0.9em; +} + +/*********** left and right padding to quoted text ***********/ +blockquote { + background: #cccccc; + border-left: 30px transparent; + border-right: 30px transparent; + /*padding: 5px;*/ +} + +input[type=text], input[type=password], textarea, select { + margin: 2px 15px 2px 5px; + width: 280px; + background: #fff; + color: #555; + border: 1px solid #dedede; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + border-radius: 2px; + font-size: 12px; +} + +input[type=text], input[type=password] { + height: 16px; +} + +select[multiple=multiple] { + height: 90px; +} + +input[type=submit], input[type=button], button { + margin: 0px; + /*width: 85px;*/ + height: 22px; + background: #eaeaea; + color: #555; + border: 1px solid #dedede; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + border-radius: 2px; +} + +fieldset { border: 1px solid #dedede; padding: 6px; } +legend { font-weight: bold; } + +input:focus, textarea:focus { background: #fafafa; } + +p {text-indent:30px;} + +p, blockquote { + margin-bottom: 10px; +} + +h1,h2,h3,h4,h5,h6 { line-height: 170%; } +h1 {font-size: 2.0em;} +h2 {font-size: 1.8em;} +h3 {font-size: 1.4em;} +h4 {font-size: 1.2em;} +h5 {font-size: 1.0em;} +h6 {font-size: 0.8em;} + +/*********** page layout alignment, width and padding ***********/ +/*body {background-color: #000;}*/ +#container, #header, #page, #content, #statusbar, +#footer, #wrapper { display:block; line-height: 170%; } +#wrapper {width: 900px;} +#container { + margin: 0 auto; + padding: 0; +} +#wrapper {margin: 0 auto;} +#wrapper {background-color: #fff; padding: 5px;} +#statusbar { margin: 5px 0px 20px 0px;} +#footer { + margin-top: 30px; + padding: 5px; +} +#statusbar, #footer { + background: #eaeaea; + border-top: 1px #aaa solid; +} +#logo { + width: 68px; + height: 62px; + background: url(../images/logo.png); +} +#appname { + color: #cccccc; +} + +#right_sidebar { width: 160px; float:right; display: none; } +#left_sidebar { width: 160px; float:left; display: none; } +#content { float: left; /*width: 740px;*//*width: 63%;*/ /*width: 640px; float:left;*/ } /* uncomment this if you are going to use sidebars */ + +.auth_navbar { + top: 0px; + float: right; + padding: 3px 10px 3px 10px; +} + +/*********** web2py specific ***********/ +div.flash { + font-weight: bold; + display: none; + position: fixed; + padding: 10px; + top: 40px; + right: 10px; + min-width: 280px; + opacity: 0.85; + margin: 0px 0px 10px 10px; + color: #fff; + vertical-align: middle; + cursor: pointer; + background: #000; + border: 2px solid #fff; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + z-index: 2; +} +div.error { + background-color: red; + color: white; + padding: 3px; +} + +/*************************** + * CSS 3 Buttons + * http://github.com/michenriksen/css3buttons + * created by Michael Henriksen + * License: Unlicense + * + * *******************/ + +a.button { display: inline-block; padding: 3px 5px 3px 5px; font-family: 'lucida grande', tahoma, verdana, arial, sans-serif; font-size: 12px; color: #3C3C3D; text-shadow: 1px 1px 0 #FFFFFF; background: #ECECEC url('../images/css3buttons_backgrounds.png') 0 0 no-repeat; white-space: nowrap; overflow: visible; cursor: pointer; text-decoration: none; border: 1px solid #CACACA; -webkit-border-radius: 2px; -moz-border-radius: 2px; -webkit-background-clip: padding-box; border-radius: 2px; outline: none; position: relative; zoom: 1; *display: inline; } +a.button.primary { font-weight: bold } +a.button:hover { color: #FFFFFF; border-color: #388AD4; text-decoration: none; text-shadow: -1px -1px 0 rgba(0,0,0,0.3); background-position: 0 -40px; background-color: #2D7DC5; } +a.button:active, +a.button.active { background-position: 0 -81px; border-color: #347BBA; background-color: #0F5EA2; color: #FFFFFF; text-shadow: none; } +a.button:active { top: 1px } +a.button.negative:hover { color: #FFFFFF; background-position: 0 -121px; background-color: #D84743; border-color: #911D1B; } +a.button.negative:active, +a.button.negative.active { background-position: 0 -161px; background-color: #A5211E; border-color: #911D1B; } +a.button.pill { -webkit-border-radius: 19px; -moz-border-radius: 19px; border-radius: 19px; padding: 2px 10px 2px 10px; } +a.button.left { -webkit-border-bottom-right-radius: 0px; -webkit-border-top-right-radius: 0px; -moz-border-radius-bottomright: 0px; -moz-border-radius-topright: 0px; border-bottom-right-radius: 0px; border-top-right-radius: 0px; margin-right: 0px; } +a.button.middle { margin-right: 0px; margin-left: 0px; -webkit-border-radius: 0px; -moz-border-radius: 0px; border-radius: 0px; border-right: none; border-left: none; } +a.button.right { -webkit-border-bottom-left-radius: 0px; -webkit-border-top-left-radius: 0px; -moz-border-radius-bottomleft: 0px; -moz-border-radius-topleft: 0px; border-top-left-radius: 0px; border-bottom-left-radius: 0px; margin-left: 0px; } +a.button.left:active, +a.button.middle:active, +a.button.right:active { top: 0px } +a.button.big { font-size: 16px; padding-left: 17px; padding-right: 17px; } +a.button span.icon { display: inline-block; width: 14px; height: 12px; margin: auto 7px auto auto; position: relative; top: 2px; background-image: url('../images/css3buttons_icons.png'); background-repeat: no-repeat; } +a.big.button span.icon { top: 0px } +a.button span.icon.book { background-position: 0 0 } +a.button:hover span.icon.book { background-position: 0 -15px } +a.button span.icon.calendar { background-position: 0 -30px } +a.button:hover span.icon.calendar { background-position: 0 -45px } +a.button span.icon.chat { background-position: 0 -60px } +a.button:hover span.icon.chat { background-position: 0 -75px } +a.button span.icon.check { background-position: 0 -90px } +a.button:hover span.icon.check { background-position: 0 -103px } +a.button span.icon.clock { background-position: 0 -116px } +a.button:hover span.icon.clock { background-position: 0 -131px } +a.button span.icon.cog { background-position: 0 -146px } +a.button:hover span.icon.cog { background-position: 0 -161px } +a.button span.icon.comment { background-position: 0 -176px } +a.button:hover span.icon.comment { background-position: 0 -190px } +a.button span.icon.cross { background-position: 0 -204px } +a.button:hover span.icon.cross { background-position: 0 -219px } +a.button span.icon.downarrow { background-position: 0 -234px } +a.button:hover span.icon.downarrow { background-position: 0 -249px } +a.button span.icon.fork { background-position: 0 -264px } +a.button:hover span.icon.fork { background-position: 0 -279px } +a.button span.icon.heart { background-position: 0 -294px } +a.button:hover span.icon.heart { background-position: 0 -308px } +a.button span.icon.home { background-position: 0 -322px } +a.button:hover span.icon.home { background-position: 0 -337px } +a.button span.icon.key { background-position: 0 -352px } +a.button:hover span.icon.key { background-position: 0 -367px } +a.button span.icon.leftarrow { background-position: 0 -382px } +a.button:hover span.icon.leftarrow { background-position: 0 -397px } +a.button span.icon.lock { background-position: 0 -412px } +a.button:hover span.icon.lock { background-position: 0 -427px } +a.button span.icon.loop { background-position: 0 -442px } +a.button:hover span.icon.loop { background-position: 0 -457px } +a.button span.icon.magnifier { background-position: 0 -472px } +a.button:hover span.icon.magnifier { background-position: 0 -487px } +a.button span.icon.mail { background-position: 0 -502px } +a.button:hover span.icon.mail { background-position: 0 -514px } +a.button span.icon.move { background-position: 0 -526px } +a.button:hover span.icon.move { background-position: 0 -541px } +a.button span.icon.pen { background-position: 0 -556px } +a.button:hover span.icon.pen { background-position: 0 -571px } +a.button span.icon.pin { background-position: 0 -586px } +a.button:hover span.icon.pin { background-position: 0 -601px } +a.button span.icon.plus { background-position: 0 -616px } +a.button:hover span.icon.plus { background-position: 0 -631px } +a.button span.icon.reload { background-position: 0 -646px } +a.button:hover span.icon.reload { background-position: 0 -660px } +a.button span.icon.rightarrow { background-position: 0 -674px } +a.button:hover span.icon.rightarrow { background-position: 0 -689px } +a.button span.icon.rss { background-position: 0 -704px } +a.button:hover span.icon.rss { background-position: 0 -719px } +a.button span.icon.tag { background-position: 0 -734px } +a.button:hover span.icon.tag { background-position: 0 -749px } +a.button span.icon.trash { background-position: 0 -764px } +a.button:hover span.icon.trash { background-position: 0 -779px } +a.button span.icon.unlock { background-position: 0 -794px } +a.button:hover span.icon.unlock { background-position: 0 -809px } +a.button span.icon.uparrow { background-position: 0 -824px } +a.button:hover span.icon.uparrow { background-position: 0 -839px } +a.button span.icon.user { background-position: 0 -854px } +a.button:hover span.icon.user { background-position: 0 -869px } + + + + + + +/***************************************************** + * HERE YOU CAN START TO WRITE YOUR OWN DIVS + */ + + + + + + + +/* + * Media queries for responsive design + */ + +@media all and (orientation:portrait) { + /* Style adjustments for portrait mode goes here */ + +} + +@media all and (orientation:landscape) { + /* Style adjustments for landscape mode goes here */ + +} + +/* Grade-A Mobile Browsers (Opera Mobile, iPhone Safari, Android Chrome) + Consider this: www.cloudfour.com/css-media-query-for-mobile-is-fools-gold/ */ +@media screen and (max-device-width: 480px) { + + + /* Uncomment if you don't want iOS and WinMobile to mobile-optimize the text for you + j.mp/textsizeadjust + html { -webkit-text-size-adjust:none; -ms-text-size-adjust:none; } */ +} + + +/* + * print styles + * inlined to avoid required HTTP connection www.phpied.com/delay-loading-your-print-css/ + */ +@media print { + * { background: transparent !important; color: #444 !important; text-shadow: none !important; } + a, a:visited { color: #444 !important; text-decoration: underline; } + a:after { content: " (" attr(href) ")"; } + abbr:after { content: " (" attr(title) ")"; } + .ir a:after { content: ""; } /* Don't show links for images */ + pre, blockquote { border: 1px solid #999; page-break-inside: avoid; } + thead { display: table-header-group; } /* css-discuss.incutio.com/wiki/Printing_Tables */ + tr, img { page-break-inside: avoid; } + @page { margin: 0.5cm; } + p, h2, h3 { orphans: 3; widows: 3; } + h2, h3{ page-break-after: avoid; } +} + ADDED applications/mobileblur/static/css/calendar.css Index: applications/mobileblur/static/css/calendar.css ================================================================== --- applications/mobileblur/static/css/calendar.css +++ applications/mobileblur/static/css/calendar.css @@ -0,0 +1,1 @@ +.calendar{z-index:99;position:relative;display:none;border-top:2px solid #fff;border-right:2px solid #000;border-bottom:2px solid #000;border-left:2px solid #fff;font-size:11px;color:#000;cursor:default;background:#d4d0c8;font-family:tahoma,verdana,sans-serif;}.calendar table{border-top:1px solid #000;border-right:1px solid #fff;border-bottom:1px solid #fff;border-left:1px solid #000;font-size:11px;color:#000;cursor:default;background:#d4d0c8;font-family:tahoma,verdana,sans-serif;}.calendar .button{text-align:center;padding:1px;border-top:1px solid #fff;border-right:1px solid #000;border-bottom:1px solid #000;border-left:1px solid #fff;}.calendar .nav{background:transparent}.calendar thead .title{font-weight:bold;padding:1px;border:1px solid #000;background:#848078;color:#fff;text-align:center;}.calendar thead .name{border-bottom:1px solid #000;padding:2px;text-align:center;background:#f4f0e8;}.calendar thead .weekend{color:#f00;}.calendar thead .hilite{border-top:2px solid #fff;border-right:2px solid #000;border-bottom:2px solid #000;border-left:2px solid #fff;padding:0;background-color:#e4e0d8;}.calendar thead .active{padding:2px 0 0 2px;border-top:1px solid #000;border-right:1px solid #fff;border-bottom:1px solid #fff;border-left:1px solid #000;background-color:#c4c0b8;}.calendar tbody .day{width:2em;text-align:right;padding:2px 4px 2px 2px;}.calendar tbody .day.othermonth{font-size:80%;color:#aaa;}.calendar tbody .day.othermonth.oweekend{color:#faa;}.calendar table .wn{padding:2px 3px 2px 2px;border-right:1px solid #000;background:#f4f0e8;}.calendar tbody .rowhilite td{background:#e4e0d8;}.calendar tbody .rowhilite td.wn{background:#d4d0c8;}.calendar tbody td.hilite{padding:1px 3px 1px 1px;border-top:1px solid #fff;border-right:1px solid #000;border-bottom:1px solid #000;border-left:1px solid #fff;}.calendar tbody td.active{padding:2px 2px 0 2px;border-top:1px solid #000;border-right:1px solid #fff;border-bottom:1px solid #fff;border-left:1px solid #000;}.calendar tbody td.selected{font-weight:bold;border-top:1px solid #000;border-right:1px solid #fff;border-bottom:1px solid #fff;border-left:1px solid #000;padding:2px 2px 0 2px;background:#e4e0d8;}.calendar tbody td.weekend{color:#f00;}.calendar tbody td.today{font-weight:bold;color:#00f;}.calendar tbody .disabled{color:#999;}.calendar tbody .emptycell{visibility:hidden;}.calendar tbody .emptyrow{display:none;}.calendar tfoot .ttip{background:#f4f0e8;padding:1px;border:1px solid #000;background:#848078;color:#fff;text-align:center;}.calendar tfoot .hilite{border-top:1px solid #fff;border-right:1px solid #000;border-bottom:1px solid #000;border-left:1px solid #fff;padding:1px;background:#e4e0d8;}.calendar tfoot .active{padding:2px 0 0 2px;border-top:1px solid #000;border-right:1px solid #fff;border-bottom:1px solid #fff;border-left:1px solid #000;}.calendar .combo{position:absolute;display:none;width:4em;top:0;left:0;cursor:default;border-top:1px solid #fff;border-right:1px solid #000;border-bottom:1px solid #000;border-left:1px solid #fff;background:#e4e0d8;font-size:90%;padding:1px;z-index:100;}.calendar .combo .label,.calendar .combo .label-IEfix{text-align:center;padding:1px;}.calendar .combo .label-IEfix{width:4em;}.calendar .combo .active{background:#c4c0b8;padding:0;border-top:1px solid #000;border-right:1px solid #fff;border-bottom:1px solid #fff;border-left:1px solid #000;}.calendar .combo .hilite{background:#048;color:#fea;}.calendar td.time{border-top:1px solid #000;padding:1px 0;text-align:center;background-color:#f4f0e8;}.calendar td.time .hour,.calendar td.time .minute,.calendar td.time .ampm{padding:0 3px 0 4px;border:1px solid #889;font-weight:bold;background-color:#fff;}.calendar td.time .ampm{text-align:center;}.calendar td.time .colon{padding:0 2px 0 3px;font-weight:bold;}.calendar td.time span.hilite{border-color:#000;background-color:#766;color:#fff;}.calendar td.time span.active{border-color:#f00;background-color:#000;color:#0f0;}#CP_hourcont{z-index:99;padding:0;position:absolute;border:1px dashed #666;background-color:#eee;display:none;}#CP_minutecont{z-index:99;background-color:#ddd;padding:1px;position:absolute;width:45px;display:none;}.floatleft{float:left;}.CP_hour{z-index:99;padding:1px;font-family:Arial,Helvetica,sans-serif;font-size:9px;white-space:nowrap;cursor:pointer;width:35px;}.CP_minute{z-index:99;padding:1px;font-family:Arial,Helvetica,sans-serif;font-size:9px;white-space:nowrap;cursor:pointer;width:auto;}.CP_over{background-color:#fff;z-index:99} ADDED applications/mobileblur/static/css/handheld.css Index: applications/mobileblur/static/css/handheld.css ================================================================== --- applications/mobileblur/static/css/handheld.css +++ applications/mobileblur/static/css/handheld.css @@ -0,0 +1,7 @@ + +* { + float: none; /* Screens are not big enough to account for floats */ + font-size: 80%; /* Slightly reducing font size to reduce need to scroll */ + background: #fff; /* As much contrast as possible */ + color: #000; +} ADDED applications/mobileblur/static/css/superfish-navbar.css Index: applications/mobileblur/static/css/superfish-navbar.css ================================================================== --- applications/mobileblur/static/css/superfish-navbar.css +++ applications/mobileblur/static/css/superfish-navbar.css @@ -0,0 +1,93 @@ + +/*** adding the class sf-navbar in addition to sf-menu creates an all-horizontal nav-bar menu ***/ +.sf-navbar { + background: #BDD2FF; + height: 2.5em; + padding-bottom: 2.5em; + position: relative; +} +.sf-navbar li { + background: #AABDE6; + position: static; +} +.sf-navbar a { + border-top: none; +} +.sf-navbar li ul { + width: 44em; /*IE6 soils itself without this*/ +} +.sf-navbar li li { + background: #BDD2FF; + position: relative; +} +.sf-navbar li li ul { + width: 13em; +} +.sf-navbar li li li { + width: 100%; +} +.sf-navbar ul li { + width: auto; + float: left; +} +.sf-navbar a, .sf-navbar a:visited { + border: none; +} +.sf-navbar li.current { + background: #BDD2FF; +} +.sf-navbar li:hover, +.sf-navbar li.sfHover, +.sf-navbar li li.current, +.sf-navbar a:focus, .sf-navbar a:hover, .sf-navbar a:active { + background: #BDD2FF; +} +.sf-navbar ul li:hover, +.sf-navbar ul li.sfHover, +ul.sf-navbar ul li:hover li, +ul.sf-navbar ul li.sfHover li, +.sf-navbar ul a:focus, .sf-navbar ul a:hover, .sf-navbar ul a:active { + background: #D1DFFF; +} +ul.sf-navbar li li li:hover, +ul.sf-navbar li li li.sfHover, +.sf-navbar li li.current li.current, +.sf-navbar ul li li a:focus, .sf-navbar ul li li a:hover, .sf-navbar ul li li a:active { + background: #E6EEFF; +} +ul.sf-navbar .current ul, +ul.sf-navbar ul li:hover ul, +ul.sf-navbar ul li.sfHover ul { + left: 0; + top: 2.5em; /* match top ul list item height */ +} +ul.sf-navbar .current ul ul { + top: -999em; +} + +.sf-navbar li li.current > a { + font-weight: bold; +} + +/*** point all arrows down ***/ +/* point right for anchors in subs */ +.sf-navbar ul .sf-sub-indicator { background-position: -10px -100px; } +.sf-navbar ul a > .sf-sub-indicator { background-position: 0 -100px; } +/* apply hovers to modern browsers */ +.sf-navbar ul a:focus > .sf-sub-indicator, +.sf-navbar ul a:hover > .sf-sub-indicator, +.sf-navbar ul a:active > .sf-sub-indicator, +.sf-navbar ul li:hover > a > .sf-sub-indicator, +.sf-navbar ul li.sfHover > a > .sf-sub-indicator { + background-position: -10px -100px; /* arrow hovers for modern browsers*/ +} + +/*** remove shadow on first submenu ***/ +.sf-navbar > li > ul { + background: transparent; + padding: 0; + -moz-border-radius-bottomleft: 0; + -moz-border-radius-topright: 0; + -webkit-border-top-right-radius: 0; + -webkit-border-bottom-left-radius: 0; +} ADDED applications/mobileblur/static/css/superfish-vertical.css Index: applications/mobileblur/static/css/superfish-vertical.css ================================================================== --- applications/mobileblur/static/css/superfish-vertical.css +++ applications/mobileblur/static/css/superfish-vertical.css @@ -0,0 +1,23 @@ +/*** adding sf-vertical in addition to sf-menu creates a vertical menu ***/ +.sf-vertical, .sf-vertical li { + width: 10em; +} +/* this lacks ul at the start of the selector, so the styles from the main CSS file override it where needed */ +.sf-vertical li:hover ul, +.sf-vertical li.sfHover ul { + left: 10em; /* match ul width */ + top: 0; +} + +/*** alter arrow directions ***/ +.sf-vertical .sf-sub-indicator { background-position: -10px 0; } /* IE6 gets solid image only */ +.sf-vertical a > .sf-sub-indicator { background-position: 0 0; } /* use translucent arrow for modern browsers*/ + +/* hover arrow direction for modern browsers*/ +.sf-vertical a:focus > .sf-sub-indicator, +.sf-vertical a:hover > .sf-sub-indicator, +.sf-vertical a:active > .sf-sub-indicator, +.sf-vertical li:hover > a > .sf-sub-indicator, +.sf-vertical li.sfHover > a > .sf-sub-indicator { + background-position: -10px 0; /* arrow hovers for modern browsers*/ +} ADDED applications/mobileblur/static/css/superfish.css Index: applications/mobileblur/static/css/superfish.css ================================================================== --- applications/mobileblur/static/css/superfish.css +++ applications/mobileblur/static/css/superfish.css @@ -0,0 +1,139 @@ + +/*** ESSENTIAL STYLES ***/ +.sf-menu, .sf-menu * { + margin: 0; + padding: 0; + list-style: none; +} +.sf-menu { + line-height: 1.0; +} +.sf-menu ul { + position: absolute; + top: -999em; + width: 10em; /* left offset of submenus need to match (see below) */ +} +.sf-menu ul li { + width: 100%; +} +.sf-menu li:hover { + visibility: inherit; /* fixes IE7 'sticky bug' */ +} +.sf-menu li { + float: left; + position: relative; +} +.sf-menu a { + display: block; + position: relative; +} +.sf-menu li:hover ul, +.sf-menu li.sfHover ul { + left: 0; + top: 2.5em; /* match top ul list item height */ + z-index: 99; +} +ul.sf-menu li:hover li ul, +ul.sf-menu li.sfHover li ul { + top: -999em; +} +ul.sf-menu li li:hover ul, +ul.sf-menu li li.sfHover ul { + left: 10em; /* match ul width */ + top: 0; +} +ul.sf-menu li li:hover li ul, +ul.sf-menu li li.sfHover li ul { + top: -999em; +} +ul.sf-menu li li li:hover ul, +ul.sf-menu li li li.sfHover ul { + left: 10em; /* match ul width */ + top: 0; +} + +/*** DEMO SKIN ***/ +.sf-menu { + float: left; + /*margin-bottom: 1em;*/ +} +.sf-menu a { + border-left: 1px solid #fff; + /*border-top: 1px solid #CFDEFF;*/ + padding: .75em 1em; + text-decoration:none; +} +.sf-menu a, .sf-menu a:visited { /* visited pseudo selector so IE6 applies text colour*/ + color: #275b90;/*#13a;*/ +} +.sf-menu li { + background: #dadada;/*#BDD2FF;*/ +} +.sf-menu li li { + background: #AABDE6; +} +.sf-menu li li a { + /*color: #13a;*/ +} +.sf-menu li li li { + background: #9AAEDB; +} +.sf-menu li:hover, .sf-menu li.sfHover, +.sf-menu a:focus, .sf-menu a:hover, .sf-menu a:active { + background: #CFDEFF; + outline: 0; +} + +/*** arrows **/ +.sf-menu a.sf-with-ul { + padding-right: 2.25em; + min-width: 1px; /* trigger IE7 hasLayout so spans position accurately */ +} +.sf-sub-indicator { + position: absolute; + display: block; + right: .75em; + top: 1.05em; /* IE6 only */ + width: 10px; + height: 10px; + text-indent: -999em; + overflow: hidden; + background: url('../images/arrows-ffffff.png') no-repeat -10px -100px; /* 8-bit indexed alpha png. IE6 gets solid image only */ +} +a > .sf-sub-indicator { /* give all except IE6 the correct values */ + top: .8em; + background-position: 0 -100px; /* use translucent arrow for modern browsers*/ +} +/* apply hovers to modern browsers */ +a:focus > .sf-sub-indicator, +a:hover > .sf-sub-indicator, +a:active > .sf-sub-indicator, +li:hover > a > .sf-sub-indicator, +li.sfHover > a > .sf-sub-indicator { + background-position: -10px -100px; /* arrow hovers for modern browsers*/ +} + +/* point right for anchors in subs */ +.sf-menu ul .sf-sub-indicator { background-position: -10px 0; } +.sf-menu ul a > .sf-sub-indicator { background-position: 0 0; } +/* apply hovers to modern browsers */ +.sf-menu ul a:focus > .sf-sub-indicator, +.sf-menu ul a:hover > .sf-sub-indicator, +.sf-menu ul a:active > .sf-sub-indicator, +.sf-menu ul li:hover > a > .sf-sub-indicator, +.sf-menu ul li.sfHover > a > .sf-sub-indicator { + background-position: -10px 0; /* arrow hovers for modern browsers*/ +} + +/*** shadows for all but IE6 ***/ +.sf-shadow ul { + background: url('../images/shadow.png') no-repeat bottom right; + padding: 0 8px 9px 0; + -moz-border-radius-bottomleft: 17px; + -moz-border-radius-topright: 17px; + -webkit-border-top-right-radius: 17px; + -webkit-border-bottom-left-radius: 17px; +} +.sf-shadow ul.sf-shadow-off { + background: transparent; +} ADDED applications/mobileblur/static/favicon.ico Index: applications/mobileblur/static/favicon.ico ================================================================== --- applications/mobileblur/static/favicon.ico +++ applications/mobileblur/static/favicon.ico cannot compute difference between binary files ADDED applications/mobileblur/static/images/arrows-ffffff.png Index: applications/mobileblur/static/images/arrows-ffffff.png ================================================================== --- applications/mobileblur/static/images/arrows-ffffff.png +++ applications/mobileblur/static/images/arrows-ffffff.png cannot compute difference between binary files ADDED applications/mobileblur/static/images/css3buttons_backgrounds.png Index: applications/mobileblur/static/images/css3buttons_backgrounds.png ================================================================== --- applications/mobileblur/static/images/css3buttons_backgrounds.png +++ applications/mobileblur/static/images/css3buttons_backgrounds.png cannot compute difference between binary files ADDED applications/mobileblur/static/images/css3buttons_icons.png Index: applications/mobileblur/static/images/css3buttons_icons.png ================================================================== --- applications/mobileblur/static/images/css3buttons_icons.png +++ applications/mobileblur/static/images/css3buttons_icons.png cannot compute difference between binary files ADDED applications/mobileblur/static/images/error.png Index: applications/mobileblur/static/images/error.png ================================================================== --- applications/mobileblur/static/images/error.png +++ applications/mobileblur/static/images/error.png cannot compute difference between binary files ADDED applications/mobileblur/static/images/ok.png Index: applications/mobileblur/static/images/ok.png ================================================================== --- applications/mobileblur/static/images/ok.png +++ applications/mobileblur/static/images/ok.png cannot compute difference between binary files ADDED applications/mobileblur/static/images/poweredby.png Index: applications/mobileblur/static/images/poweredby.png ================================================================== --- applications/mobileblur/static/images/poweredby.png +++ applications/mobileblur/static/images/poweredby.png cannot compute difference between binary files ADDED applications/mobileblur/static/images/shadow.png Index: applications/mobileblur/static/images/shadow.png ================================================================== --- applications/mobileblur/static/images/shadow.png +++ applications/mobileblur/static/images/shadow.png cannot compute difference between binary files ADDED applications/mobileblur/static/images/warn.png Index: applications/mobileblur/static/images/warn.png ================================================================== --- applications/mobileblur/static/images/warn.png +++ applications/mobileblur/static/images/warn.png cannot compute difference between binary files ADDED applications/mobileblur/static/images/warning.png Index: applications/mobileblur/static/images/warning.png ================================================================== --- applications/mobileblur/static/images/warning.png +++ applications/mobileblur/static/images/warning.png cannot compute difference between binary files ADDED applications/mobileblur/static/js/calendar.js Index: applications/mobileblur/static/js/calendar.js ================================================================== --- applications/mobileblur/static/js/calendar.js +++ applications/mobileblur/static/js/calendar.js cannot compute difference between binary files ADDED applications/mobileblur/static/js/dd_belatedpng.js Index: applications/mobileblur/static/js/dd_belatedpng.js ================================================================== --- applications/mobileblur/static/js/dd_belatedpng.js +++ applications/mobileblur/static/js/dd_belatedpng.js @@ -0,0 +1,13 @@ +/** +* DD_belatedPNG: Adds IE6 support: PNG images for CSS background-image and HTML . +* Author: Drew Diller +* Email: drew.diller@gmail.com +* URL: http://www.dillerdesign.com/experiment/DD_belatedPNG/ +* Version: 0.0.8a +* Licensed under the MIT License: http://dillerdesign.com/experiment/DD_belatedPNG/#license +* +* Example usage: +* DD_belatedPNG.fix('.png_bg'); // argument is a CSS selector +* DD_belatedPNG.fixPng( someNode ); // argument is an HTMLDomElement +**/ +var DD_belatedPNG={ns:"DD_belatedPNG",imgSize:{},delay:10,nodesFixed:0,createVmlNameSpace:function(){if(document.namespaces&&!document.namespaces[this.ns]){document.namespaces.add(this.ns,"urn:schemas-microsoft-com:vml")}},createVmlStyleSheet:function(){var b,a;b=document.createElement("style");b.setAttribute("media","screen");document.documentElement.firstChild.insertBefore(b,document.documentElement.firstChild.firstChild);if(b.styleSheet){b=b.styleSheet;b.addRule(this.ns+"\\:*","{behavior:url(#default#VML)}");b.addRule(this.ns+"\\:shape","position:absolute;");b.addRule("img."+this.ns+"_sizeFinder","behavior:none; border:none; position:absolute; z-index:-1; top:-10000px; visibility:hidden;");this.screenStyleSheet=b;a=document.createElement("style");a.setAttribute("media","print");document.documentElement.firstChild.insertBefore(a,document.documentElement.firstChild.firstChild);a=a.styleSheet;a.addRule(this.ns+"\\:*","{display: none !important;}");a.addRule("img."+this.ns+"_sizeFinder","{display: none !important;}")}},readPropertyChange:function(){var b,c,a;b=event.srcElement;if(!b.vmlInitiated){return}if(event.propertyName.search("background")!=-1||event.propertyName.search("border")!=-1){DD_belatedPNG.applyVML(b)}if(event.propertyName=="style.display"){c=(b.currentStyle.display=="none")?"none":"block";for(a in b.vml){if(b.vml.hasOwnProperty(a)){b.vml[a].shape.style.display=c}}}if(event.propertyName.search("filter")!=-1){DD_belatedPNG.vmlOpacity(b)}},vmlOpacity:function(b){if(b.currentStyle.filter.search("lpha")!=-1){var a=b.currentStyle.filter;a=parseInt(a.substring(a.lastIndexOf("=")+1,a.lastIndexOf(")")),10)/100;b.vml.color.shape.style.filter=b.currentStyle.filter;b.vml.image.fill.opacity=a}},handlePseudoHover:function(a){setTimeout(function(){DD_belatedPNG.applyVML(a)},1)},fix:function(a){if(this.screenStyleSheet){var c,b;c=a.split(",");for(b=0;bn.H){i.B=n.H}d.vml.image.shape.style.clip="rect("+i.T+"px "+(i.R+a)+"px "+i.B+"px "+(i.L+a)+"px)"}else{d.vml.image.shape.style.clip="rect("+f.T+"px "+f.R+"px "+f.B+"px "+f.L+"px)"}},figurePercentage:function(d,c,f,a){var b,e;e=true;b=(f=="X");switch(a){case"left":case"top":d[f]=0;break;case"center":d[f]=0.5;break;case"right":case"bottom":d[f]=1;break;default:if(a.search("%")!=-1){d[f]=parseInt(a,10)/100}else{e=false}}d[f]=Math.ceil(e?((c[b?"W":"H"]*d[f])-(c[b?"w":"h"]*d[f])):parseInt(a,10));if(d[f]%2===0){d[f]++}return d[f]},fixPng:function(c){c.style.behavior="none";var g,b,f,a,d;if(c.nodeName=="BODY"||c.nodeName=="TD"||c.nodeName=="TR"){return}c.isImg=false;if(c.nodeName=="IMG"){if(c.src.toLowerCase().search(/\.png$/)!=-1){c.isImg=true;c.style.visibility="hidden"}else{return}}else{if(c.currentStyle.backgroundImage.toLowerCase().search(".png")==-1){return}}g=DD_belatedPNG;c.vml={color:{},image:{}};b={shape:{},fill:{}};for(a in c.vml){if(c.vml.hasOwnProperty(a)){for(d in b){if(b.hasOwnProperty(d)){f=g.ns+":"+d;c.vml[a][d]=document.createElement(f)}}c.vml[a].shape.stroked=false;c.vml[a].shape.appendChild(c.vml[a].fill);c.parentNode.insertBefore(c.vml[a].shape,c)}}c.vml.image.shape.fillcolor="none";c.vml.image.fill.type="tile";c.vml.color.fill.on=false;g.attachHandlers(c);g.giveLayout(c);g.giveLayout(c.offsetParent);c.vmlInitiated=true;g.applyVML(c)}};try{document.execCommand("BackgroundImageCache",false,true)}catch(r){}DD_belatedPNG.createVmlNameSpace();DD_belatedPNG.createVmlStyleSheet(); ADDED applications/mobileblur/static/js/jquery.js Index: applications/mobileblur/static/js/jquery.js ================================================================== --- applications/mobileblur/static/js/jquery.js +++ applications/mobileblur/static/js/jquery.js cannot compute difference between binary files ADDED applications/mobileblur/static/js/modernizr-1.7.min.js Index: applications/mobileblur/static/js/modernizr-1.7.min.js ================================================================== --- applications/mobileblur/static/js/modernizr-1.7.min.js +++ applications/mobileblur/static/js/modernizr-1.7.min.js @@ -0,0 +1,2 @@ +// Modernizr v1.7 www.modernizr.com +window.Modernizr=function(a,b,c){function G(){e.input=function(a){for(var b=0,c=a.length;b7)},r.history=function(){return !!(a.history&&history.pushState)},r.draganddrop=function(){return x("dragstart")&&x("drop")},r.websockets=function(){return"WebSocket"in a},r.rgba=function(){A("background-color:rgba(150,255,150,.5)");return D(k.backgroundColor,"rgba")},r.hsla=function(){A("background-color:hsla(120,40%,100%,.5)");return D(k.backgroundColor,"rgba")||D(k.backgroundColor,"hsla")},r.multiplebgs=function(){A("background:url(//:),url(//:),red url(//:)");return(new RegExp("(url\\s*\\(.*?){3}")).test(k.background)},r.backgroundsize=function(){return F("backgroundSize")},r.borderimage=function(){return F("borderImage")},r.borderradius=function(){return F("borderRadius","",function(a){return D(a,"orderRadius")})},r.boxshadow=function(){return F("boxShadow")},r.textshadow=function(){return b.createElement("div").style.textShadow===""},r.opacity=function(){B("opacity:.55");return/^0.55$/.test(k.opacity)},r.cssanimations=function(){return F("animationName")},r.csscolumns=function(){return F("columnCount")},r.cssgradients=function(){var a="background-image:",b="gradient(linear,left top,right bottom,from(#9f9),to(white));",c="linear-gradient(left top,#9f9, white);";A((a+o.join(b+a)+o.join(c+a)).slice(0,-a.length));return D(k.backgroundImage,"gradient")},r.cssreflections=function(){return F("boxReflect")},r.csstransforms=function(){return!!E(["transformProperty","WebkitTransform","MozTransform","OTransform","msTransform"])},r.csstransforms3d=function(){var a=!!E(["perspectiveProperty","WebkitPerspective","MozPerspective","OPerspective","msPerspective"]);a&&"webkitPerspective"in g.style&&(a=w("@media ("+o.join("transform-3d),(")+"modernizr)"));return a},r.csstransitions=function(){return F("transitionProperty")},r.fontface=function(){var a,c,d=h||g,e=b.createElement("style"),f=b.implementation||{hasFeature:function(){return!1}};e.type="text/css",d.insertBefore(e,d.firstChild),a=e.sheet||e.styleSheet;var i=f.hasFeature("CSS2","")?function(b){if(!a||!b)return!1;var c=!1;try{a.insertRule(b,0),c=/src/i.test(a.cssRules[0].cssText),a.deleteRule(a.cssRules.length-1)}catch(d){}return c}:function(b){if(!a||!b)return!1;a.cssText=b;return a.cssText.length!==0&&/src/i.test(a.cssText)&&a.cssText.replace(/\r+|\n+/g,"").indexOf(b.split(" ")[0])===0};c=i('@font-face { font-family: "font"; src: url(data:,); }'),d.removeChild(e);return c},r.video=function(){var a=b.createElement("video"),c=!!a.canPlayType;if(c){c=new Boolean(c),c.ogg=a.canPlayType('video/ogg; codecs="theora"');var d='video/mp4; codecs="avc1.42E01E';c.h264=a.canPlayType(d+'"')||a.canPlayType(d+', mp4a.40.2"'),c.webm=a.canPlayType('video/webm; codecs="vp8, vorbis"')}return c},r.audio=function(){var a=b.createElement("audio"),c=!!a.canPlayType;c&&(c=new Boolean(c),c.ogg=a.canPlayType('audio/ogg; codecs="vorbis"'),c.mp3=a.canPlayType("audio/mpeg;"),c.wav=a.canPlayType('audio/wav; codecs="1"'),c.m4a=a.canPlayType("audio/x-m4a;")||a.canPlayType("audio/aac;"));return c},r.localstorage=function(){try{return!!localStorage.getItem}catch(a){return!1}},r.sessionstorage=function(){try{return!!sessionStorage.getItem}catch(a){return!1}},r.webWorkers=function(){return!!a.Worker},r.applicationcache=function(){return!!a.applicationCache},r.svg=function(){return!!b.createElementNS&&!!b.createElementNS(q.svg,"svg").createSVGRect},r.inlinesvg=function(){var a=b.createElement("div");a.innerHTML="";return(a.firstChild&&a.firstChild.namespaceURI)==q.svg},r.smil=function(){return!!b.createElementNS&&/SVG/.test(n.call(b.createElementNS(q.svg,"animate")))},r.svgclippaths=function(){return!!b.createElementNS&&/SVG/.test(n.call(b.createElementNS(q.svg,"clipPath")))};for(var H in r)z(r,H)&&(v=H.toLowerCase(),e[v]=r[H](),u.push((e[v]?"":"no-")+v));e.input||G(),e.crosswindowmessaging=e.postmessage,e.historymanagement=e.history,e.addTest=function(a,b){a=a.toLowerCase();if(!e[a]){b=!!b(),g.className+=" "+(b?"":"no-")+a,e[a]=b;return e}},A(""),j=l=null,f&&a.attachEvent&&function(){var a=b.createElement("div");a.innerHTML="";return a.childNodes.length!==1}()&&function(a,b){function p(a,b){var c=-1,d=a.length,e,f=[];while(++c »'].join('')), + over = function(){ + var $$ = $(this), menu = getMenu($$); + clearTimeout(menu.sfTimer); + $$.showSuperfishUl().siblings().hideSuperfishUl(); + }, + out = function(){ + var $$ = $(this), menu = getMenu($$), o = sf.op; + clearTimeout(menu.sfTimer); + menu.sfTimer=setTimeout(function(){ + o.retainPath=($.inArray($$[0],o.$path)>-1); + $$.hideSuperfishUl(); + if (o.$path.length && $$.parents(['li.',o.hoverClass].join('')).length<1){over.call(o.$path);} + },o.delay); + }, + getMenu = function($menu){ + var menu = $menu.parents(['ul.',c.menuClass,':first'].join(''))[0]; + sf.op = sf.o[menu.serial]; + return menu; + }, + addArrow = function($a){ $a.addClass(c.anchorClass).append($arrow.clone()); }; + + return this.each(function() { + var s = this.serial = sf.o.length; + var o = $.extend({},sf.defaults,op); + o.$path = $('li.'+o.pathClass,this).slice(0,o.pathLevels).each(function(){ + $(this).addClass([o.hoverClass,c.bcClass].join(' ')) + .filter('li:has(ul)').removeClass(o.pathClass); + }); + sf.o[s] = sf.op = o; + + $('li:has(ul)',this)[($.fn.hoverIntent && !o.disableHI) ? 'hoverIntent' : 'hover'](over,out).each(function() { + if (o.autoArrows) addArrow( $('>a:first-child',this) ); + }) + .not('.'+c.bcClass) + .hideSuperfishUl(); + + var $a = $('a',this); + $a.each(function(i){ + var $li = $a.eq(i).parents('li'); + $a.eq(i).focus(function(){over.call($li);}).blur(function(){out.call($li);}); + }); + o.onInit.call(this); + + }).each(function() { + var menuClasses = [c.menuClass]; + if (sf.op.dropShadows && !($.browser.msie && $.browser.version < 7)) menuClasses.push(c.shadowClass); + $(this).addClass(menuClasses.join(' ')); + }); + }; + + var sf = $.fn.superfish; + sf.o = []; + sf.op = {}; + sf.IE7fix = function(){ + var o = sf.op; + if ($.browser.msie && $.browser.version > 6 && o.dropShadows && o.animation.opacity!=undefined) + this.toggleClass(sf.c.shadowClass+'-off'); + }; + sf.c = { + bcClass : 'sf-breadcrumb', + menuClass : 'sf-js-enabled', + anchorClass : 'sf-with-ul', + arrowClass : 'sf-sub-indicator', + shadowClass : 'sf-shadow' + }; + sf.defaults = { + hoverClass : 'sfHover', + pathClass : 'overideThisToUse', + pathLevels : 1, + delay : 800, + animation : {opacity:'show'}, + speed : 'normal', + autoArrows : true, + dropShadows : true, + disableHI : false, // true disables hoverIntent detection + onInit : function(){}, // callback functions + onBeforeShow: function(){}, + onShow : function(){}, + onHide : function(){} + }; + $.fn.extend({ + hideSuperfishUl : function(){ + var o = sf.op, + not = (o.retainPath===true) ? o.$path : ''; + o.retainPath = false; + var $ul = $(['li.',o.hoverClass].join(''),this).add(this).not(not).removeClass(o.hoverClass) + .find('>ul').hide().css('visibility','hidden'); + o.onHide.call($ul); + return this; + }, + showSuperfishUl : function(){ + var o = sf.op, + sh = sf.c.shadowClass+'-off', + $ul = this.addClass(o.hoverClass) + .find('>ul:hidden').css('visibility','visible'); + sf.IE7fix.call($ul); + o.onBeforeShow.call($ul); + $ul.animate(o.animation,o.speed,function(){ sf.IE7fix.call($ul); o.onShow.call($ul); }); + return this; + } + }); + +})(jQuery); ADDED applications/mobileblur/static/js/web2py_ajax.js Index: applications/mobileblur/static/js/web2py_ajax.js ================================================================== --- applications/mobileblur/static/js/web2py_ajax.js +++ applications/mobileblur/static/js/web2py_ajax.js @@ -0,0 +1,97 @@ +function popup(url) { + newwindow=window.open(url,'name','height=400,width=600'); + if (window.focus) newwindow.focus(); + return false; +} +function collapse(id) { jQuery('#'+id).slideToggle(); } +function fade(id,value) { if(value>0) jQuery('#'+id).hide().fadeIn('slow'); else jQuery('#'+id).show().fadeOut('slow'); } +function ajax(u,s,t) { + query = ''; + if (typeof s == "string") { + d = jQuery(s).serialize(); + if(d){ query = d; } + } else { + pcs = []; + for(i=0; i0){query = pcs.join("&");} + } + jQuery.ajax({type: "POST", url: u, data: query, success: function(msg) { if(t) { if(t==':eval') eval(msg); else jQuery("#" + t).html(msg); } } }); +} + +String.prototype.reverse = function () { return this.split('').reverse().join('');}; +function web2py_ajax_init() { + jQuery('.hidden').hide(); + jQuery('.error').hide().slideDown('slow'); + jQuery('.flash').click(function() { jQuery(this).fadeOut('slow'); return false; }); + // jQuery('input[type=submit]').click(function(){var t=jQuery(this);t.hide();t.after('')}); + jQuery('input.integer').live('keyup', function(){this.value=this.value.reverse().replace(/[^0-9\-]|\-(?=.)/g,'').reverse();}); + jQuery('input.double,input.decimal').live('keyup', function(){this.value=this.value.reverse().replace(/[^0-9\-\.,]|[\-](?=.)|[\.,](?=[0-9]*[\.,])/g,'').reverse();}); + var confirm_message = (typeof w2p_ajax_confirm_message != 'undefined') ? w2p_ajax_confirm_message : "Are you sure you want to delete this object?"; + jQuery("input[type='checkbox'].delete").live('click', function(){ if(this.checked) if(!confirm(confirm_message)) this.checked=false; }); + var date_format = (typeof w2p_ajax_date_format != 'undefined') ? w2p_ajax_date_format : "%Y-%m-%d"; + try {jQuery("input.date").live('focus',function() {Calendar.setup({ + inputField:this, ifFormat:date_format, showsTime:false + }); }); } catch(e) {}; + var datetime_format = (typeof w2p_ajax_datetime_format != 'undefined') ? w2p_ajax_datetime_format : "%Y-%m-%d %H:%M:%S"; + try { jQuery("input.datetime").live('focus', function() {Calendar.setup({ + inputField:this, ifFormat:datetime_format, showsTime: true,timeFormat: "24" + }); }); } catch(e) {}; + + jQuery("input.time").live('focus', function() { var el = jQuery(this); + if (!el.hasClass('hasTimeEntry')) try { el.timeEntry(); } catch(e) {}; + }); +}; + +jQuery(function() { + var flash = jQuery('.flash'); + flash.hide(); + if(flash.html()) flash.slideDown(); + web2py_ajax_init(); +}); +function web2py_trap_form(action,target) { + jQuery('#'+target+' form').each(function(i){ + var form=jQuery(this); + if(!form.hasClass('no_trap')) + form.submit(function(obj){ + jQuery('.flash').hide().html(''); + web2py_ajax_page('post',action,form.serialize(),target); + return false; + }); + }); +} +function web2py_ajax_page(method,action,data,target) { + jQuery.ajax({'type':method,'url':action,'data':data, + 'beforeSend':function(xhr) { + xhr.setRequestHeader('web2py-component-location',document.location); + xhr.setRequestHeader('web2py-component-element',target);}, + 'complete':function(xhr,text){ + var html=xhr.responseText; + var content=xhr.getResponseHeader('web2py-component-content'); + var command=xhr.getResponseHeader('web2py-component-command'); + var flash=xhr.getResponseHeader('web2py-component-flash'); + var t = jQuery('#'+target); + if(content=='prepend') t.prepend(html); + else if(content=='append') t.append(html); + else if(content!='hide') t.html(html); + web2py_trap_form(action,target); + web2py_ajax_init(); + if(command) eval(command); + if(flash) jQuery('.flash').html(flash).slideDown(); + } + }); +} +function web2py_component(action,target) { + jQuery(function(){ web2py_ajax_page('get',action,null,target); }); +} +function web2py_comet(url,onmessage,onopen,onclose) { + if ("WebSocket" in window) { + var ws = new WebSocket(url); + ws.onopen = onopen?onopen:(function(){}); + ws.onmessage = onmessage; + ws.onclose = onclose?onclose:(function(){}); + return true; // supported + } else return false; // not supported +} ADDED applications/mobileblur/static/robots.txt Index: applications/mobileblur/static/robots.txt ================================================================== --- applications/mobileblur/static/robots.txt +++ applications/mobileblur/static/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: /welcome/default/user ADDED applications/mobileblur/views/__init__.py Index: applications/mobileblur/views/__init__.py ================================================================== --- applications/mobileblur/views/__init__.py +++ applications/mobileblur/views/__init__.py ADDED applications/mobileblur/views/appadmin.html Index: applications/mobileblur/views/appadmin.html ================================================================== --- applications/mobileblur/views/appadmin.html +++ applications/mobileblur/views/appadmin.html @@ -0,0 +1,198 @@ +{{extend 'layout.html'}} + + +{{if request.function=='index':}} +

    {{=T("Available databases and tables")}}

    + {{if not databases:}}{{=T("No databases in this application")}}{{pass}} + {{for db in sorted(databases):}} + {{for table in databases[db].tables:}} + {{qry='%s.%s.id>0'%(db,table)}} + {{tbl=databases[db][table]}} + {{if hasattr(tbl,'_primarykey'):}} + {{if tbl._primarykey:}} + {{firstkey=tbl[tbl._primarykey[0]]}} + {{if firstkey.type in ['string','text']:}} + {{qry='%s.%s.%s!=""'%(db,table,firstkey.name)}} + {{else:}} + {{qry='%s.%s.%s>0'%(db,table,firstkey.name)}} + {{pass}} + {{else:}} + {{qry=''}} + {{pass}} + {{pass}} +

    {{=A("%s.%s" % (db,table),_href=URL('select',args=[db],vars=dict(query=qry)))}} +

    + [ {{=A(str(T('insert new'))+' '+table,_href=URL('insert',args=[db,table]))}} ] +

    + {{pass}} + {{pass}} + +{{elif request.function=='select':}} +

    {{=XML(str(T("database %s select"))%A(request.args[0],_href=URL('index'))) }} +

    + {{if table:}} + [ {{=A(str(T('insert new %s'))%table,_href=URL('insert',args=[request.args[0],table]))}} ]

    +

    {{=T("Rows in table")}}


    + {{else:}} +

    {{=T("Rows selected")}}


    + {{pass}} + {{=form}} +

    {{=T('The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.')}}
    + {{=T('Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.')}}
    + {{=T('"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN')}}

    +

    +

    {{=nrows}} {{=T("selected")}}

    + {{if start>0:}}[ {{=A(T('previous 100 rows'),_href=URL('select',args=request.args[0],vars=dict(start=start-100)))}} ]{{pass}} + {{if stop + {{linkto=URL('update',args=request.args[0])}} + {{upload=URL('download',args=request.args[0])}} + {{=SQLTABLE(rows,linkto,upload,orderby=True,_class='sortable')}} + + {{pass}} +

    {{=T("Import/Export")}}


    + [ {{=T("export as csv file")}} ] + {{if table:}} + {{=FORM(str(T('or import from csv file'))+" ",INPUT(_type='file',_name='csvfile'),INPUT(_type='hidden',_value=table,_name='table'),INPUT(_type='submit',_value='import'))}} + {{pass}} + + +{{elif request.function=='insert':}} +

    {{=T("database")}} {{=A(request.args[0],_href=URL('index'))}} + {{if hasattr(table,'_primarykey'):}} + {{fieldname=table._primarykey[0]}} + {{dbname=request.args[0]}} + {{tablename=request.args[1]}} + {{cond = table[fieldname].type in ['string','text'] and '!=""' or '>0'}} + {{=T("table")}} {{=A(tablename,_href=URL('select',args=dbname,vars=dict(query='%s.%s.%s%s'%(dbname,tablename,fieldname,cond))))}} + {{else:}} + {{=T("table")}} {{=A(request.args[1],_href=URL('select',args=request.args[0],vars=dict(query='%s.%s.id>0'%tuple(request.args[:2]))))}} + {{pass}} +

    +

    {{=T("New Record")}}


    + {{=form}} + + + +{{elif request.function=='update':}} +

    {{=T("database")}} {{=A(request.args[0],_href=URL('index'))}} + {{if hasattr(table,'_primarykey'):}} + {{fieldname=request.vars.keys()[0]}} + {{dbname=request.args[0]}} + {{tablename=request.args[1]}} + {{cond = table[fieldname].type in ['string','text'] and '!=""' or '>0'}} + {{=T("table")}} {{=A(tablename,_href=URL('select',args=dbname,vars=dict(query='%s.%s.%s%s'%(dbname,tablename,fieldname,cond))))}} + {{=T("record")}} {{=A('%s=%s'%request.vars.items()[0],_href=URL('update',args=request.args[:2],vars=request.vars))}} + {{else:}} + {{=T("table")}} {{=A(request.args[1],_href=URL('select',args=request.args[0],vars=dict(query='%s.%s.id>0'%tuple(request.args[:2]))))}} + {{=T("record id")}} {{=A(request.args[2],_href=URL('update',args=request.args[:3]))}} + {{pass}} +

    +

    {{=T("Edit current record")}}



    {{=form}} + + + +{{elif request.function=='state':}} +

    {{=T("Internal State")}}

    +

    {{=T("Current request")}}

    + {{=BEAUTIFY(request)}} +

    {{=T("Current response")}}

    + {{=BEAUTIFY(response)}} +

    {{=T("Current session")}}

    + {{=BEAUTIFY(session)}} + + +{{elif request.function == 'ccache':}} +

    Cache

    +
    +
    +
    + Statistics +
    +
    +

    Overview

    +

    + Hit Ratio: + {{=total['ratio']}}% + ({{=total['hits']}} hits + and {{=total['misses']}} misses) +

    +

    + Size of cache: + {{=total['objects']}} items, + {{=total['bytes']}} bytes + {{if total['bytes'] > 524287:}} + ({{="%.0d" % (total['bytes'] / 1048576)}} MB) + {{pass}} +

    +

    + Cache contains items up to + {{="%02d" % total['oldest'][0]}} hours + {{="%02d" % total['oldest'][1]}} minutes + {{="%02d" % total['oldest'][2]}} seconds old. +

    +

    RAM

    +

    + Hit Ratio: + {{=ram['ratio']}}% + ({{=ram['hits']}} hits + and {{=ram['misses']}} misses) +

    +

    + Size of cache: + {{=ram['objects']}} items, + {{=ram['bytes']}} bytes + {{if ram['bytes'] > 524287:}} + ({{=ram['bytes'] / 1048576}} MB) + {{pass}} +

    +

    + RAM contains items up to + {{="%02d" % ram['oldest'][0]}} hours + {{="%02d" % ram['oldest'][1]}} minutes + {{="%02d" % ram['oldest'][2]}} seconds old. +

    +

    DISK

    +

    + Hit Ratio: + {{=disk['ratio']}}% + ({{=disk['hits']}} hits + and {{=disk['misses']}} misses) +

    +

    + Size of cache: + {{=disk['objects']}} items, + {{=disk['bytes']}} bytes + {{if disk['bytes'] > 524287:}} + ({{=disk['bytes'] / 1048576}} MB) + {{pass}} +

    +

    + DISK contains items up to + {{="%02d" % disk['oldest'][0]}} hours + {{="%02d" % disk['oldest'][1]}} minutes + {{="%02d" % disk['oldest'][2]}} seconds old. +

    +
    + +
    + Manage Cache +
    +
    +

    + {{=form}} +

    +
    +
    +
    +
    +{{pass}} ADDED applications/mobileblur/views/default/index.html Index: applications/mobileblur/views/default/index.html ================================================================== --- applications/mobileblur/views/default/index.html +++ applications/mobileblur/views/default/index.html @@ -0,0 +1,12 @@ +{{left_sidebar_enabled=right_sidebar_enabled=False}} +{{extend 'layout.html'}} + +{{ for feed in feeds.itervalues(): }} + {{ if threshold == -1 and feed["ng"] > 0: }} [ {{= feed["ng"] }} ] {{ pass }} + {{ if threshold <= 0 and feed["nt"] > 0: }} [ {{= feed["nt"] }} ] {{ pass }} + {{if feed["ps"] > 0: }}[ {{= feed["ps"] }} ] {{ pass }} + {{= feed["feed_title"] }}
    +{{ pass }} + +{{block left_sidebar}}New Left Sidebar Content{{end}} +{{block right_sidebar}}New Right Sidebar Content{{end}} ADDED applications/mobileblur/views/default/user.html Index: applications/mobileblur/views/default/user.html ================================================================== --- applications/mobileblur/views/default/user.html +++ applications/mobileblur/views/default/user.html @@ -0,0 +1,19 @@ +{{extend 'layout.html'}} +

    {{=T( request.args(0).replace('_',' ').capitalize() )}}

    +
    +{{=form}} +{{if request.args(0)=='login':}} +{{if not 'register' in auth.settings.actions_disabled:}} +
    register +{{pass}} +{{if not 'request_reset_password' in auth.settings.actions_disabled:}} +
    lost password +{{pass}} +{{pass}} +
    + + ADDED applications/mobileblur/views/feeds/view.html Index: applications/mobileblur/views/feeds/view.html ================================================================== --- applications/mobileblur/views/feeds/view.html +++ applications/mobileblur/views/feeds/view.html @@ -0,0 +1,12 @@ +{{left_sidebar_enabled=right_sidebar_enabled=False}} +{{extend 'layout.html'}} + +

    {{= feed["feed_title"] }}

    + Mark feed as read + +{{ for story in stories: }} +

    {{= story["story_title"] }}

    +{{ pass }} + +{{block left_sidebar}}New Left Sidebar Content{{end}} +{{block right_sidebar}}New Right Sidebar Content{{end}} ADDED applications/mobileblur/views/generic.html Index: applications/mobileblur/views/generic.html ================================================================== --- applications/mobileblur/views/generic.html +++ applications/mobileblur/views/generic.html @@ -0,0 +1,16 @@ +{{extend 'layout.html'}} +{{""" + +You should not modify this file. +It is used as default when a view is not provided for your controllers + +"""}} +

    {{=' '.join(x.capitalize() for x in request.function.split('_'))}}

    +{{if len(response._vars)==1:}} +{{=response._vars.values()[0]}} +{{elif len(response._vars)>1:}} +{{=BEAUTIFY(response._vars)}} +{{pass}} +{{if request.is_local:}} +{{=response.toolbar()}} +{{pass}} ADDED applications/mobileblur/views/generic.json Index: applications/mobileblur/views/generic.json ================================================================== --- applications/mobileblur/views/generic.json +++ applications/mobileblur/views/generic.json @@ -0,0 +1,1 @@ +{{from gluon.serializers import json}}{{=XML(json(response._vars))}} ADDED applications/mobileblur/views/generic.load Index: applications/mobileblur/views/generic.load ================================================================== --- applications/mobileblur/views/generic.load +++ applications/mobileblur/views/generic.load @@ -0,0 +1,30 @@ +{{''' +# License: Public Domain +# Author: Iceberg at 21cn dot com + +With this generic.load file, you can use same function to serve two purposes. + += regular action +- ajax callback (when called with .load) + +Example modified from http://www.web2py.com/AlterEgo/default/show/252: + +def index(): + return dict( + part1='hello world', + part2=LOAD(url=URL(r=request,f='auxiliary.load'),ajax=True)) + +def auxiliary(): + form=SQLFORM.factory(Field('name')) + if form.accepts(request.vars): + response.flash = 'ok' + return dict(message="Hello %s" % form.vars.name) + return dict(form=form) + +Notice: + +- no need to set response.headers['web2py-response-flash'] +- no need to return a string +even if the function is called via ajax. + +'''}}{{if len(response._vars)==1:}}{{=response._vars.values()[0]}}{{else:}}{{=BEAUTIFY(response._vars)}}{{pass}} ADDED applications/mobileblur/views/generic.pdf Index: applications/mobileblur/views/generic.pdf ================================================================== --- applications/mobileblur/views/generic.pdf +++ applications/mobileblur/views/generic.pdf @@ -0,0 +1,11 @@ +{{ +import os +from gluon.contrib.generics import pdf_from_html +filename = '%s/%s.html' % (request.controller,request.function) +if os.path.exists(os.path.join(request.folder,'views',filename)): + html=response.render(filename) +else: + html=BODY(BEAUTIFY(response._vars)).xml() +pass +=pdf_from_html(html) +}} ADDED applications/mobileblur/views/generic.rss Index: applications/mobileblur/views/generic.rss ================================================================== --- applications/mobileblur/views/generic.rss +++ applications/mobileblur/views/generic.rss @@ -0,0 +1,10 @@ +{{ +### +# response._vars contains the dictionary returned by the controller action +# for this to work the action must return something like +# +# dict(title=...,link=...,description=...,created_on='...',items=...) +# +# items is a list of dictionaries each with title, link, description, pub_date. +### +from gluon.serializers import rss}}{{=XML(rss(response._vars))}} ADDED applications/mobileblur/views/generic.xml Index: applications/mobileblur/views/generic.xml ================================================================== --- applications/mobileblur/views/generic.xml +++ applications/mobileblur/views/generic.xml @@ -0,0 +1,1 @@ +{{from gluon.serializers import xml}}{{=XML(xml(response._vars))}} ADDED applications/mobileblur/views/layout.html Index: applications/mobileblur/views/layout.html ================================================================== --- applications/mobileblur/views/layout.html +++ applications/mobileblur/views/layout.html @@ -0,0 +1,159 @@ + + + + + + + + + + + {{=response.title or request.application}} + + + + + + + + + + + + + + + + + + + + + {{#------ require CSS and JS files for this page (read info in base.css) ------}} + {{response.files.append(URL('static','css/base.css'))}} + {{response.files.append(URL('static','css/superfish.css'))}} + {{response.files.append(URL('static','js/superfish.js'))}} + {{#------ include web2py specific js code (jquery, calendar, form stuff) ------}} + {{include 'web2py_ajax.html'}} + + {{ + #using sidebars need to know what sidebar you want to use + #prior of using it, because of static width size of content, you can use + #left_sidebar, right_sidebar, both or none (False left and right) + left_sidebar_enabled = globals().get('left_sidebar_enabled',False) + right_sidebar_enabled = globals().get('right_sidebar_enabled',False) + if left_sidebar_enabled and right_sidebar_enabled: width_content='63%' + elif left_sidebar_enabled != right_sidebar_enabled: width_content='740px' + else: width_content='100%' + if left_sidebar_enabled: left_sidebar_style = 'style="display: block;"' + else: left_sidebar_style = 'style="display: none;"' + if right_sidebar_enabled: right_sidebar_style = 'style="display: block;"' + else: right_sidebar_style = 'style="display: none;"' + style_content = 'style="width: %s"' % width_content + }} + + + + + + + + + +
    {{=response.flash or ''}}
    + +
    + +
    + + + +
    + {{block statusbar}} + {{#------ superfish menu ------}} + {{=MENU(response.menu,_class='sf-menu')}} + +
    + {{end}} +
    + +
    + + {{if left_sidebar_enabled:}} + + {{pass}} + + +
    + {{include}} +
    + + + {{if right_sidebar_enabled:}} + + {{pass}} + + +
    + +
    + + +
    +
    + + + + + + + ADDED applications/mobileblur/views/stories/view.html Index: applications/mobileblur/views/stories/view.html ================================================================== --- applications/mobileblur/views/stories/view.html +++ applications/mobileblur/views/stories/view.html @@ -0,0 +1,9 @@ +{{left_sidebar_enabled=right_sidebar_enabled=False}} +{{extend 'layout.html'}} + +

    {{= story["story_title"] }}

    + +{{= XML(story["story_content"]) }} + +{{block left_sidebar}}New Left Sidebar Content{{end}} +{{block right_sidebar}}New Right Sidebar Content{{end}} ADDED applications/mobileblur/views/web2py_ajax.html Index: applications/mobileblur/views/web2py_ajax.html ================================================================== --- applications/mobileblur/views/web2py_ajax.html +++ applications/mobileblur/views/web2py_ajax.html @@ -0,0 +1,25 @@ +{{ +response.files.insert(0,URL('static','js/jquery.js')) +response.files.insert(1,URL('static','css/calendar.css')) +response.files.insert(2,URL('static','js/calendar.js')) +for _item in response.meta or []:}} + {{ +pass +for _k,_file in enumerate(response.files or []): + if _file in response.files[:_k]: + continue + _file0=_file.lower().split('?')[0] + if _file0.endswith('.css'):}} + {{ + elif _file0.endswith('.js'):}} + {{ + pass +pass +}} + + ADDED applications/welcome/ABOUT Index: applications/welcome/ABOUT ================================================================== --- applications/welcome/ABOUT +++ applications/welcome/ABOUT @@ -0,0 +1,2 @@ +Write something about this app. +Developed with web2py. ADDED applications/welcome/LICENSE Index: applications/welcome/LICENSE ================================================================== --- applications/welcome/LICENSE +++ applications/welcome/LICENSE @@ -0,0 +1,4 @@ +The web2py welcome app is licensed under public domain +(except for the css and js files that it includes, which have their own third party licenses). + +You can modify this license when you add your own code. ADDED applications/welcome/__init__.py Index: applications/welcome/__init__.py ================================================================== --- applications/welcome/__init__.py +++ applications/welcome/__init__.py ADDED applications/welcome/controllers/appadmin.py Index: applications/welcome/controllers/appadmin.py ================================================================== --- applications/welcome/controllers/appadmin.py +++ applications/welcome/controllers/appadmin.py @@ -0,0 +1,408 @@ +# -*- coding: utf-8 -*- + +# ########################################################## +# ## make sure administrator is on localhost +# ########################################################### + +import os +import socket +import datetime +import copy +import gluon.contenttype +import gluon.fileutils + +# ## critical --- make a copy of the environment + +global_env = copy.copy(globals()) +global_env['datetime'] = datetime + +http_host = request.env.http_host.split(':')[0] +remote_addr = request.env.remote_addr +try: + hosts = (http_host, socket.gethostname(), + socket.gethostbyname(http_host), + '::1','127.0.0.1','::ffff:127.0.0.1') +except: + hosts = (http_host, ) + +if request.env.http_x_forwarded_for or request.env.wsgi_url_scheme\ + in ['https', 'HTTPS']: + session.secure() +elif (remote_addr not in hosts) and (remote_addr != "127.0.0.1"): + raise HTTP(200, T('appadmin is disabled because insecure channel')) + +if (request.application=='admin' and not session.authorized) or \ + (request.application!='admin' and not gluon.fileutils.check_credentials(request)): + redirect(URL('admin', 'default', 'index')) + +ignore_rw = True +response.view = 'appadmin.html' +response.menu = [[T('design'), False, URL('admin', 'default', 'design', + args=[request.application])], [T('db'), False, + URL('index')], [T('state'), False, + URL('state')], [T('cache'), False, + URL('ccache')]] + +# ########################################################## +# ## auxiliary functions +# ########################################################### + + +def get_databases(request): + dbs = {} + for (key, value) in global_env.items(): + cond = False + try: + cond = isinstance(value, GQLDB) + except: + cond = isinstance(value, SQLDB) + if cond: + dbs[key] = value + return dbs + + +databases = get_databases(None) + + +def eval_in_global_env(text): + exec ('_ret=%s' % text, {}, global_env) + return global_env['_ret'] + + +def get_database(request): + if request.args and request.args[0] in databases: + return eval_in_global_env(request.args[0]) + else: + session.flash = T('invalid request') + redirect(URL('index')) + + +def get_table(request): + db = get_database(request) + if len(request.args) > 1 and request.args[1] in db.tables: + return (db, request.args[1]) + else: + session.flash = T('invalid request') + redirect(URL('index')) + + +def get_query(request): + try: + return eval_in_global_env(request.vars.query) + except Exception: + return None + + +def query_by_table_type(tablename,db,request=request): + keyed = hasattr(db[tablename],'_primarykey') + if keyed: + firstkey = db[tablename][db[tablename]._primarykey[0]] + cond = '>0' + if firstkey.type in ['string', 'text']: + cond = '!=""' + qry = '%s.%s.%s%s' % (request.args[0], request.args[1], firstkey.name, cond) + else: + qry = '%s.%s.id>0' % tuple(request.args[:2]) + return qry + + + +# ########################################################## +# ## list all databases and tables +# ########################################################### + + +def index(): + return dict(databases=databases) + + +# ########################################################## +# ## insert a new record +# ########################################################### + + +def insert(): + (db, table) = get_table(request) + form = SQLFORM(db[table], ignore_rw=ignore_rw) + if form.accepts(request.vars, session): + response.flash = T('new record inserted') + return dict(form=form,table=db[table]) + + +# ########################################################## +# ## list all records in table and insert new record +# ########################################################### + + +def download(): + import os + db = get_database(request) + return response.download(request,db) + +def csv(): + import gluon.contenttype + response.headers['Content-Type'] = \ + gluon.contenttype.contenttype('.csv') + db = get_database(request) + query = get_query(request) + if not query: + return None + response.headers['Content-disposition'] = 'attachment; filename=%s_%s.csv'\ + % tuple(request.vars.query.split('.')[:2]) + return str(db(query).select()) + + +def import_csv(table, file): + table.import_from_csv_file(file) + +def select(): + import re + db = get_database(request) + dbname = request.args[0] + regex = re.compile('(?P
    \w+)\.(?P\w+)=(?P\d+)') + if len(request.args)>1 and hasattr(db[request.args[1]],'_primarykey'): + regex = re.compile('(?P
    \w+)\.(?P\w+)=(?P.+)') + if request.vars.query: + match = regex.match(request.vars.query) + if match: + request.vars.query = '%s.%s.%s==%s' % (request.args[0], + match.group('table'), match.group('field'), + match.group('value')) + else: + request.vars.query = session.last_query + query = get_query(request) + if request.vars.start: + start = int(request.vars.start) + else: + start = 0 + nrows = 0 + stop = start + 100 + table = None + rows = [] + orderby = request.vars.orderby + if orderby: + orderby = dbname + '.' + orderby + if orderby == session.last_orderby: + if orderby[0] == '~': + orderby = orderby[1:] + else: + orderby = '~' + orderby + session.last_orderby = orderby + session.last_query = request.vars.query + form = FORM(TABLE(TR(T('Query:'), '', INPUT(_style='width:400px', + _name='query', _value=request.vars.query or '', + requires=IS_NOT_EMPTY(error_message=T("Cannot be empty")))), TR(T('Update:'), + INPUT(_name='update_check', _type='checkbox', + value=False), INPUT(_style='width:400px', + _name='update_fields', _value=request.vars.update_fields + or '')), TR(T('Delete:'), INPUT(_name='delete_check', + _class='delete', _type='checkbox', value=False), ''), + TR('', '', INPUT(_type='submit', _value='submit'))), + _action=URL(r=request,args=request.args)) + if request.vars.csvfile != None: + try: + import_csv(db[request.vars.table], + request.vars.csvfile.file) + response.flash = T('data uploaded') + except Exception, e: + response.flash = DIV(T('unable to parse csv file'),PRE(str(e))) + if form.accepts(request.vars, formname=None): +# regex = re.compile(request.args[0] + '\.(?P
    \w+)\.id\>0') + regex = re.compile(request.args[0] + '\.(?P
    \w+)\..+') + + match = regex.match(form.vars.query.strip()) + if match: + table = match.group('table') + try: + nrows = db(query).count() + if form.vars.update_check and form.vars.update_fields: + db(query).update(**eval_in_global_env('dict(%s)' + % form.vars.update_fields)) + response.flash = T('%s rows updated', nrows) + elif form.vars.delete_check: + db(query).delete() + response.flash = T('%s rows deleted', nrows) + nrows = db(query).count() + if orderby: + rows = db(query).select(limitby=(start, stop), + orderby=eval_in_global_env(orderby)) + else: + rows = db(query).select(limitby=(start, stop)) + except Exception, e: + (rows, nrows) = ([], 0) + response.flash = DIV(T('Invalid Query'),PRE(str(e))) + return dict( + form=form, + table=table, + start=start, + stop=stop, + nrows=nrows, + rows=rows, + query=request.vars.query, + ) + + +# ########################################################## +# ## edit delete one record +# ########################################################### + + +def update(): + (db, table) = get_table(request) + keyed = hasattr(db[table],'_primarykey') + record = None + if keyed: + key = [f for f in request.vars if f in db[table]._primarykey] + if key: + record = db(db[table][key[0]] == request.vars[key[0]]).select().first() + else: + record = db(db[table].id == request.args(2)).select().first() + + if not record: + qry = query_by_table_type(table, db) + session.flash = T('record does not exist') + redirect(URL('select', args=request.args[:1], + vars=dict(query=qry))) + + if keyed: + for k in db[table]._primarykey: + db[table][k].writable=False + + form = SQLFORM(db[table], record, deletable=True, delete_label=T('Check to delete'), + ignore_rw=ignore_rw and not keyed, + linkto=URL('select', + args=request.args[:1]), upload=URL(r=request, + f='download', args=request.args[:1])) + + if form.accepts(request.vars, session): + session.flash = T('done!') + qry = query_by_table_type(table, db) + redirect(URL('select', args=request.args[:1], + vars=dict(query=qry))) + return dict(form=form,table=db[table]) + + +# ########################################################## +# ## get global variables +# ########################################################### + + +def state(): + return dict() + +def ccache(): + form = FORM( + P(TAG.BUTTON("Clear CACHE?", _type="submit", _name="yes", _value="yes")), + P(TAG.BUTTON("Clear RAM", _type="submit", _name="ram", _value="ram")), + P(TAG.BUTTON("Clear DISK", _type="submit", _name="disk", _value="disk")), + ) + + if form.accepts(request.vars, session): + clear_ram = False + clear_disk = False + session.flash = "" + if request.vars.yes: + clear_ram = clear_disk = True + if request.vars.ram: + clear_ram = True + if request.vars.disk: + clear_disk = True + + if clear_ram: + cache.ram.clear() + session.flash += "Ram Cleared " + if clear_disk: + cache.disk.clear() + session.flash += "Disk Cleared" + + redirect(URL(r=request)) + + try: + from guppy import hpy; hp=hpy() + except ImportError: + hp = False + + import shelve, os, copy, time, math + from gluon import portalocker + + ram = { + 'bytes': 0, + 'objects': 0, + 'hits': 0, + 'misses': 0, + 'ratio': 0, + 'oldest': time.time() + } + disk = copy.copy(ram) + total = copy.copy(ram) + + for key, value in cache.ram.storage.items(): + if isinstance(value, dict): + ram['hits'] = value['hit_total'] - value['misses'] + ram['misses'] = value['misses'] + try: + ram['ratio'] = ram['hits'] * 100 / value['hit_total'] + except (KeyError, ZeroDivisionError): + ram['ratio'] = 0 + else: + if hp: + ram['bytes'] += hp.iso(value[1]).size + ram['objects'] += hp.iso(value[1]).count + + if value[0] < ram['oldest']: + ram['oldest'] = value[0] + + locker = open(os.path.join(request.folder, + 'cache/cache.lock'), 'a') + portalocker.lock(locker, portalocker.LOCK_EX) + disk_storage = shelve.open(os.path.join(request.folder, 'cache/cache.shelve')) + try: + for key, value in disk_storage.items(): + if isinstance(value, dict): + disk['hits'] = value['hit_total'] - value['misses'] + disk['misses'] = value['misses'] + try: + disk['ratio'] = disk['hits'] * 100 / value['hit_total'] + except (KeyError, ZeroDivisionError): + disk['ratio'] = 0 + else: + if hp: + disk['bytes'] += hp.iso(value[1]).size + disk['objects'] += hp.iso(value[1]).count + if value[0] < disk['oldest']: + disk['oldest'] = value[0] + finally: + portalocker.unlock(locker) + locker.close() + disk_storage.close() + + total['bytes'] = ram['bytes'] + disk['bytes'] + total['objects'] = ram['objects'] + disk['objects'] + total['hits'] = ram['hits'] + disk['hits'] + total['misses'] = ram['misses'] + disk['misses'] + try: + total['ratio'] = total['hits'] * 100 / (total['hits'] + total['misses']) + except (KeyError, ZeroDivisionError): + total['ratio'] = 0 + + if disk['oldest'] < ram['oldest']: + total['oldest'] = disk['oldest'] + else: + total['oldest'] = ram['oldest'] + + def GetInHMS(seconds): + hours = math.floor(seconds / 3600) + seconds -= hours * 3600 + minutes = math.floor(seconds / 60) + seconds -= minutes * 60 + seconds = math.floor(seconds) + + return (hours, minutes, seconds) + + ram['oldest'] = GetInHMS(time.time() - ram['oldest']) + disk['oldest'] = GetInHMS(time.time() - disk['oldest']) + total['oldest'] = GetInHMS(time.time() - total['oldest']) + + return dict(form=form, total=total, + ram=ram, disk=disk) + ADDED applications/welcome/controllers/default.py Index: applications/welcome/controllers/default.py ================================================================== --- applications/welcome/controllers/default.py +++ applications/welcome/controllers/default.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- +# this file is released under public domain and you can use without limitations + +######################################################################### +## This is a samples controller +## - index is the default action of any application +## - user is required for authentication and authorization +## - download is for downloading files uploaded in the db (does streaming) +## - call exposes all registered services (none by default) +######################################################################### + +def index(): + """ + example action using the internationalization operator T and flash + rendered by views/default/index.html or views/generic.html + """ + return dict(message=T('Hello World')) + +def user(): + """ + exposes: + http://..../[app]/default/user/login + http://..../[app]/default/user/logout + http://..../[app]/default/user/register + http://..../[app]/default/user/profile + http://..../[app]/default/user/retrieve_password + http://..../[app]/default/user/change_password + use @auth.requires_login() + @auth.requires_membership('group name') + @auth.requires_permission('read','table name',record_id) + to decorate functions that need access control + """ + return dict(form=auth()) + + +def download(): + """ + allows downloading of uploaded files + http://..../[app]/default/download/[filename] + """ + return response.download(request,db) + + +def call(): + """ + exposes services. for example: + http://..../[app]/default/call/jsonrpc + decorate with @services.jsonrpc the functions to expose + supports xml, json, xmlrpc, jsonrpc, amfrpc, rss, csv + """ + return service() + + +@auth.requires_signature() +def data(): + """ + http://..../[app]/default/data/tables + http://..../[app]/default/data/create/[table] + http://..../[app]/default/data/read/[table]/[id] + http://..../[app]/default/data/update/[table]/[id] + http://..../[app]/default/data/delete/[table]/[id[ + http://..../[app]/default/data/select/[table] + http://..../[app]/default/data/search/[table] + but URLs bust be signed, i.e. linked with + A('table',_href=URL('data/tables',user_signature=True)) + or with the signed load operator + LOAD('default','data.load',args='tables',ajax=True,user_signature=True) + """ + return dict(form=crud()) ADDED applications/welcome/cron/crontab Index: applications/welcome/cron/crontab ================================================================== --- applications/welcome/cron/crontab +++ applications/welcome/cron/crontab @@ -0,0 +1,1 @@ +#crontab ADDED applications/welcome/languages/es-es.py Index: applications/welcome/languages/es-es.py ================================================================== --- applications/welcome/languages/es-es.py +++ applications/welcome/languages/es-es.py @@ -0,0 +1,259 @@ +# coding: utf8 +{ +'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"actualice" es una expresión opcional como "campo1=\'nuevo_valor\'". No se puede actualizar o eliminar resultados de un JOIN', +'%Y-%m-%d': '%Y-%m-%d', +'%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S', +'%s rows deleted': '%s filas eliminadas', +'%s rows updated': '%s filas actualizadas', +'(something like "it-it")': '(algo como "it-it")', +'A new version of web2py is available': 'Hay una nueva versión de web2py disponible', +'A new version of web2py is available: %s': 'Hay una nueva versión de web2py disponible: %s', +'ATTENTION: Login requires a secure (HTTPS) connection or running on localhost.': 'ATENCION: Inicio de sesión requiere una conexión segura (HTTPS) o localhost.', +'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': 'ATENCION: NO EJECUTE VARIAS PRUEBAS SIMULTANEAMENTE, NO SON THREAD SAFE.', +'ATTENTION: you cannot edit the running application!': 'ATENCION: no puede modificar la aplicación que se ejecuta!', +'About': 'Acerca de', +'About application': 'Acerca de la aplicación', +'Admin is disabled because insecure channel': 'Admin deshabilitado, el canal no es seguro', +'Admin is disabled because unsecure channel': 'Admin deshabilitado, el canal no es seguro', +'Administrator Password:': 'Contraseña del Administrador:', +'Are you sure you want to delete file "%s"?': '¿Está seguro que desea eliminar el archivo "%s"?', +'Are you sure you want to uninstall application "%s"': '¿Está seguro que desea desinstalar la aplicación "%s"', +'Are you sure you want to uninstall application "%s"?': '¿Está seguro que desea desinstalar la aplicación "%s"?', +'Authentication': 'Autenticación', +'Available databases and tables': 'Bases de datos y tablas disponibles', +'Cannot be empty': 'No puede estar vacío', +'Cannot compile: there are errors in your app. Debug it, correct errors and try again.': 'No se puede compilar: hay errores en su aplicación. Depure, corrija errores y vuelva a intentarlo.', +'Change Password': 'Cambie Contraseña', +'Check to delete': 'Marque para eliminar', +'Client IP': 'IP del Cliente', +'Controller': 'Controlador', +'Controllers': 'Controladores', +'Copyright': 'Derechos de autor', +'Create new application': 'Cree una nueva aplicación', +'Current request': 'Solicitud en curso', +'Current response': 'Respuesta en curso', +'Current session': 'Sesión en curso', +'DB Model': 'Modelo "db"', +'DESIGN': 'DISEÑO', +'Database': 'Base de datos', +'Date and Time': 'Fecha y Hora', +'Delete': 'Elimine', +'Delete:': 'Elimine:', +'Deploy on Google App Engine': 'Instale en Google App Engine', +'Description': 'Descripción', +'Design for': 'Diseño para', +'E-mail': 'Correo electrónico', +'EDIT': 'EDITAR', +'Edit': 'Editar', +'Edit Profile': 'Editar Perfil', +'Edit This App': 'Edite esta App', +'Edit application': 'Editar aplicación', +'Edit current record': 'Edite el registro actual', +'Editing file': 'Editando archivo', +'Editing file "%s"': 'Editando archivo "%s"', +'Error logs for "%(app)s"': 'Bitácora de errores en "%(app)s"', +'First name': 'Nombre', +'Functions with no doctests will result in [passed] tests.': 'Funciones sin doctests equivalen a pruebas [aceptadas].', +'Group ID': 'ID de Grupo', +'Hello World': 'Hola Mundo', +'Import/Export': 'Importar/Exportar', +'Index': 'Indice', +'Installed applications': 'Aplicaciones instaladas', +'Internal State': 'Estado Interno', +'Invalid Query': 'Consulta inválida', +'Invalid action': 'Acción inválida', +'Invalid email': 'Correo inválido', +'Language files (static strings) updated': 'Archivos de lenguaje (cadenas estáticas) actualizados', +'Languages': 'Lenguajes', +'Last name': 'Apellido', +'Last saved on:': 'Guardado en:', +'Layout': 'Diseño de página', +'License for': 'Licencia para', +'Login': 'Inicio de sesión', +'Login to the Administrative Interface': 'Inicio de sesión para la Interfaz Administrativa', +'Logout': 'Fin de sesión', +'Lost Password': 'Contraseña perdida', +'Main Menu': 'Menú principal', +'Menu Model': 'Modelo "menu"', +'Models': 'Modelos', +'Modules': 'Módulos', +'NO': 'NO', +'Name': 'Nombre', +'New Record': 'Registro nuevo', +'No databases in this application': 'No hay bases de datos en esta aplicación', +'Origin': 'Origen', +'Original/Translation': 'Original/Traducción', +'Password': 'Contraseña', +'Peeking at file': 'Visualizando archivo', +'Powered by': 'Este sitio usa', +'Query:': 'Consulta:', +'Record ID': 'ID de Registro', +'Register': 'Registrese', +'Registration key': 'Contraseña de Registro', +'Reset Password key': 'Reset Password key', +'Resolve Conflict file': 'archivo Resolución de Conflicto', +'Role': 'Rol', +'Rows in table': 'Filas en la tabla', +'Rows selected': 'Filas seleccionadas', +'Saved file hash:': 'Hash del archivo guardado:', +'Static files': 'Archivos estáticos', +'Stylesheet': 'Hoja de estilo', +'Sure you want to delete this object?': '¿Está seguro que desea eliminar este objeto?', +'Table name': 'Nombre de la tabla', +'Testing application': 'Probando aplicación', +'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'La "consulta" es una condición como "db.tabla1.campo1==\'valor\'". Algo como "db.tabla1.campo1==db.tabla2.campo2" resulta en un JOIN SQL.', +'The output of the file is a dictionary that was rendered by the view': 'La salida del archivo es un diccionario escenificado por la vista', +'There are no controllers': 'No hay controladores', +'There are no models': 'No hay modelos', +'There are no modules': 'No hay módulos', +'There are no static files': 'No hay archivos estáticos', +'There are no translators, only default language is supported': 'No hay traductores, sólo el lenguaje por defecto es soportado', +'There are no views': 'No hay vistas', +'This is a copy of the scaffolding application': 'Esta es una copia de la aplicación de andamiaje', +'This is the %(filename)s template': 'Esta es la plantilla %(filename)s', +'Ticket': 'Tiquete', +'Timestamp': 'Timestamp', +'Unable to check for upgrades': 'No es posible verificar la existencia de actualizaciones', +'Unable to download': 'No es posible la descarga', +'Unable to download app': 'No es posible descarga la aplicación', +'Update:': 'Actualice:', +'Upload existing application': 'Suba esta aplicación', +'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Use (...)&(...) para AND, (...)|(...) para OR, y ~(...) para NOT, para crear consultas más complejas.', +'User ID': 'ID de Usuario', +'View': 'Vista', +'Views': 'Vistas', +'Welcome': 'Welcome', +'Welcome %s': 'Bienvenido %s', +'Welcome to web2py': 'Bienvenido a web2py', +'Which called the function': 'La cual llamó la función', +'YES': 'SI', +'You are successfully running web2py': 'Usted está ejecutando web2py exitosamente', +'You can modify this application and adapt it to your needs': 'Usted puede modificar esta aplicación y adaptarla a sus necesidades', +'You visited the url': 'Usted visitó la url', +'about': 'acerca de', +'additional code for your application': 'código adicional para su aplicación', +'admin disabled because no admin password': ' por falta de contraseña', +'admin disabled because not supported on google app engine': 'admin deshabilitado, no es soportado en GAE', +'admin disabled because unable to access password file': 'admin deshabilitado, imposible acceder al archivo con la contraseña', +'and rename it (required):': 'y renombrela (requerido):', +'and rename it:': ' y renombrelo:', +'appadmin': 'appadmin', +'appadmin is disabled because insecure channel': 'admin deshabilitado, el canal no es seguro', +'application "%s" uninstalled': 'aplicación "%s" desinstalada', +'application compiled': 'aplicación compilada', +'application is compiled and cannot be designed': 'la aplicación está compilada y no puede ser modificada', +'cache': 'cache', +'cache, errors and sessions cleaned': 'cache, errores y sesiones eliminados', +'cannot create file': 'no es posible crear archivo', +'cannot upload file "%(filename)s"': 'no es posible subir archivo "%(filename)s"', +'change password': 'cambie contraseña', +'check all': 'marcar todos', +'clean': 'limpiar', +'Online examples': 'Ejemplos en línea', +'Administrative interface': 'Interfaz administrativa', +'click to check for upgrades': 'haga clic para buscar actualizaciones', +'compile': 'compilar', +'compiled application removed': 'aplicación compilada removida', +'controllers': 'controladores', +'create file with filename:': 'cree archivo con nombre:', +'create new application:': 'nombre de la nueva aplicación:', +'crontab': 'crontab', +'currently saved or': 'actualmente guardado o', +'customize me!': 'Adaptame!', +'data uploaded': 'datos subidos', +'database': 'base de datos', +'database %s select': 'selección en base de datos %s', +'database administration': 'administración base de datos', +'db': 'db', +'defines tables': 'define tablas', +'delete': 'eliminar', +'delete all checked': 'eliminar marcados', +'design': 'modificar', +'Documentation': 'Documentación', +'done!': 'listo!', +'edit': 'editar', +'edit controller': 'editar controlador', +'edit profile': 'editar perfil', +'errors': 'errores', +'export as csv file': 'exportar como archivo CSV', +'exposes': 'expone', +'extends': 'extiende', +'failed to reload module': 'recarga del módulo ha fallado', +'file "%(filename)s" created': 'archivo "%(filename)s" creado', +'file "%(filename)s" deleted': 'archivo "%(filename)s" eliminado', +'file "%(filename)s" uploaded': 'archivo "%(filename)s" subido', +'file "%(filename)s" was not deleted': 'archivo "%(filename)s" no fué eliminado', +'file "%s" of %s restored': 'archivo "%s" de %s restaurado', +'file changed on disk': 'archivo modificado en el disco', +'file does not exist': 'archivo no existe', +'file saved on %(time)s': 'archivo guardado %(time)s', +'file saved on %s': 'archivo guardado %s', +'help': 'ayuda', +'htmledit': 'htmledit', +'includes': 'incluye', +'insert new': 'inserte nuevo', +'insert new %s': 'inserte nuevo %s', +'internal error': 'error interno', +'invalid password': 'contraseña inválida', +'invalid request': 'solicitud inválida', +'invalid ticket': 'tiquete inválido', +'language file "%(filename)s" created/updated': 'archivo de lenguaje "%(filename)s" creado/actualizado', +'languages': 'lenguajes', +'languages updated': 'lenguajes actualizados', +'loading...': 'cargando...', +'located in the file': 'localizada en el archivo', +'login': 'inicio de sesión', +'logout': 'fin de sesión', +'lost password?': '¿olvido la contraseña?', +'merge': 'combinar', +'models': 'modelos', +'modules': 'módulos', +'new application "%s" created': 'nueva aplicación "%s" creada', +'new record inserted': 'nuevo registro insertado', +'next 100 rows': '100 filas siguientes', +'or import from csv file': 'o importar desde archivo CSV', +'or provide application url:': 'o provea URL de la aplicación:', +'pack all': 'empaquetar todo', +'pack compiled': 'empaquete compiladas', +'previous 100 rows': '100 filas anteriores', +'record': 'registro', +'record does not exist': 'el registro no existe', +'record id': 'id de registro', +'register': 'registrese', +'remove compiled': 'eliminar compiladas', +'restore': 'restaurar', +'revert': 'revertir', +'save': 'guardar', +'selected': 'seleccionado(s)', +'session expired': 'sesión expirada', +'shell': 'shell', +'site': 'sitio', +'some files could not be removed': 'algunos archivos no pudieron ser removidos', +'state': 'estado', +'static': 'estáticos', +'table': 'tabla', +'test': 'probar', +'the application logic, each URL path is mapped in one exposed function in the controller': 'la lógica de la aplicación, cada ruta URL se mapea en una función expuesta en el controlador', +'the data representation, define database tables and sets': 'la representación de datos, define tablas y conjuntos de base de datos', +'the presentations layer, views are also known as templates': 'la capa de presentación, las vistas también son llamadas plantillas', +'these files are served without processing, your images go here': 'estos archivos son servidos sin procesar, sus imágenes van aquí', +'to previous version.': 'a la versión previa.', +'translation strings for the application': 'cadenas de caracteres de traducción para la aplicación', +'try': 'intente', +'try something like': 'intente algo como', +'unable to create application "%s"': 'no es posible crear la aplicación "%s"', +'unable to delete file "%(filename)s"': 'no es posible eliminar el archivo "%(filename)s"', +'unable to parse csv file': 'no es posible analizar el archivo CSV', +'unable to uninstall "%s"': 'no es posible instalar "%s"', +'uncheck all': 'desmarcar todos', +'uninstall': 'desinstalar', +'update': 'actualizar', +'update all languages': 'actualizar todos los lenguajes', +'upload application:': 'subir aplicación:', +'upload file:': 'suba archivo:', +'versioning': 'versiones', +'view': 'vista', +'views': 'vistas', +'web2py Recent Tweets': 'Tweets Recientes de web2py', +'web2py is up to date': 'web2py está actualizado', +} ADDED applications/welcome/languages/fr-ca.py Index: applications/welcome/languages/fr-ca.py ================================================================== --- applications/welcome/languages/fr-ca.py +++ applications/welcome/languages/fr-ca.py @@ -0,0 +1,167 @@ +# coding: utf8 +{ +'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"update" est une expression optionnelle comme "champ1=\'nouvellevaleur\'". Vous ne pouvez mettre à jour ou supprimer les résultats d\'un JOIN', +'%Y-%m-%d': '%Y-%m-%d', +'%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S', +'%s rows deleted': '%s rangées supprimées', +'%s rows updated': '%s rangées mises à jour', +'About': 'À propos', +'Access Control': "Contrôle d'accès", +'Administrative interface': "Interface d'administration", +'Ajax Recipes': 'Recettes Ajax', +'Are you sure you want to delete this object?': 'Êtes-vous sûr de vouloir supprimer cet objet?', +'Authentication': 'Authentification', +'Available databases and tables': 'Bases de données et tables disponibles', +'Buy this book': 'Acheter ce livre', +'Cannot be empty': 'Ne peut pas être vide', +'Check to delete': 'Cliquez pour supprimer', +'Check to delete:': 'Cliquez pour supprimer:', +'Client IP': 'IP client', +'Community': 'Communauté', +'Controller': 'Contrôleur', +'Copyright': "Droit d'auteur", +'Current request': 'Demande actuelle', +'Current response': 'Réponse actuelle', +'Current session': 'Session en cours', +'DB Model': 'Modèle DB', +'Database': 'Base de données', +'Delete:': 'Supprimer:', +'Demo': 'Démo', +'Deployment Recipes': 'Recettes de déploiement ', +'Description': 'Descriptif', +'Documentation': 'Documentation', +'Download': 'Téléchargement', +'E-mail': 'Courriel', +'Edit': 'Éditer', +'Edit This App': 'Modifier cette application', +'Edit current record': "Modifier l'enregistrement courant", +'Errors': 'Erreurs', +'FAQ': 'faq', +'First name': 'Prénom', +'Forms and Validators': 'Formulaires et Validateurs', +'Free Applications': 'Applications gratuites', +'Function disabled': 'Fonction désactivée', +'Group %(group_id)s created': '%(group_id)s groupe créé', +'Group ID': 'Groupe ID', +'Group uniquely assigned to user %(id)s': "Groupe unique attribué à l'utilisateur %(id)s", +'Groups': 'Groupes', +'Hello World': 'Bonjour le monde', +'Home': 'Accueil', +'Import/Export': 'Importer/Exporter', +'Index': 'Index', +'Internal State': 'État interne', +'Introduction': 'Présentation', +'Invalid Query': 'Requête Invalide', +'Invalid email': 'Courriel invalide', +'Last name': 'Nom', +'Layout': 'Mise en page', +'Layouts': 'layouts', +'Live chat': 'Clavardage en direct', +'Logged in': 'Connecté', +'Login': 'Connectez-vous', +'Lost Password': 'Mot de passe perdu', +'Main Menu': 'Menu principal', +'Menu Model': 'Menu modèle', +'Name': 'Nom', +'New Record': 'Nouvel enregistrement', +'No databases in this application': "Cette application n'a pas de bases de données", +'Online examples': 'Exemples en ligne', +'Origin': 'Origine', +'Other Recipes': 'Autres recettes', +'Overview': 'Présentation', +'Password': 'Mot de passe', +"Password fields don't match": 'Les mots de passe ne correspondent pas', +'Plugins': 'Plugiciels', +'Powered by': 'Alimenté par', +'Preface': 'Préface', +'Python': 'Python', +'Query:': 'Requête:', +'Quick Examples': 'Examples Rapides', +'Readme': 'Lisez-moi', +'Recipes': 'Recettes', +'Record %(id)s created': 'Record %(id)s created', +'Record %(id)s updated': 'Record %(id)s updated', +'Record Created': 'Record Created', +'Record ID': "ID d'enregistrement", +'Record Updated': 'Record Updated', +'Register': "S'inscrire", +'Registration key': "Clé d'enregistrement", +'Registration successful': 'Inscription réussie', +'Remember me (for 30 days)': 'Se souvenir de moi (pendant 30 jours)', +'Request reset password': 'Demande de réinitialiser le mot clé', +'Reset Password key': 'Réinitialiser le mot clé', +'Resources': 'Ressources', +'Role': 'Rôle', +'Rows in table': 'Lignes du tableau', +'Rows selected': 'Lignes sélectionnées', +'Semantic': 'Sémantique', +'Services': 'Services', +'Stylesheet': 'Feuille de style', +'Submit': 'Soumettre', +'Support': 'Soutien', +'Sure you want to delete this object?': 'Êtes-vous sûr de vouloir supprimer cet objet?', +'Table name': 'Nom du tableau', +'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'La "query" est une condition comme "db.table1.champ1==\'valeur\'". Quelque chose comme "db.table1.champ1==db.table2.champ2" résulte en un JOIN SQL.', +'The Core': 'Le noyau', +'The Views': 'Les Vues', +'The output of the file is a dictionary that was rendered by the view': 'La sortie de ce fichier est un dictionnaire qui été restitué par la vue', +'This App': 'Cette Appli', +'This is a copy of the scaffolding application': "Ceci est une copie de l'application échafaudage", +'Timestamp': 'Horodatage', +'Twitter': 'Twitter', +'Update:': 'Mise à jour:', +'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Employez (...)&(...) pour AND, (...)|(...) pour OR, and ~(...) pour NOT pour construire des requêtes plus complexes.', +'User %(id)s Logged-in': 'Utilisateur %(id)s connecté', +'User %(id)s Registered': 'Utilisateur %(id)s enregistré', +'User ID': 'ID utilisateur', +'User Voice': 'User Voice', +'Verify Password': 'Vérifiez le mot de passe', +'Videos': 'Vidéos', +'View': 'Présentation', +'Web2py': 'Web2py', +'Welcome': 'Bienvenu', +'Welcome %s': 'Bienvenue %s', +'Welcome to web2py': 'Bienvenue à web2py', +'Which called the function': 'Qui a appelé la fonction', +'You are successfully running web2py': 'Vous roulez avec succès web2py', +'You can modify this application and adapt it to your needs': "Vous pouvez modifier cette application et l'adapter à vos besoins", +'You visited the url': "Vous avez visité l'URL", +'about': 'à propos', +'appadmin is disabled because insecure channel': "appadmin est désactivée parce que le canal n'est pas sécurisé", +'cache': 'cache', +'change password': 'changer le mot de passe', +'customize me!': 'personnalisez-moi!', +'data uploaded': 'données téléchargées', +'database': 'base de données', +'database %s select': 'base de données %s select', +'db': 'db', +'design': 'design', +'done!': 'fait!', +'edit profile': 'modifier le profil', +'enter an integer between %(min)g and %(max)g': 'entrer un entier compris entre %(min)g et %(max)g', +'export as csv file': 'exporter sous forme de fichier csv', +'insert new': 'insérer un nouveau', +'insert new %s': 'insérer un nouveau %s', +'invalid request': 'requête invalide', +'located in the file': 'se trouvant dans le fichier', +'login': 'connectez-vous', +'logout': 'déconnectez-vous', +'lost password': 'mot de passe perdu', +'lost password?': 'mot de passe perdu?', +'new record inserted': 'nouvel enregistrement inséré', +'next 100 rows': '100 prochaines lignes', +'or import from csv file': "ou importer d'un fichier CSV", +'password': 'mot de passe', +'please input your password again': "S'il vous plaît entrer votre mot de passe", +'previous 100 rows': '100 lignes précédentes', +'profile': 'profile', +'record': 'enregistrement', +'record does not exist': "l'archive n'existe pas", +'record id': "id d'enregistrement", +'register': "s'inscrire", +'selected': 'sélectionné', +'state': 'état', +'table': 'tableau', +'unable to parse csv file': "incapable d'analyser le fichier cvs", +'value already in database or empty': 'valeur déjà dans la base ou vide', +} ADDED applications/welcome/languages/fr-fr.py Index: applications/welcome/languages/fr-fr.py ================================================================== --- applications/welcome/languages/fr-fr.py +++ applications/welcome/languages/fr-fr.py @@ -0,0 +1,155 @@ +# coding: utf8 +{ +'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"update" est une expression optionnelle comme "champ1=\'nouvellevaleur\'". Vous ne pouvez mettre à jour ou supprimer les résultats d\'un JOIN', +'%Y-%m-%d': '%Y-%m-%d', +'%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S', +'%s rows deleted': '%s rangées supprimées', +'%s rows updated': '%s rangées mises à jour', +'About': 'À propos', +'Access Control': 'Contrôle d\'accès', +'Ajax Recipes': 'Recettes Ajax', +'Are you sure you want to delete this object?': 'Êtes-vous sûr de vouloir supprimer cet objet?', +'Authentication': 'Authentification', +'Available databases and tables': 'Bases de données et tables disponibles', +'Buy this book': 'Acheter ce livre', +'Cannot be empty': 'Ne peut pas être vide', +'Check to delete': 'Cliquez pour supprimer', +'Check to delete:': 'Cliquez pour supprimer:', +'Client IP': 'IP client', +'Community': 'Communauté', +'Controller': 'Contrôleur', +'Copyright': 'Copyright', +'Current request': 'Demande actuelle', +'Current response': 'Réponse actuelle', +'Current session': 'Session en cours', +'DB Model': 'Modèle DB', +'Database': 'Base de données', +'Delete:': 'Supprimer:', +'Demo': 'Démo', +'Deployment Recipes': 'Recettes de déploiement', +'Description': 'Description', +'Documentation': 'Documentation', +'Download': 'Téléchargement', +'E-mail': 'E-mail', +'Edit': 'Éditer', +'Edit This App': 'Modifier cette application', +'Edit current record': "Modifier l'enregistrement courant", +'Errors': 'Erreurs', +'FAQ': 'FAQ', +'First name': 'Prénom', +'Forms and Validators': 'Formulaires et Validateurs', +'Free Applications': 'Applications gratuites', +'Function disabled': 'Fonction désactivée', +'Group ID': 'Groupe ID', +'Groups': 'Groups', +'Hello World': 'Bonjour le monde', +'Home': 'Accueil', +'Import/Export': 'Importer/Exporter', +'Index': 'Index', +'Internal State': 'État interne', +'Introduction': 'Introduction', +'Invalid Query': 'Requête Invalide', +'Invalid email': 'E-mail invalide', +'Last name': 'Nom', +'Layout': 'Mise en page', +'Layouts': 'Layouts', +'Live chat': 'Chat live', +'Login': 'Connectez-vous', +'Lost Password': 'Mot de passe perdu', +'Main Menu': 'Menu principal', +'Menu Model': 'Menu modèle', +'Name': 'Nom', +'New Record': 'Nouvel enregistrement', +'No databases in this application': "Cette application n'a pas de bases de données", +'Origin': 'Origine', +'Other Recipes': 'Autres recettes', +'Overview': 'Présentation', +'Password': 'Mot de passe', +"Password fields don't match": 'Les mots de passe ne correspondent pas', +'Plugins': 'Plugiciels', +'Powered by': 'Alimenté par', +'Preface': 'Préface', +'Python': 'Python', +'Query:': 'Requête:', +'Quick Examples': 'Examples Rapides', +'Recipes': 'Recettes', +'Record ID': 'ID d\'enregistrement', +'Register': "S'inscrire", +'Registration key': "Clé d'enregistrement", +'Remember me (for 30 days)': 'Se souvenir de moi (pendant 30 jours)', +'Request reset password': 'Demande de réinitialiser le mot clé', +'Reset Password key': 'Réinitialiser le mot clé', +'Resources': 'Ressources', +'Role': 'Rôle', +'Rows in table': 'Lignes du tableau', +'Rows selected': 'Lignes sélectionnées', +'Semantic': 'Sémantique', +'Services': 'Services', +'Stylesheet': 'Feuille de style', +'Submit': 'Soumettre', +'Support': 'Support', +'Sure you want to delete this object?': 'Êtes-vous sûr de vouloir supprimer cet objet?', +'Table name': 'Nom du tableau', +'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'La "query" est une condition comme "db.table1.champ1==\'valeur\'". Quelque chose comme "db.table1.champ1==db.table2.champ2" résulte en un JOIN SQL.', +'The Core': 'Le noyau', +'The Views': 'Les Vues', +'The output of the file is a dictionary that was rendered by the view': 'La sortie de ce fichier est un dictionnaire qui été restitué par la vue', +'This App': 'Cette Appli', +'This is a copy of the scaffolding application': 'Ceci est une copie de l\'application échafaudage', +'Timestamp': 'Horodatage', +'Twitter': 'Twitter', +'Update:': 'Mise à jour:', +'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Employez (...)&(...) pour AND, (...)|(...) pour OR, and ~(...) pour NOT pour construire des requêtes plus complexes.', +'User %(id)s Logged-in': 'Utilisateur %(id)s connecté', +'User %(id)s Registered': 'Utilisateur %(id)s enregistré', +'User ID': 'ID utilisateur', +'User Voice': 'User Voice', +'Verify Password': 'Vérifiez le mot de passe', +'Videos': 'Vidéos', +'View': 'Présentation', +'Web2py': 'Web2py', +'Welcome': 'Bienvenu', +'Welcome %s': 'Bienvenue %s', +'Welcome to web2py': 'Bienvenue à web2py', +'Which called the function': 'Qui a appelé la fonction', +'You are successfully running web2py': 'Vous roulez avec succès web2py', +'You can modify this application and adapt it to your needs': 'Vous pouvez modifier cette application et l\'adapter à vos besoins', +'You visited the url': 'Vous avez visité l\'URL', +'appadmin is disabled because insecure channel': "appadmin est désactivée parce que le canal n'est pas sécurisé", +'cache': 'cache', +'change password': 'changer le mot de passe', +'Online examples': 'Exemples en ligne', +'Administrative interface': "Interface d'administration", +'customize me!': 'personnalisez-moi!', +'data uploaded': 'données téléchargées', +'database': 'base de données', +'database %s select': 'base de données %s select', +'db': 'db', +'design': 'design', +'Documentation': 'Documentation', +'done!': 'fait!', +'edit profile': 'modifier le profil', +'enter an integer between %(min)g and %(max)g': 'enter an integer between %(min)g and %(max)g', +'export as csv file': 'exporter sous forme de fichier csv', +'insert new': 'insérer un nouveau', +'insert new %s': 'insérer un nouveau %s', +'invalid request': 'requête invalide', +'located in the file': 'se trouvant dans le fichier', +'login': 'connectez-vous', +'logout': 'déconnectez-vous', +'lost password': 'mot de passe perdu', +'lost password?': 'mot de passe perdu?', +'new record inserted': 'nouvel enregistrement inséré', +'next 100 rows': '100 prochaines lignes', +'or import from csv file': "ou importer d'un fichier CSV", +'previous 100 rows': '100 lignes précédentes', +'record': 'enregistrement', +'record does not exist': "l'archive n'existe pas", +'record id': "id d'enregistrement", +'register': "s'inscrire", +'selected': 'sélectionné', +'state': 'état', +'table': 'tableau', +'unable to parse csv file': "incapable d'analyser le fichier cvs", +'Readme': "Lisez-moi", +} ADDED applications/welcome/languages/hi-hi.py Index: applications/welcome/languages/hi-hi.py ================================================================== --- applications/welcome/languages/hi-hi.py +++ applications/welcome/languages/hi-hi.py @@ -0,0 +1,82 @@ +# coding: utf8 +{ +'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN', +'%Y-%m-%d': '%Y-%m-%d', +'%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S', +'%s rows deleted': '%s \xe0\xa4\xaa\xe0\xa4\x82\xe0\xa4\x95\xe0\xa5\x8d\xe0\xa4\xa4\xe0\xa4\xbf\xe0\xa4\xaf\xe0\xa4\xbe\xe0\xa4\x81 \xe0\xa4\xae\xe0\xa4\xbf\xe0\xa4\x9f\xe0\xa4\xbe\xe0\xa4\x8f\xe0\xa4\x81', +'%s rows updated': '%s \xe0\xa4\xaa\xe0\xa4\x82\xe0\xa4\x95\xe0\xa5\x8d\xe0\xa4\xa4\xe0\xa4\xbf\xe0\xa4\xaf\xe0\xa4\xbe\xe0\xa4\x81 \xe0\xa4\x85\xe0\xa4\xa6\xe0\xa5\x8d\xe0\xa4\xaf\xe0\xa4\xa4\xe0\xa4\xa8', +'Available databases and tables': '\xe0\xa4\x89\xe0\xa4\xaa\xe0\xa4\xb2\xe0\xa4\xac\xe0\xa5\x8d\xe0\xa4\xa7 \xe0\xa4\xa1\xe0\xa5\x87\xe0\xa4\x9f\xe0\xa4\xbe\xe0\xa4\xac\xe0\xa5\x87\xe0\xa4\xb8 \xe0\xa4\x94\xe0\xa4\xb0 \xe0\xa4\xa4\xe0\xa4\xbe\xe0\xa4\xb2\xe0\xa4\xbf\xe0\xa4\x95\xe0\xa4\xbe', +'Cannot be empty': '\xe0\xa4\x96\xe0\xa4\xbe\xe0\xa4\xb2\xe0\xa5\x80 \xe0\xa4\xa8\xe0\xa4\xb9\xe0\xa5\x80\xe0\xa4\x82 \xe0\xa4\xb9\xe0\xa5\x8b \xe0\xa4\xb8\xe0\xa4\x95\xe0\xa4\xa4\xe0\xa4\xbe', +'Change Password': '\xe0\xa4\xaa\xe0\xa4\xbe\xe0\xa4\xb8\xe0\xa4\xb5\xe0\xa4\xb0\xe0\xa5\x8d\xe0\xa4\xa1 \xe0\xa4\xac\xe0\xa4\xa6\xe0\xa4\xb2\xe0\xa5\x87\xe0\xa4\x82', +'Check to delete': '\xe0\xa4\xb9\xe0\xa4\x9f\xe0\xa4\xbe\xe0\xa4\xa8\xe0\xa5\x87 \xe0\xa4\x95\xe0\xa5\x87 \xe0\xa4\xb2\xe0\xa4\xbf\xe0\xa4\x8f \xe0\xa4\x9a\xe0\xa5\x81\xe0\xa4\xa8\xe0\xa5\x87\xe0\xa4\x82', +'Controller': 'Controller', +'Copyright': 'Copyright', +'Current request': '\xe0\xa4\xb5\xe0\xa4\xb0\xe0\xa5\x8d\xe0\xa4\xa4\xe0\xa4\xae\xe0\xa4\xbe\xe0\xa4\xa8 \xe0\xa4\x85\xe0\xa4\xa8\xe0\xa5\x81\xe0\xa4\xb0\xe0\xa5\x8b\xe0\xa4\xa7', +'Current response': '\xe0\xa4\xb5\xe0\xa4\xb0\xe0\xa5\x8d\xe0\xa4\xa4\xe0\xa4\xae\xe0\xa4\xbe\xe0\xa4\xa8 \xe0\xa4\xaa\xe0\xa5\x8d\xe0\xa4\xb0\xe0\xa4\xa4\xe0\xa4\xbf\xe0\xa4\x95\xe0\xa5\x8d\xe0\xa4\xb0\xe0\xa4\xbf\xe0\xa4\xaf\xe0\xa4\xbe', +'Current session': '\xe0\xa4\xb5\xe0\xa4\xb0\xe0\xa5\x8d\xe0\xa4\xa4\xe0\xa4\xae\xe0\xa4\xbe\xe0\xa4\xa8 \xe0\xa4\xb8\xe0\xa5\x87\xe0\xa4\xb6\xe0\xa4\xa8', +'DB Model': 'DB Model', +'Database': 'Database', +'Delete:': '\xe0\xa4\xae\xe0\xa4\xbf\xe0\xa4\x9f\xe0\xa4\xbe\xe0\xa4\xa8\xe0\xa4\xbe:', +'Edit': 'Edit', +'Edit Profile': '\xe0\xa4\xaa\xe0\xa5\x8d\xe0\xa4\xb0\xe0\xa5\x8b\xe0\xa4\xab\xe0\xa4\xbc\xe0\xa4\xbe\xe0\xa4\x87\xe0\xa4\xb2 \xe0\xa4\xb8\xe0\xa4\x82\xe0\xa4\xaa\xe0\xa4\xbe\xe0\xa4\xa6\xe0\xa4\xbf\xe0\xa4\xa4 \xe0\xa4\x95\xe0\xa4\xb0\xe0\xa5\x87\xe0\xa4\x82', +'Edit This App': 'Edit This App', +'Edit current record': '\xe0\xa4\xb5\xe0\xa4\xb0\xe0\xa5\x8d\xe0\xa4\xa4\xe0\xa4\xae\xe0\xa4\xbe\xe0\xa4\xa8 \xe0\xa4\xb0\xe0\xa5\x87\xe0\xa4\x95\xe0\xa5\x89\xe0\xa4\xb0\xe0\xa5\x8d\xe0\xa4\xa1 \xe0\xa4\xb8\xe0\xa4\x82\xe0\xa4\xaa\xe0\xa4\xbe\xe0\xa4\xa6\xe0\xa4\xbf\xe0\xa4\xa4 \xe0\xa4\x95\xe0\xa4\xb0\xe0\xa5\x87\xe0\xa4\x82 ', +'Hello World': 'Hello World', +'Hello from MyApp': 'Hello from MyApp', +'Import/Export': '\xe0\xa4\x86\xe0\xa4\xaf\xe0\xa4\xbe\xe0\xa4\xa4 / \xe0\xa4\xa8\xe0\xa4\xbf\xe0\xa4\xb0\xe0\xa5\x8d\xe0\xa4\xaf\xe0\xa4\xbe\xe0\xa4\xa4', +'Index': 'Index', +'Internal State': '\xe0\xa4\x86\xe0\xa4\x82\xe0\xa4\xa4\xe0\xa4\xb0\xe0\xa4\xbf\xe0\xa4\x95 \xe0\xa4\xb8\xe0\xa5\x8d\xe0\xa4\xa5\xe0\xa4\xbf\xe0\xa4\xa4\xe0\xa4\xbf', +'Invalid Query': '\xe0\xa4\x85\xe0\xa4\xae\xe0\xa4\xbe\xe0\xa4\xa8\xe0\xa5\x8d\xe0\xa4\xaf \xe0\xa4\xaa\xe0\xa5\x8d\xe0\xa4\xb0\xe0\xa4\xb6\xe0\xa5\x8d\xe0\xa4\xa8', +'Layout': 'Layout', +'Login': '\xe0\xa4\xb2\xe0\xa5\x89\xe0\xa4\x97 \xe0\xa4\x87\xe0\xa4\xa8', +'Logout': '\xe0\xa4\xb2\xe0\xa5\x89\xe0\xa4\x97 \xe0\xa4\x86\xe0\xa4\x89\xe0\xa4\x9f', +'Lost Password': '\xe0\xa4\xaa\xe0\xa4\xbe\xe0\xa4\xb8\xe0\xa4\xb5\xe0\xa4\xb0\xe0\xa5\x8d\xe0\xa4\xa1 \xe0\xa4\x96\xe0\xa5\x8b \xe0\xa4\x97\xe0\xa4\xaf\xe0\xa4\xbe', +'Main Menu': 'Main Menu', +'Menu Model': 'Menu Model', +'New Record': '\xe0\xa4\xa8\xe0\xa4\xaf\xe0\xa4\xbe \xe0\xa4\xb0\xe0\xa5\x87\xe0\xa4\x95\xe0\xa5\x89\xe0\xa4\xb0\xe0\xa5\x8d\xe0\xa4\xa1', +'No databases in this application': '\xe0\xa4\x87\xe0\xa4\xb8 \xe0\xa4\x85\xe0\xa4\xa8\xe0\xa5\x81\xe0\xa4\xaa\xe0\xa5\x8d\xe0\xa4\xb0\xe0\xa4\xaf\xe0\xa5\x8b\xe0\xa4\x97 \xe0\xa4\xae\xe0\xa5\x87\xe0\xa4\x82 \xe0\xa4\x95\xe0\xa5\x8b\xe0\xa4\x88 \xe0\xa4\xa1\xe0\xa5\x87\xe0\xa4\x9f\xe0\xa4\xbe\xe0\xa4\xac\xe0\xa5\x87\xe0\xa4\xb8 \xe0\xa4\xa8\xe0\xa4\xb9\xe0\xa5\x80\xe0\xa4\x82 \xe0\xa4\xb9\xe0\xa5\x88\xe0\xa4\x82', +'Powered by': 'Powered by', +'Query:': '\xe0\xa4\xaa\xe0\xa5\x8d\xe0\xa4\xb0\xe0\xa4\xb6\xe0\xa5\x8d\xe0\xa4\xa8:', +'Register': '\xe0\xa4\xaa\xe0\xa4\x82\xe0\xa4\x9c\xe0\xa5\x80\xe0\xa4\x95\xe0\xa5\x83\xe0\xa4\xa4 (\xe0\xa4\xb0\xe0\xa4\x9c\xe0\xa4\xbf\xe0\xa4\xb8\xe0\xa5\x8d\xe0\xa4\x9f\xe0\xa4\xb0) \xe0\xa4\x95\xe0\xa4\xb0\xe0\xa4\xa8\xe0\xa4\xbe ', +'Rows in table': '\xe0\xa4\xa4\xe0\xa4\xbe\xe0\xa4\xb2\xe0\xa4\xbf\xe0\xa4\x95\xe0\xa4\xbe \xe0\xa4\xae\xe0\xa5\x87\xe0\xa4\x82 \xe0\xa4\xaa\xe0\xa4\x82\xe0\xa4\x95\xe0\xa5\x8d\xe0\xa4\xa4\xe0\xa4\xbf\xe0\xa4\xaf\xe0\xa4\xbe\xe0\xa4\x81 ', +'Rows selected': '\xe0\xa4\x9a\xe0\xa4\xaf\xe0\xa4\xa8\xe0\xa4\xbf\xe0\xa4\xa4 (\xe0\xa4\x9a\xe0\xa5\x81\xe0\xa4\xa8\xe0\xa5\x87 \xe0\xa4\x97\xe0\xa4\xaf\xe0\xa5\x87) \xe0\xa4\xaa\xe0\xa4\x82\xe0\xa4\x95\xe0\xa5\x8d\xe0\xa4\xa4\xe0\xa4\xbf\xe0\xa4\xaf\xe0\xa4\xbe\xe0\xa4\x81 ', +'Stylesheet': 'Stylesheet', +'Sure you want to delete this object?': '\xe0\xa4\xb8\xe0\xa5\x81\xe0\xa4\xa8\xe0\xa4\xbf\xe0\xa4\xb6\xe0\xa5\x8d\xe0\xa4\x9a\xe0\xa4\xbf\xe0\xa4\xa4 \xe0\xa4\xb9\xe0\xa5\x88\xe0\xa4\x82 \xe0\xa4\x95\xe0\xa4\xbf \xe0\xa4\x86\xe0\xa4\xaa \xe0\xa4\x87\xe0\xa4\xb8 \xe0\xa4\xb5\xe0\xa4\xb8\xe0\xa5\x8d\xe0\xa4\xa4\xe0\xa5\x81 \xe0\xa4\x95\xe0\xa5\x8b \xe0\xa4\xb9\xe0\xa4\x9f\xe0\xa4\xbe\xe0\xa4\xa8\xe0\xa4\xbe \xe0\xa4\x9a\xe0\xa4\xbe\xe0\xa4\xb9\xe0\xa4\xa4\xe0\xa5\x87 \xe0\xa4\xb9\xe0\xa5\x88\xe0\xa4\x82?', +'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.', +'Update:': '\xe0\xa4\x85\xe0\xa4\xa6\xe0\xa5\x8d\xe0\xa4\xaf\xe0\xa4\xa4\xe0\xa4\xa8 \xe0\xa4\x95\xe0\xa4\xb0\xe0\xa4\xa8\xe0\xa4\xbe:', +'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.', +'View': 'View', +'Welcome %s': 'Welcome %s', +'Welcome to web2py': '\xe0\xa4\xb5\xe0\xa5\x87\xe0\xa4\xac\xe0\xa5\xa8\xe0\xa4\xaa\xe0\xa4\xbe\xe0\xa4\x87 (web2py) \xe0\xa4\xae\xe0\xa5\x87\xe0\xa4\x82 \xe0\xa4\x86\xe0\xa4\xaa\xe0\xa4\x95\xe0\xa4\xbe \xe0\xa4\xb8\xe0\xa5\x8d\xe0\xa4\xb5\xe0\xa4\xbe\xe0\xa4\x97\xe0\xa4\xa4 \xe0\xa4\xb9\xe0\xa5\x88', +'appadmin is disabled because insecure channel': '\xe0\xa4\x85\xe0\xa4\xaa \xe0\xa4\x86\xe0\xa4\xa1\xe0\xa4\xae\xe0\xa4\xbf\xe0\xa4\xa8 (appadmin) \xe0\xa4\x85\xe0\xa4\x95\xe0\xa5\x8d\xe0\xa4\xb7\xe0\xa4\xae \xe0\xa4\xb9\xe0\xa5\x88 \xe0\xa4\x95\xe0\xa5\x8d\xe0\xa4\xaf\xe0\xa5\x8b\xe0\xa4\x82\xe0\xa4\x95\xe0\xa4\xbf \xe0\xa4\x85\xe0\xa4\xb8\xe0\xa5\x81\xe0\xa4\xb0\xe0\xa4\x95\xe0\xa5\x8d\xe0\xa4\xb7\xe0\xa4\xbf\xe0\xa4\xa4 \xe0\xa4\x9a\xe0\xa5\x88\xe0\xa4\xa8\xe0\xa4\xb2', +'cache': 'cache', +'change password': 'change password', +'Online examples': '\xe0\xa4\x91\xe0\xa4\xa8\xe0\xa4\xb2\xe0\xa4\xbe\xe0\xa4\x87\xe0\xa4\xa8 \xe0\xa4\x89\xe0\xa4\xa6\xe0\xa4\xbe\xe0\xa4\xb9\xe0\xa4\xb0\xe0\xa4\xa3 \xe0\xa4\x95\xe0\xa5\x87 \xe0\xa4\xb2\xe0\xa4\xbf\xe0\xa4\x8f \xe0\xa4\xaf\xe0\xa4\xb9\xe0\xa4\xbe\xe0\xa4\x81 \xe0\xa4\x95\xe0\xa5\x8d\xe0\xa4\xb2\xe0\xa4\xbf\xe0\xa4\x95 \xe0\xa4\x95\xe0\xa4\xb0\xe0\xa5\x87\xe0\xa4\x82', +'Administrative interface': '\xe0\xa4\xaa\xe0\xa5\x8d\xe0\xa4\xb0\xe0\xa4\xb6\xe0\xa4\xbe\xe0\xa4\xb8\xe0\xa4\xa8\xe0\xa4\xbf\xe0\xa4\x95 \xe0\xa4\x87\xe0\xa4\x82\xe0\xa4\x9f\xe0\xa4\xb0\xe0\xa4\xab\xe0\xa5\x87\xe0\xa4\xb8 \xe0\xa4\x95\xe0\xa5\x87 \xe0\xa4\xb2\xe0\xa4\xbf\xe0\xa4\x8f \xe0\xa4\xaf\xe0\xa4\xb9\xe0\xa4\xbe\xe0\xa4\x81 \xe0\xa4\x95\xe0\xa5\x8d\xe0\xa4\xb2\xe0\xa4\xbf\xe0\xa4\x95 \xe0\xa4\x95\xe0\xa4\xb0\xe0\xa5\x87\xe0\xa4\x82', +'customize me!': '\xe0\xa4\xae\xe0\xa5\x81\xe0\xa4\x9d\xe0\xa5\x87 \xe0\xa4\x85\xe0\xa4\xa8\xe0\xa5\x81\xe0\xa4\x95\xe0\xa5\x82\xe0\xa4\xb2\xe0\xa4\xbf\xe0\xa4\xa4 (\xe0\xa4\x95\xe0\xa4\xb8\xe0\xa5\x8d\xe0\xa4\x9f\xe0\xa4\xae\xe0\xa4\xbe\xe0\xa4\x87\xe0\xa4\x9c\xe0\xa4\xbc) \xe0\xa4\x95\xe0\xa4\xb0\xe0\xa5\x87\xe0\xa4\x82!', +'data uploaded': '\xe0\xa4\xa1\xe0\xa4\xbe\xe0\xa4\x9f\xe0\xa4\xbe \xe0\xa4\x85\xe0\xa4\xaa\xe0\xa4\xb2\xe0\xa5\x8b\xe0\xa4\xa1 \xe0\xa4\xb8\xe0\xa4\xae\xe0\xa5\x8d\xe0\xa4\xaa\xe0\xa4\xa8\xe0\xa5\x8d\xe0\xa4\xa8 ', +'database': '\xe0\xa4\xa1\xe0\xa5\x87\xe0\xa4\x9f\xe0\xa4\xbe\xe0\xa4\xac\xe0\xa5\x87\xe0\xa4\xb8', +'database %s select': '\xe0\xa4\xa1\xe0\xa5\x87\xe0\xa4\x9f\xe0\xa4\xbe\xe0\xa4\xac\xe0\xa5\x87\xe0\xa4\xb8 %s \xe0\xa4\x9a\xe0\xa5\x81\xe0\xa4\xa8\xe0\xa5\x80 \xe0\xa4\xb9\xe0\xa5\x81\xe0\xa4\x88', +'db': 'db', +'design': '\xe0\xa4\xb0\xe0\xa4\x9a\xe0\xa4\xa8\xe0\xa4\xbe \xe0\xa4\x95\xe0\xa4\xb0\xe0\xa5\x87\xe0\xa4\x82', +'done!': '\xe0\xa4\xb9\xe0\xa5\x8b \xe0\xa4\x97\xe0\xa4\xaf\xe0\xa4\xbe!', +'edit profile': 'edit profile', +'export as csv file': 'csv \xe0\xa4\xab\xe0\xa4\xbc\xe0\xa4\xbe\xe0\xa4\x87\xe0\xa4\xb2 \xe0\xa4\x95\xe0\xa5\x87 \xe0\xa4\xb0\xe0\xa5\x82\xe0\xa4\xaa \xe0\xa4\xae\xe0\xa5\x87\xe0\xa4\x82 \xe0\xa4\xa8\xe0\xa4\xbf\xe0\xa4\xb0\xe0\xa5\x8d\xe0\xa4\xaf\xe0\xa4\xbe\xe0\xa4\xa4', +'insert new': '\xe0\xa4\xa8\xe0\xa4\xaf\xe0\xa4\xbe \xe0\xa4\xa1\xe0\xa4\xbe\xe0\xa4\xb2\xe0\xa5\x87\xe0\xa4\x82', +'insert new %s': '\xe0\xa4\xa8\xe0\xa4\xaf\xe0\xa4\xbe %s \xe0\xa4\xa1\xe0\xa4\xbe\xe0\xa4\xb2\xe0\xa5\x87\xe0\xa4\x82', +'invalid request': '\xe0\xa4\x85\xe0\xa4\xb5\xe0\xa5\x88\xe0\xa4\xa7 \xe0\xa4\x85\xe0\xa4\xa8\xe0\xa5\x81\xe0\xa4\xb0\xe0\xa5\x8b\xe0\xa4\xa7', +'login': 'login', +'logout': 'logout', +'new record inserted': '\xe0\xa4\xa8\xe0\xa4\xaf\xe0\xa4\xbe \xe0\xa4\xb0\xe0\xa5\x87\xe0\xa4\x95\xe0\xa5\x89\xe0\xa4\xb0\xe0\xa5\x8d\xe0\xa4\xa1 \xe0\xa4\xa1\xe0\xa4\xbe\xe0\xa4\xb2\xe0\xa4\xbe', +'next 100 rows': '\xe0\xa4\x85\xe0\xa4\x97\xe0\xa4\xb2\xe0\xa5\x87 100 \xe0\xa4\xaa\xe0\xa4\x82\xe0\xa4\x95\xe0\xa5\x8d\xe0\xa4\xa4\xe0\xa4\xbf\xe0\xa4\xaf\xe0\xa4\xbe\xe0\xa4\x81', +'or import from csv file': '\xe0\xa4\xaf\xe0\xa4\xbe csv \xe0\xa4\xab\xe0\xa4\xbc\xe0\xa4\xbe\xe0\xa4\x87\xe0\xa4\xb2 \xe0\xa4\xb8\xe0\xa5\x87 \xe0\xa4\x86\xe0\xa4\xaf\xe0\xa4\xbe\xe0\xa4\xa4', +'previous 100 rows': '\xe0\xa4\xaa\xe0\xa4\xbf\xe0\xa4\x9b\xe0\xa4\xb2\xe0\xa5\x87 100 \xe0\xa4\xaa\xe0\xa4\x82\xe0\xa4\x95\xe0\xa5\x8d\xe0\xa4\xa4\xe0\xa4\xbf\xe0\xa4\xaf\xe0\xa4\xbe\xe0\xa4\x81', +'record': 'record', +'record does not exist': '\xe0\xa4\xb0\xe0\xa4\xbf\xe0\xa4\x95\xe0\xa5\x89\xe0\xa4\xb0\xe0\xa5\x8d\xe0\xa4\xa1 \xe0\xa4\xae\xe0\xa5\x8c\xe0\xa4\x9c\xe0\xa5\x82\xe0\xa4\xa6 \xe0\xa4\xa8\xe0\xa4\xb9\xe0\xa5\x80\xe0\xa4\x82 \xe0\xa4\xb9\xe0\xa5\x88', +'record id': '\xe0\xa4\xb0\xe0\xa4\xbf\xe0\xa4\x95\xe0\xa5\x89\xe0\xa4\xb0\xe0\xa5\x8d\xe0\xa4\xa1 \xe0\xa4\xaa\xe0\xa4\xb9\xe0\xa4\x9a\xe0\xa4\xbe\xe0\xa4\xa8\xe0\xa4\x95\xe0\xa4\xb0\xe0\xa5\x8d\xe0\xa4\xa4\xe0\xa4\xbe (\xe0\xa4\x86\xe0\xa4\x88\xe0\xa4\xa1\xe0\xa5\x80)', +'register': 'register', +'selected': '\xe0\xa4\x9a\xe0\xa5\x81\xe0\xa4\xa8\xe0\xa4\xbe \xe0\xa4\xb9\xe0\xa5\x81\xe0\xa4\x86', +'state': '\xe0\xa4\xb8\xe0\xa5\x8d\xe0\xa4\xa5\xe0\xa4\xbf\xe0\xa4\xa4\xe0\xa4\xbf', +'table': '\xe0\xa4\xa4\xe0\xa4\xbe\xe0\xa4\xb2\xe0\xa4\xbf\xe0\xa4\x95\xe0\xa4\xbe', +'unable to parse csv file': 'csv \xe0\xa4\xab\xe0\xa4\xbc\xe0\xa4\xbe\xe0\xa4\x87\xe0\xa4\xb2 \xe0\xa4\xaa\xe0\xa4\xbe\xe0\xa4\xb0\xe0\xa5\x8d\xe0\xa4\xb8 \xe0\xa4\x95\xe0\xa4\xb0\xe0\xa4\xa8\xe0\xa5\x87 \xe0\xa4\xae\xe0\xa5\x87\xe0\xa4\x82 \xe0\xa4\x85\xe0\xa4\xb8\xe0\xa4\xae\xe0\xa4\xb0\xe0\xa5\x8d\xe0\xa4\xa5', +} ADDED applications/welcome/languages/hu-hu.py Index: applications/welcome/languages/hu-hu.py ================================================================== --- applications/welcome/languages/hu-hu.py +++ applications/welcome/languages/hu-hu.py @@ -0,0 +1,93 @@ +# coding: utf8 +{ +'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN', +'%Y-%m-%d': '%Y.%m.%d.', +'%Y-%m-%d %H:%M:%S': '%Y.%m.%d. %H:%M:%S', +'%s rows deleted': '%s sorok t\xc3\xb6rl\xc5\x91dtek', +'%s rows updated': '%s sorok friss\xc3\xadt\xc5\x91dtek', +'Available databases and tables': 'El\xc3\xa9rhet\xc5\x91 adatb\xc3\xa1zisok \xc3\xa9s t\xc3\xa1bl\xc3\xa1k', +'Cannot be empty': 'Nem lehet \xc3\xbcres', +'Check to delete': 'T\xc3\xb6rl\xc3\xa9shez v\xc3\xa1laszd ki', +'Client IP': 'Client IP', +'Controller': 'Controller', +'Copyright': 'Copyright', +'Current request': 'Jelenlegi lek\xc3\xa9rdez\xc3\xa9s', +'Current response': 'Jelenlegi v\xc3\xa1lasz', +'Current session': 'Jelenlegi folyamat', +'DB Model': 'DB Model', +'Database': 'Adatb\xc3\xa1zis', +'Delete:': 'T\xc3\xb6r\xc3\xb6l:', +'Description': 'Description', +'E-mail': 'E-mail', +'Edit': 'Szerkeszt', +'Edit This App': 'Alkalmaz\xc3\xa1st szerkeszt', +'Edit current record': 'Aktu\xc3\xa1lis bejegyz\xc3\xa9s szerkeszt\xc3\xa9se', +'First name': 'First name', +'Group ID': 'Group ID', +'Hello World': 'Hello Vil\xc3\xa1g', +'Import/Export': 'Import/Export', +'Index': 'Index', +'Internal State': 'Internal State', +'Invalid Query': 'Hib\xc3\xa1s lek\xc3\xa9rdez\xc3\xa9s', +'Invalid email': 'Invalid email', +'Last name': 'Last name', +'Layout': 'Szerkezet', +'Main Menu': 'F\xc5\x91men\xc3\xbc', +'Menu Model': 'Men\xc3\xbc model', +'Name': 'Name', +'New Record': '\xc3\x9aj bejegyz\xc3\xa9s', +'No databases in this application': 'Nincs adatb\xc3\xa1zis ebben az alkalmaz\xc3\xa1sban', +'Origin': 'Origin', +'Password': 'Password', +'Powered by': 'Powered by', +'Query:': 'Lek\xc3\xa9rdez\xc3\xa9s:', +'Record ID': 'Record ID', +'Registration key': 'Registration key', +'Reset Password key': 'Reset Password key', +'Role': 'Role', +'Rows in table': 'Sorok a t\xc3\xa1bl\xc3\xa1ban', +'Rows selected': 'Kiv\xc3\xa1lasztott sorok', +'Stylesheet': 'Stylesheet', +'Sure you want to delete this object?': 'Biztos t\xc3\xb6rli ezt az objektumot?', +'Table name': 'Table name', +'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.', +'Timestamp': 'Timestamp', +'Update:': 'Friss\xc3\xadt:', +'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.', +'User ID': 'User ID', +'View': 'N\xc3\xa9zet', +'Welcome %s': 'Welcome %s', +'Welcome to web2py': 'Isten hozott a web2py-ban', +'appadmin is disabled because insecure channel': 'az appadmin a biztons\xc3\xa1gtalan csatorna miatt letiltva', +'cache': 'gyors\xc3\xadt\xc3\xb3t\xc3\xa1r', +'change password': 'jelsz\xc3\xb3 megv\xc3\xa1ltoztat\xc3\xa1sa', +'Online examples': 'online p\xc3\xa9ld\xc3\xa1k\xc3\xa9rt kattints ide', +'Administrative interface': 'az adminisztr\xc3\xa1ci\xc3\xb3s fel\xc3\xbclet\xc3\xa9rt kattints ide', +'customize me!': 'v\xc3\xa1ltoztass meg!', +'data uploaded': 'adat felt\xc3\xb6ltve', +'database': 'adatb\xc3\xa1zis', +'database %s select': 'adatb\xc3\xa1zis %s kiv\xc3\xa1laszt\xc3\xa1s', +'db': 'db', +'design': 'design', +'done!': 'k\xc3\xa9sz!', +'edit profile': 'profil szerkeszt\xc3\xa9se', +'export as csv file': 'export\xc3\xa1l csv f\xc3\xa1jlba', +'insert new': '\xc3\xbaj beilleszt\xc3\xa9se', +'insert new %s': '\xc3\xbaj beilleszt\xc3\xa9se %s', +'invalid request': 'hib\xc3\xa1s k\xc3\xa9r\xc3\xa9s', +'login': 'bel\xc3\xa9p', +'logout': 'kil\xc3\xa9p', +'lost password': 'elveszett jelsz\xc3\xb3', +'new record inserted': '\xc3\xbaj bejegyz\xc3\xa9s felv\xc3\xa9ve', +'next 100 rows': 'k\xc3\xb6vetkez\xc5\x91 100 sor', +'or import from csv file': 'vagy bet\xc3\xb6lt\xc3\xa9s csv f\xc3\xa1jlb\xc3\xb3l', +'previous 100 rows': 'el\xc5\x91z\xc5\x91 100 sor', +'record': 'bejegyz\xc3\xa9s', +'record does not exist': 'bejegyz\xc3\xa9s nem l\xc3\xa9tezik', +'record id': 'bejegyz\xc3\xa9s id', +'register': 'regisztr\xc3\xa1ci\xc3\xb3', +'selected': 'kiv\xc3\xa1lasztott', +'state': '\xc3\xa1llapot', +'table': 't\xc3\xa1bla', +'unable to parse csv file': 'nem lehet a csv f\xc3\xa1jlt beolvasni', +} ADDED applications/welcome/languages/hu.py Index: applications/welcome/languages/hu.py ================================================================== --- applications/welcome/languages/hu.py +++ applications/welcome/languages/hu.py @@ -0,0 +1,93 @@ +# coding: utf8 +{ +'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN', +'%Y-%m-%d': '%Y.%m.%d.', +'%Y-%m-%d %H:%M:%S': '%Y.%m.%d. %H:%M:%S', +'%s rows deleted': '%s sorok t\xc3\xb6rl\xc5\x91dtek', +'%s rows updated': '%s sorok friss\xc3\xadt\xc5\x91dtek', +'Available databases and tables': 'El\xc3\xa9rhet\xc5\x91 adatb\xc3\xa1zisok \xc3\xa9s t\xc3\xa1bl\xc3\xa1k', +'Cannot be empty': 'Nem lehet \xc3\xbcres', +'Check to delete': 'T\xc3\xb6rl\xc3\xa9shez v\xc3\xa1laszd ki', +'Client IP': 'Client IP', +'Controller': 'Controller', +'Copyright': 'Copyright', +'Current request': 'Jelenlegi lek\xc3\xa9rdez\xc3\xa9s', +'Current response': 'Jelenlegi v\xc3\xa1lasz', +'Current session': 'Jelenlegi folyamat', +'DB Model': 'DB Model', +'Database': 'Adatb\xc3\xa1zis', +'Delete:': 'T\xc3\xb6r\xc3\xb6l:', +'Description': 'Description', +'E-mail': 'E-mail', +'Edit': 'Szerkeszt', +'Edit This App': 'Alkalmaz\xc3\xa1st szerkeszt', +'Edit current record': 'Aktu\xc3\xa1lis bejegyz\xc3\xa9s szerkeszt\xc3\xa9se', +'First name': 'First name', +'Group ID': 'Group ID', +'Hello World': 'Hello Vil\xc3\xa1g', +'Import/Export': 'Import/Export', +'Index': 'Index', +'Internal State': 'Internal State', +'Invalid Query': 'Hib\xc3\xa1s lek\xc3\xa9rdez\xc3\xa9s', +'Invalid email': 'Invalid email', +'Last name': 'Last name', +'Layout': 'Szerkezet', +'Main Menu': 'F\xc5\x91men\xc3\xbc', +'Menu Model': 'Men\xc3\xbc model', +'Name': 'Name', +'New Record': '\xc3\x9aj bejegyz\xc3\xa9s', +'No databases in this application': 'Nincs adatb\xc3\xa1zis ebben az alkalmaz\xc3\xa1sban', +'Origin': 'Origin', +'Password': 'Password', +'Powered by': 'Powered by', +'Query:': 'Lek\xc3\xa9rdez\xc3\xa9s:', +'Record ID': 'Record ID', +'Registration key': 'Registration key', +'Reset Password key': 'Reset Password key', +'Role': 'Role', +'Rows in table': 'Sorok a t\xc3\xa1bl\xc3\xa1ban', +'Rows selected': 'Kiv\xc3\xa1lasztott sorok', +'Stylesheet': 'Stylesheet', +'Sure you want to delete this object?': 'Biztos t\xc3\xb6rli ezt az objektumot?', +'Table name': 'Table name', +'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.', +'Timestamp': 'Timestamp', +'Update:': 'Friss\xc3\xadt:', +'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.', +'User ID': 'User ID', +'View': 'N\xc3\xa9zet', +'Welcome %s': 'Welcome %s', +'Welcome to web2py': 'Isten hozott a web2py-ban', +'appadmin is disabled because insecure channel': 'az appadmin a biztons\xc3\xa1gtalan csatorna miatt letiltva', +'cache': 'gyors\xc3\xadt\xc3\xb3t\xc3\xa1r', +'change password': 'jelsz\xc3\xb3 megv\xc3\xa1ltoztat\xc3\xa1sa', +'Online examples': 'online p\xc3\xa9ld\xc3\xa1k\xc3\xa9rt kattints ide', +'Administrative interface': 'az adminisztr\xc3\xa1ci\xc3\xb3s fel\xc3\xbclet\xc3\xa9rt kattints ide', +'customize me!': 'v\xc3\xa1ltoztass meg!', +'data uploaded': 'adat felt\xc3\xb6ltve', +'database': 'adatb\xc3\xa1zis', +'database %s select': 'adatb\xc3\xa1zis %s kiv\xc3\xa1laszt\xc3\xa1s', +'db': 'db', +'design': 'design', +'done!': 'k\xc3\xa9sz!', +'edit profile': 'profil szerkeszt\xc3\xa9se', +'export as csv file': 'export\xc3\xa1l csv f\xc3\xa1jlba', +'insert new': '\xc3\xbaj beilleszt\xc3\xa9se', +'insert new %s': '\xc3\xbaj beilleszt\xc3\xa9se %s', +'invalid request': 'hib\xc3\xa1s k\xc3\xa9r\xc3\xa9s', +'login': 'bel\xc3\xa9p', +'logout': 'kil\xc3\xa9p', +'lost password': 'elveszett jelsz\xc3\xb3', +'new record inserted': '\xc3\xbaj bejegyz\xc3\xa9s felv\xc3\xa9ve', +'next 100 rows': 'k\xc3\xb6vetkez\xc5\x91 100 sor', +'or import from csv file': 'vagy bet\xc3\xb6lt\xc3\xa9s csv f\xc3\xa1jlb\xc3\xb3l', +'previous 100 rows': 'el\xc5\x91z\xc5\x91 100 sor', +'record': 'bejegyz\xc3\xa9s', +'record does not exist': 'bejegyz\xc3\xa9s nem l\xc3\xa9tezik', +'record id': 'bejegyz\xc3\xa9s id', +'register': 'regisztr\xc3\xa1ci\xc3\xb3', +'selected': 'kiv\xc3\xa1lasztott', +'state': '\xc3\xa1llapot', +'table': 't\xc3\xa1bla', +'unable to parse csv file': 'nem lehet a csv f\xc3\xa1jlt beolvasni', +} ADDED applications/welcome/languages/it-it.py Index: applications/welcome/languages/it-it.py ================================================================== --- applications/welcome/languages/it-it.py +++ applications/welcome/languages/it-it.py @@ -0,0 +1,104 @@ +# coding: utf8 +{ +'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"update" è un\'espressione opzionale come "campo1=\'nuovo valore\'". Non si può fare "update" o "delete" dei risultati di un JOIN ', +'%Y-%m-%d': '%d/%m/%Y', +'%Y-%m-%d %H:%M:%S': '%d/%m/%Y %H:%M:%S', +'%s rows deleted': '%s righe ("record") cancellate', +'%s rows updated': '%s righe ("record") modificate', +'Available databases and tables': 'Database e tabelle disponibili', +'Cannot be empty': 'Non può essere vuoto', +'Check to delete': 'Seleziona per cancellare', +'Client IP': 'Client IP', +'Controller': 'Controller', +'Copyright': 'Copyright', +'Current request': 'Richiesta (request) corrente', +'Current response': 'Risposta (response) corrente', +'Current session': 'Sessione (session) corrente', +'DB Model': 'Modello di DB', +'Database': 'Database', +'Delete:': 'Cancella:', +'Description': 'Descrizione', +'E-mail': 'E-mail', +'Edit': 'Modifica', +'Edit This App': 'Modifica questa applicazione', +'Edit current record': 'Modifica record corrente', +'First name': 'Nome', +'Group ID': 'ID Gruppo', +'Hello World': 'Salve Mondo', +'Hello World in a flash!': 'Salve Mondo in un flash!', +'Import/Export': 'Importa/Esporta', +'Index': 'Indice', +'Internal State': 'Stato interno', +'Invalid Query': 'Richiesta (query) non valida', +'Invalid email': 'Email non valida', +'Last name': 'Cognome', +'Layout': 'Layout', +'Main Menu': 'Menu principale', +'Menu Model': 'Menu Modelli', +'Name': 'Nome', +'New Record': 'Nuovo elemento (record)', +'No databases in this application': 'Nessun database presente in questa applicazione', +'Origin': 'Origine', +'Password': 'Password', +'Powered by': 'Powered by', +'Query:': 'Richiesta (query):', +'Record ID': 'Record ID', +'Registration key': 'Chiave di Registazione', +'Reset Password key': 'Resetta chiave Password ', +'Role': 'Ruolo', +'Rows in table': 'Righe nella tabella', +'Rows selected': 'Righe selezionate', +'Stylesheet': 'Foglio di stile (stylesheet)', +'Sure you want to delete this object?': 'Vuoi veramente cancellare questo oggetto?', +'Table name': 'Nome tabella', +'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'La richiesta (query) è una condizione come ad esempio "db.tabella1.campo1==\'valore\'". Una condizione come "db.tabella1.campo1==db.tabella2.campo2" produce un "JOIN" SQL.', +'The output of the file is a dictionary that was rendered by the view': 'L\'output del file è un "dictionary" che è stato visualizzato dalla vista', +'This is a copy of the scaffolding application': "Questa è una copia dell'applicazione di base (scaffold)", +'Timestamp': 'Ora (timestamp)', +'Update:': 'Aggiorna:', +'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Per costruire richieste (query) più complesse si usano (...)&(...) come "e" (AND), (...)|(...) come "o" (OR), e ~(...) come negazione (NOT).', +'User ID': 'ID Utente', +'View': 'Vista', +'Welcome %s': 'Benvenuto %s', +'Welcome to web2py': 'Benvenuto su web2py', +'Which called the function': 'che ha chiamato la funzione', +'You are successfully running web2py': 'Stai eseguendo web2py con successo', +'You can modify this application and adapt it to your needs': 'Puoi modificare questa applicazione adattandola alle tue necessità', +'You visited the url': "Hai visitato l'URL", +'appadmin is disabled because insecure channel': 'Amministrazione (appadmin) disabilitata: comunicazione non sicura', +'cache': 'cache', +'change password': 'Cambia password', +'Online examples': 'Vedere gli esempi', +'Administrative interface': "Interfaccia amministrativa", +'customize me!': 'Personalizzami!', +'data uploaded': 'dati caricati', +'database': 'database', +'database %s select': 'database %s select', +'db': 'db', +'design': 'progetta', +'Documentation': 'Documentazione', +'done!': 'fatto!', +'edit profile': 'modifica profilo', +'export as csv file': 'esporta come file CSV', +'hello world': 'salve mondo', +'insert new': 'inserisci nuovo', +'insert new %s': 'inserisci nuovo %s', +'invalid request': 'richiesta non valida', +'located in the file': 'presente nel file', +'login': 'accesso', +'logout': 'uscita', +'lost password?': 'dimenticato la password?', +'new record inserted': 'nuovo record inserito', +'next 100 rows': 'prossime 100 righe', +'not authorized': 'non autorizzato', +'or import from csv file': 'oppure importa da file CSV', +'previous 100 rows': '100 righe precedenti', +'record': 'record', +'record does not exist': 'il record non esiste', +'record id': 'record id', +'register': 'registrazione', +'selected': 'selezionato', +'state': 'stato', +'table': 'tabella', +'unable to parse csv file': 'non riesco a decodificare questo file CSV', +} ADDED applications/welcome/languages/it.py Index: applications/welcome/languages/it.py ================================================================== --- applications/welcome/languages/it.py +++ applications/welcome/languages/it.py @@ -0,0 +1,104 @@ +# coding: utf8 +{ +'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"update" è un\'espressione opzionale come "campo1=\'nuovo valore\'". Non si può fare "update" o "delete" dei risultati di un JOIN ', +'%Y-%m-%d': '%d/%m/%Y', +'%Y-%m-%d %H:%M:%S': '%d/%m/%Y %H:%M:%S', +'%s rows deleted': '%s righe ("record") cancellate', +'%s rows updated': '%s righe ("record") modificate', +'Available databases and tables': 'Database e tabelle disponibili', +'Cannot be empty': 'Non può essere vuoto', +'Check to delete': 'Seleziona per cancellare', +'Client IP': 'Client IP', +'Controller': 'Controller', +'Copyright': 'Copyright', +'Current request': 'Richiesta (request) corrente', +'Current response': 'Risposta (response) corrente', +'Current session': 'Sessione (session) corrente', +'DB Model': 'Modello di DB', +'Database': 'Database', +'Delete:': 'Cancella:', +'Description': 'Descrizione', +'E-mail': 'E-mail', +'Edit': 'Modifica', +'Edit This App': 'Modifica questa applicazione', +'Edit current record': 'Modifica record corrente', +'First name': 'Nome', +'Group ID': 'ID Gruppo', +'Hello World': 'Salve Mondo', +'Hello World in a flash!': 'Salve Mondo in un flash!', +'Import/Export': 'Importa/Esporta', +'Index': 'Indice', +'Internal State': 'Stato interno', +'Invalid Query': 'Richiesta (query) non valida', +'Invalid email': 'Email non valida', +'Last name': 'Cognome', +'Layout': 'Layout', +'Main Menu': 'Menu principale', +'Menu Model': 'Menu Modelli', +'Name': 'Nome', +'New Record': 'Nuovo elemento (record)', +'No databases in this application': 'Nessun database presente in questa applicazione', +'Origin': 'Origine', +'Password': 'Password', +'Powered by': 'Powered by', +'Query:': 'Richiesta (query):', +'Record ID': 'Record ID', +'Registration key': 'Chiave di Registazione', +'Reset Password key': 'Resetta chiave Password ', +'Role': 'Ruolo', +'Rows in table': 'Righe nella tabella', +'Rows selected': 'Righe selezionate', +'Stylesheet': 'Foglio di stile (stylesheet)', +'Sure you want to delete this object?': 'Vuoi veramente cancellare questo oggetto?', +'Table name': 'Nome tabella', +'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'La richiesta (query) è una condizione come ad esempio "db.tabella1.campo1==\'valore\'". Una condizione come "db.tabella1.campo1==db.tabella2.campo2" produce un "JOIN" SQL.', +'The output of the file is a dictionary that was rendered by the view': 'L\'output del file è un "dictionary" che è stato visualizzato dalla vista', +'This is a copy of the scaffolding application': "Questa è una copia dell'applicazione di base (scaffold)", +'Timestamp': 'Ora (timestamp)', +'Update:': 'Aggiorna:', +'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Per costruire richieste (query) più complesse si usano (...)&(...) come "e" (AND), (...)|(...) come "o" (OR), e ~(...) come negazione (NOT).', +'User ID': 'ID Utente', +'View': 'Vista', +'Welcome %s': 'Benvenuto %s', +'Welcome to web2py': 'Benvenuto su web2py', +'Which called the function': 'che ha chiamato la funzione', +'You are successfully running web2py': 'Stai eseguendo web2py con successo', +'You can modify this application and adapt it to your needs': 'Puoi modificare questa applicazione adattandola alle tue necessità', +'You visited the url': "Hai visitato l'URL", +'appadmin is disabled because insecure channel': 'Amministrazione (appadmin) disabilitata: comunicazione non sicura', +'cache': 'cache', +'change password': 'Cambia password', +'Online examples': 'Vedere gli esempi', +'Administrative interface': "Interfaccia amministrativa", +'customize me!': 'Personalizzami!', +'data uploaded': 'dati caricati', +'database': 'database', +'database %s select': 'database %s select', +'db': 'db', +'design': 'progetta', +'Documentation': 'Documentazione', +'done!': 'fatto!', +'edit profile': 'modifica profilo', +'export as csv file': 'esporta come file CSV', +'hello world': 'salve mondo', +'insert new': 'inserisci nuovo', +'insert new %s': 'inserisci nuovo %s', +'invalid request': 'richiesta non valida', +'located in the file': 'presente nel file', +'login': 'accesso', +'logout': 'uscita', +'lost password?': 'dimenticato la password?', +'new record inserted': 'nuovo record inserito', +'next 100 rows': 'prossime 100 righe', +'not authorized': 'non autorizzato', +'or import from csv file': 'oppure importa da file CSV', +'previous 100 rows': '100 righe precedenti', +'record': 'record', +'record does not exist': 'il record non esiste', +'record id': 'record id', +'register': 'registrazione', +'selected': 'selezionato', +'state': 'stato', +'table': 'tabella', +'unable to parse csv file': 'non riesco a decodificare questo file CSV', +} ADDED applications/welcome/languages/pl-pl.py Index: applications/welcome/languages/pl-pl.py ================================================================== --- applications/welcome/languages/pl-pl.py +++ applications/welcome/languages/pl-pl.py @@ -0,0 +1,81 @@ +# coding: utf8 +{ +'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"Uaktualnij" jest dodatkowym wyra\xc5\xbceniem postaci "pole1=\'nowawarto\xc5\x9b\xc4\x87\'". Nie mo\xc5\xbcesz uaktualni\xc4\x87 lub usun\xc4\x85\xc4\x87 wynik\xc3\xb3w z JOIN:', +'%Y-%m-%d': '%Y-%m-%d', +'%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S', +'%s rows deleted': 'Wierszy usuni\xc4\x99tych: %s', +'%s rows updated': 'Wierszy uaktualnionych: %s', +'Available databases and tables': 'Dost\xc4\x99pne bazy danych i tabele', +'Cannot be empty': 'Nie mo\xc5\xbce by\xc4\x87 puste', +'Change Password': 'Change Password', +'Check to delete': 'Zaznacz aby usun\xc4\x85\xc4\x87', +'Controller': 'Controller', +'Copyright': 'Copyright', +'Current request': 'Aktualne \xc5\xbc\xc4\x85danie', +'Current response': 'Aktualna odpowied\xc5\xba', +'Current session': 'Aktualna sesja', +'DB Model': 'DB Model', +'Database': 'Database', +'Delete:': 'Usu\xc5\x84:', +'Edit': 'Edit', +'Edit Profile': 'Edit Profile', +'Edit This App': 'Edit This App', +'Edit current record': 'Edytuj aktualny rekord', +'Hello World': 'Witaj \xc5\x9awiecie', +'Import/Export': 'Importuj/eksportuj', +'Index': 'Index', +'Internal State': 'Stan wewn\xc4\x99trzny', +'Invalid Query': 'B\xc5\x82\xc4\x99dne zapytanie', +'Layout': 'Layout', +'Login': 'Zaloguj', +'Logout': 'Logout', +'Lost Password': 'Przypomnij has\xc5\x82o', +'Main Menu': 'Main Menu', +'Menu Model': 'Menu Model', +'New Record': 'Nowy rekord', +'No databases in this application': 'Brak baz danych w tej aplikacji', +'Powered by': 'Powered by', +'Query:': 'Zapytanie:', +'Register': 'Zarejestruj', +'Rows in table': 'Wiersze w tabeli', +'Rows selected': 'Wybrane wiersze', +'Stylesheet': 'Stylesheet', +'Sure you want to delete this object?': 'Czy na pewno chcesz usun\xc4\x85\xc4\x87 ten obiekt?', +'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': '"Zapytanie" jest warunkiem postaci "db.tabela1.pole1==\'warto\xc5\x9b\xc4\x87\'". Takie co\xc5\x9b jak "db.tabela1.pole1==db.tabela2.pole2" oznacza SQL JOIN.', +'Update:': 'Uaktualnij:', +'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'U\xc5\xbcyj (...)&(...) jako AND, (...)|(...) jako OR oraz ~(...) jako NOT do tworzenia bardziej skomplikowanych zapyta\xc5\x84.', +'View': 'View', +'Welcome %s': 'Welcome %s', +'Welcome to web2py': 'Witaj w web2py', +'appadmin is disabled because insecure channel': 'appadmin is disabled because insecure channel', +'cache': 'cache', +'change password': 'change password', +'Online examples': 'Kliknij aby przej\xc5\x9b\xc4\x87 do interaktywnych przyk\xc5\x82ad\xc3\xb3w', +'Administrative interface': 'Kliknij aby przej\xc5\x9b\xc4\x87 do panelu administracyjnego', +'customize me!': 'dostosuj mnie!', +'data uploaded': 'dane wys\xc5\x82ane', +'database': 'baza danych', +'database %s select': 'wyb\xc3\xb3r z bazy danych %s', +'db': 'baza danych', +'design': 'projektuj', +'done!': 'zrobione!', +'edit profile': 'edit profile', +'export as csv file': 'eksportuj jako plik csv', +'insert new': 'wstaw nowy rekord tabeli', +'insert new %s': 'wstaw nowy rekord do tabeli %s', +'invalid request': 'B\xc5\x82\xc4\x99dne \xc5\xbc\xc4\x85danie', +'login': 'login', +'logout': 'logout', +'new record inserted': 'nowy rekord zosta\xc5\x82 wstawiony', +'next 100 rows': 'nast\xc4\x99pne 100 wierszy', +'or import from csv file': 'lub zaimportuj z pliku csv', +'previous 100 rows': 'poprzednie 100 wierszy', +'record': 'record', +'record does not exist': 'rekord nie istnieje', +'record id': 'id rekordu', +'register': 'register', +'selected': 'wybranych', +'state': 'stan', +'table': 'tabela', +'unable to parse csv file': 'nie mo\xc5\xbcna sparsowa\xc4\x87 pliku csv', +} ADDED applications/welcome/languages/pl.py Index: applications/welcome/languages/pl.py ================================================================== --- applications/welcome/languages/pl.py +++ applications/welcome/languages/pl.py @@ -0,0 +1,104 @@ +# coding: utf8 +{ +'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"Uaktualnij" jest dodatkowym wyra\xc5\xbceniem postaci "pole1=\'nowawarto\xc5\x9b\xc4\x87\'". Nie mo\xc5\xbcesz uaktualni\xc4\x87 lub usun\xc4\x85\xc4\x87 wynik\xc3\xb3w z JOIN:', +'%Y-%m-%d': '%Y-%m-%d', +'%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S', +'%s rows deleted': 'Wierszy usuni\xc4\x99tych: %s', +'%s rows updated': 'Wierszy uaktualnionych: %s', +'Authentication': 'Uwierzytelnienie', +'Available databases and tables': 'Dost\xc4\x99pne bazy danych i tabele', +'Cannot be empty': 'Nie mo\xc5\xbce by\xc4\x87 puste', +'Change Password': 'Zmie\xc5\x84 has\xc5\x82o', +'Check to delete': 'Zaznacz aby usun\xc4\x85\xc4\x87', +'Check to delete:': 'Zaznacz aby usun\xc4\x85\xc4\x87:', +'Client IP': 'IP klienta', +'Controller': 'Kontroler', +'Copyright': 'Copyright', +'Current request': 'Aktualne \xc5\xbc\xc4\x85danie', +'Current response': 'Aktualna odpowied\xc5\xba', +'Current session': 'Aktualna sesja', +'DB Model': 'Model bazy danych', +'Database': 'Baza danych', +'Delete:': 'Usu\xc5\x84:', +'Description': 'Opis', +'E-mail': 'Adres e-mail', +'Edit': 'Edycja', +'Edit Profile': 'Edytuj profil', +'Edit This App': 'Edytuj t\xc4\x99 aplikacj\xc4\x99', +'Edit current record': 'Edytuj obecny rekord', +'First name': 'Imi\xc4\x99', +'Function disabled': 'Funkcja wy\xc5\x82\xc4\x85czona', +'Group ID': 'ID grupy', +'Hello World': 'Witaj \xc5\x9awiecie', +'Import/Export': 'Importuj/eksportuj', +'Index': 'Indeks', +'Internal State': 'Stan wewn\xc4\x99trzny', +'Invalid Query': 'B\xc5\x82\xc4\x99dne zapytanie', +'Invalid email': 'B\xc5\x82\xc4\x99dny adres email', +'Last name': 'Nazwisko', +'Layout': 'Uk\xc5\x82ad', +'Login': 'Zaloguj', +'Logout': 'Wyloguj', +'Lost Password': 'Przypomnij has\xc5\x82o', +'Main Menu': 'Menu g\xc5\x82\xc3\xb3wne', +'Menu Model': 'Model menu', +'Name': 'Nazwa', +'New Record': 'Nowy rekord', +'No databases in this application': 'Brak baz danych w tej aplikacji', +'Origin': '\xc5\xb9r\xc3\xb3d\xc5\x82o', +'Password': 'Has\xc5\x82o', +"Password fields don't match": 'Pola has\xc5\x82a nie s\xc4\x85 zgodne ze sob\xc4\x85', +'Powered by': 'Zasilane przez', +'Query:': 'Zapytanie:', +'Record ID': 'ID rekordu', +'Register': 'Zarejestruj', +'Registration key': 'Klucz rejestracji', +'Role': 'Rola', +'Rows in table': 'Wiersze w tabeli', +'Rows selected': 'Wybrane wiersze', +'Stylesheet': 'Arkusz styl\xc3\xb3w', +'Submit': 'Wy\xc5\x9blij', +'Sure you want to delete this object?': 'Czy na pewno chcesz usun\xc4\x85\xc4\x87 ten obiekt?', +'Table name': 'Nazwa tabeli', +'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': '"Zapytanie" jest warunkiem postaci "db.tabela1.pole1==\'warto\xc5\x9b\xc4\x87\'". Takie co\xc5\x9b jak "db.tabela1.pole1==db.tabela2.pole2" oznacza SQL JOIN.', +'Timestamp': 'Znacznik czasu', +'Update:': 'Uaktualnij:', +'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'U\xc5\xbcyj (...)&(...) jako AND, (...)|(...) jako OR oraz ~(...) jako NOT do tworzenia bardziej skomplikowanych zapyta\xc5\x84.', +'User %(id)s Registered': 'U\xc5\xbcytkownik %(id)s zosta\xc5\x82 zarejestrowany', +'User ID': 'ID u\xc5\xbcytkownika', +'Verify Password': 'Potwierd\xc5\xba has\xc5\x82o', +'View': 'Widok', +'Welcome %s': 'Welcome %s', +'Welcome to web2py': 'Witaj w web2py', +'appadmin is disabled because insecure channel': 'administracja aplikacji wy\xc5\x82\xc4\x85czona z powodu braku bezpiecznego po\xc5\x82\xc4\x85czenia', +'cache': 'cache', +'change password': 'change password', +'Online examples': 'Kliknij aby przej\xc5\x9b\xc4\x87 do interaktywnych przyk\xc5\x82ad\xc3\xb3w', +'Administrative interface': 'Kliknij aby przej\xc5\x9b\xc4\x87 do panelu administracyjnego', +'customize me!': 'dostosuj mnie!', +'data uploaded': 'dane wys\xc5\x82ane', +'database': 'baza danych', +'database %s select': 'wyb\xc3\xb3r z bazy danych %s', +'db': 'baza danych', +'design': 'projektuj', +'done!': 'zrobione!', +'edit profile': 'edit profile', +'export as csv file': 'eksportuj jako plik csv', +'insert new': 'wstaw nowy rekord tabeli', +'insert new %s': 'wstaw nowy rekord do tabeli %s', +'invalid request': 'B\xc5\x82\xc4\x99dne \xc5\xbc\xc4\x85danie', +'login': 'login', +'logout': 'logout', +'new record inserted': 'nowy rekord zosta\xc5\x82 wstawiony', +'next 100 rows': 'nast\xc4\x99pne 100 wierszy', +'or import from csv file': 'lub zaimportuj z pliku csv', +'previous 100 rows': 'poprzednie 100 wierszy', +'record': 'rekord', +'record does not exist': 'rekord nie istnieje', +'record id': 'id rekordu', +'register': 'register', +'selected': 'wybranych', +'state': 'stan', +'table': 'tabela', +'unable to parse csv file': 'nie mo\xc5\xbcna sparsowa\xc4\x87 pliku csv', +} ADDED applications/welcome/languages/pt-br.py Index: applications/welcome/languages/pt-br.py ================================================================== --- applications/welcome/languages/pt-br.py +++ applications/welcome/languages/pt-br.py @@ -0,0 +1,142 @@ +# coding: utf8 +{ +'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"update" é uma expressão opcional como "campo1=\'novovalor\'". Você não pode atualizar ou apagar os resultados de um JOIN', +'%Y-%m-%d': '%d-%m-%Y', +'%Y-%m-%d %H:%M:%S': '%d-%m-%Y %H:%M:%S', +'%s rows deleted': '%s linhas apagadas', +'%s rows updated': '%s linhas atualizadas', +'About': 'About', +'Access Control': 'Access Control', +'Ajax Recipes': 'Ajax Recipes', +'Available databases and tables': 'Bancos de dados e tabelas disponíveis', +'Buy this book': 'Buy this book', +'Cannot be empty': 'Não pode ser vazio', +'Check to delete': 'Marque para apagar', +'Client IP': 'Client IP', +'Community': 'Community', +'Controller': 'Controlador', +'Copyright': 'Copyright', +'Current request': 'Requisição atual', +'Current response': 'Resposta atual', +'Current session': 'Sessão atual', +'DB Model': 'Modelo BD', +'Database': 'Banco de dados', +'Delete:': 'Apagar:', +'Demo': 'Demo', +'Deployment Recipes': 'Deployment Recipes', +'Description': 'Description', +'Documentation': 'Documentation', +'Download': 'Download', +'E-mail': 'E-mail', +'Edit': 'Editar', +'Edit This App': 'Edit This App', +'Edit current record': 'Editar o registro atual', +'Errors': 'Errors', +'FAQ': 'FAQ', +'First name': 'First name', +'Forms and Validators': 'Forms and Validators', +'Free Applications': 'Free Applications', +'Group ID': 'Group ID', +'Groups': 'Groups', +'Hello World': 'Olá Mundo', +'Home': 'Home', +'Import/Export': 'Importar/Exportar', +'Index': 'Início', +'Internal State': 'Estado Interno', +'Introduction': 'Introduction', +'Invalid Query': 'Consulta Inválida', +'Invalid email': 'Invalid email', +'Last name': 'Last name', +'Layout': 'Layout', +'Layouts': 'Layouts', +'Live chat': 'Live chat', +'Login': 'Autentique-se', +'Lost Password': 'Esqueceu sua senha?', +'Main Menu': 'Menu Principal', +'Menu Model': 'Modelo de Menu', +'Name': 'Name', +'New Record': 'Novo Registro', +'No databases in this application': 'Sem bancos de dados nesta aplicação', +'Origin': 'Origin', +'Other Recipes': 'Other Recipes', +'Overview': 'Overview', +'Password': 'Password', +'Plugins': 'Plugins', +'Powered by': 'Powered by', +'Preface': 'Preface', +'Python': 'Python', +'Query:': 'Consulta:', +'Quick Examples': 'Quick Examples', +'Recipes': 'Recipes', +'Record ID': 'Record ID', +'Register': 'Registre-se', +'Registration key': 'Registration key', +'Reset Password key': 'Reset Password key', +'Resources': 'Resources', +'Role': 'Role', +'Rows in table': 'Linhas na tabela', +'Rows selected': 'Linhas selecionadas', +'Semantic': 'Semantic', +'Services': 'Services', +'Stylesheet': 'Stylesheet', +'Support': 'Support', +'Sure you want to delete this object?': 'Está certo(a) que deseja apagar esse objeto ?', +'Table name': 'Table name', +'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'Uma "consulta" é uma condição como "db.tabela1.campo1==\'valor\'". Expressões como "db.tabela1.campo1==db.tabela2.campo2" resultam em um JOIN SQL.', +'The Core': 'The Core', +'The Views': 'The Views', +'The output of the file is a dictionary that was rendered by the view': 'The output of the file is a dictionary that was rendered by the view', +'This App': 'This App', +'This is a copy of the scaffolding application': 'This is a copy of the scaffolding application', +'Timestamp': 'Timestamp', +'Twitter': 'Twitter', +'Update:': 'Atualizar:', +'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Use (...)&(...) para AND, (...)|(...) para OR, e ~(...) para NOT para construir consultas mais complexas.', +'User ID': 'User ID', +'User Voice': 'User Voice', +'Videos': 'Videos', +'View': 'Visualização', +'Web2py': 'Web2py', +'Welcome': 'Welcome', +'Welcome %s': 'Vem vindo %s', +'Welcome to web2py': 'Bem vindo ao web2py', +'Which called the function': 'Which called the function', +'You are successfully running web2py': 'You are successfully running web2py', +'You are successfully running web2py.': 'You are successfully running web2py.', +'You can modify this application and adapt it to your needs': 'You can modify this application and adapt it to your needs', +'You visited the url': 'You visited the url', +'appadmin is disabled because insecure channel': 'Administração desativada devido ao canal inseguro', +'cache': 'cache', +'change password': 'modificar senha', +'Online examples': 'Alguns exemplos', +'Administrative interface': 'Interface administrativa', +'customize me!': 'Personalize-me!', +'data uploaded': 'dados enviados', +'database': 'banco de dados', +'database %s select': 'Selecionar banco de dados %s', +'db': 'bd', +'design': 'design', +'Documentation': 'Documentation', +'done!': 'concluído!', +'edit profile': 'editar perfil', +'export as csv file': 'exportar como um arquivo csv', +'insert new': 'inserir novo', +'insert new %s': 'inserir novo %s', +'invalid request': 'requisição inválida', +'located in the file': 'located in the file', +'login': 'Entrar', +'logout': 'Sair', +'lost password?': 'lost password?', +'new record inserted': 'novo registro inserido', +'next 100 rows': 'próximas 100 linhas', +'or import from csv file': 'ou importar de um arquivo csv', +'previous 100 rows': '100 linhas anteriores', +'record': 'registro', +'record does not exist': 'registro não existe', +'record id': 'id do registro', +'register': 'Registre-se', +'selected': 'selecionado', +'state': 'estado', +'table': 'tabela', +'unable to parse csv file': 'não foi possível analisar arquivo csv', +} ADDED applications/welcome/languages/pt-pt.py Index: applications/welcome/languages/pt-pt.py ================================================================== --- applications/welcome/languages/pt-pt.py +++ applications/welcome/languages/pt-pt.py @@ -0,0 +1,116 @@ +# coding: utf8 +{ +'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"update" é uma expressão opcional como "field1=\'newvalue\'". Não pode actualizar ou eliminar os resultados de um JOIN', +'%Y-%m-%d': '%Y-%m-%d', +'%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S', +'%s rows deleted': '%s linhas eliminadas', +'%s rows updated': '%s linhas actualizadas', +'About': 'About', +'Author Reference Auth User': 'Author Reference Auth User', +'Author Reference Auth User.username': 'Author Reference Auth User.username', +'Available databases and tables': 'bases de dados e tabelas disponíveis', +'Cannot be empty': 'não pode ser vazio', +'Category Create': 'Category Create', +'Category Select': 'Category Select', +'Check to delete': 'seleccione para eliminar', +'Comment Create': 'Comment Create', +'Comment Select': 'Comment Select', +'Content': 'Content', +'Controller': 'Controlador', +'Copyright': 'Direitos de cópia', +'Created By': 'Created By', +'Created On': 'Created On', +'Current request': 'pedido currente', +'Current response': 'resposta currente', +'Current session': 'sessão currente', +'DB Model': 'Modelo de BD', +'Database': 'Base de dados', +'Delete:': 'Eliminar:', +'Edit': 'Editar', +'Edit This App': 'Edite esta aplicação', +'Edit current record': 'Edição de registo currente', +'Email': 'Email', +'First Name': 'First Name', +'For %s #%s': 'For %s #%s', +'Hello World': 'Olá Mundo', +'Import/Export': 'Importar/Exportar', +'Index': 'Índice', +'Internal State': 'Estado interno', +'Invalid Query': 'Consulta Inválida', +'Last Name': 'Last Name', +'Layout': 'Esboço', +'Main Menu': 'Menu Principal', +'Menu Model': 'Menu do Modelo', +'Modified By': 'Modified By', +'Modified On': 'Modified On', +'Name': 'Name', +'New Record': 'Novo Registo', +'No Data': 'No Data', +'No databases in this application': 'Não há bases de dados nesta aplicação', +'Password': 'Password', +'Post Create': 'Post Create', +'Post Select': 'Post Select', +'Powered by': 'Suportado por', +'Query:': 'Interrogação:', +'Replyto Reference Post': 'Replyto Reference Post', +'Rows in table': 'Linhas numa tabela', +'Rows selected': 'Linhas seleccionadas', +'Stylesheet': 'Folha de estilo', +'Sure you want to delete this object?': 'Tem a certeza que deseja eliminar este objecto?', +'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'A "query" é uma condição do tipo "db.table1.field1==\'value\'". Algo como "db.table1.field1==db.table2.field2" resultaria num SQL JOIN.', +'Title': 'Title', +'Update:': 'Actualização:', +'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Utilize (...)&(...) para AND, (...)|(...) para OR, e ~(...) para NOT para construir interrogações mais complexas.', +'Username': 'Username', +'View': 'Vista', +'Welcome %s': 'Bem-vindo(a) %s', +'Welcome to Gluonization': 'Bem vindo ao Web2py', +'Welcome to web2py': 'Bem-vindo(a) ao web2py', +'When': 'When', +'appadmin is disabled because insecure channel': 'appadmin está desactivada pois o canal é inseguro', +'cache': 'cache', +'change password': 'alterar palavra-chave', +'Online examples': 'Exemplos online', +'Administrative interface': 'Painel administrativo', +'create new category': 'create new category', +'create new comment': 'create new comment', +'create new post': 'create new post', +'customize me!': 'Personaliza-me!', +'data uploaded': 'informação enviada', +'database': 'base de dados', +'database %s select': 'selecção de base de dados %s', +'db': 'bd', +'design': 'design', +'done!': 'concluído!', +'edit category': 'edit category', +'edit comment': 'edit comment', +'edit post': 'edit post', +'edit profile': 'Editar perfil', +'export as csv file': 'exportar como ficheiro csv', +'insert new': 'inserir novo', +'insert new %s': 'inserir novo %s', +'invalid request': 'Pedido Inválido', +'login': 'login', +'logout': 'logout', +'new record inserted': 'novo registo inserido', +'next 100 rows': 'próximas 100 linhas', +'or import from csv file': 'ou importe a partir de ficheiro csv', +'previous 100 rows': '100 linhas anteriores', +'record': 'registo', +'record does not exist': 'registo inexistente', +'record id': 'id de registo', +'register': 'register', +'search category': 'search category', +'search comment': 'search comment', +'search post': 'search post', +'select category': 'select category', +'select comment': 'select comment', +'select post': 'select post', +'selected': 'seleccionado(s)', +'show category': 'show category', +'show comment': 'show comment', +'show post': 'show post', +'state': 'estado', +'table': 'tabela', +'unable to parse csv file': 'não foi possível carregar ficheiro csv', +} ADDED applications/welcome/languages/pt.py Index: applications/welcome/languages/pt.py ================================================================== --- applications/welcome/languages/pt.py +++ applications/welcome/languages/pt.py @@ -0,0 +1,116 @@ +# coding: utf8 +{ +'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"update" é uma expressão opcional como "field1=\'newvalue\'". Não pode actualizar ou eliminar os resultados de um JOIN', +'%Y-%m-%d': '%Y-%m-%d', +'%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S', +'%s rows deleted': '%s linhas eliminadas', +'%s rows updated': '%s linhas actualizadas', +'About': 'About', +'Author Reference Auth User': 'Author Reference Auth User', +'Author Reference Auth User.username': 'Author Reference Auth User.username', +'Available databases and tables': 'bases de dados e tabelas disponíveis', +'Cannot be empty': 'não pode ser vazio', +'Category Create': 'Category Create', +'Category Select': 'Category Select', +'Check to delete': 'seleccione para eliminar', +'Comment Create': 'Comment Create', +'Comment Select': 'Comment Select', +'Content': 'Content', +'Controller': 'Controlador', +'Copyright': 'Direitos de cópia', +'Created By': 'Created By', +'Created On': 'Created On', +'Current request': 'pedido currente', +'Current response': 'resposta currente', +'Current session': 'sessão currente', +'DB Model': 'Modelo de BD', +'Database': 'Base de dados', +'Delete:': 'Eliminar:', +'Edit': 'Editar', +'Edit This App': 'Edite esta aplicação', +'Edit current record': 'Edição de registo currente', +'Email': 'Email', +'First Name': 'First Name', +'For %s #%s': 'For %s #%s', +'Hello World': 'Olá Mundo', +'Import/Export': 'Importar/Exportar', +'Index': 'Índice', +'Internal State': 'Estado interno', +'Invalid Query': 'Consulta Inválida', +'Last Name': 'Last Name', +'Layout': 'Esboço', +'Main Menu': 'Menu Principal', +'Menu Model': 'Menu do Modelo', +'Modified By': 'Modified By', +'Modified On': 'Modified On', +'Name': 'Name', +'New Record': 'Novo Registo', +'No Data': 'No Data', +'No databases in this application': 'Não há bases de dados nesta aplicação', +'Password': 'Password', +'Post Create': 'Post Create', +'Post Select': 'Post Select', +'Powered by': 'Suportado por', +'Query:': 'Interrogação:', +'Replyto Reference Post': 'Replyto Reference Post', +'Rows in table': 'Linhas numa tabela', +'Rows selected': 'Linhas seleccionadas', +'Stylesheet': 'Folha de estilo', +'Sure you want to delete this object?': 'Tem a certeza que deseja eliminar este objecto?', +'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'A "query" é uma condição do tipo "db.table1.field1==\'value\'". Algo como "db.table1.field1==db.table2.field2" resultaria num SQL JOIN.', +'Title': 'Title', +'Update:': 'Actualização:', +'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Utilize (...)&(...) para AND, (...)|(...) para OR, e ~(...) para NOT para construir interrogações mais complexas.', +'Username': 'Username', +'View': 'Vista', +'Welcome %s': 'Bem-vindo(a) %s', +'Welcome to Gluonization': 'Bem vindo ao Web2py', +'Welcome to web2py': 'Bem-vindo(a) ao web2py', +'When': 'When', +'appadmin is disabled because insecure channel': 'appadmin está desactivada pois o canal é inseguro', +'cache': 'cache', +'change password': 'alterar palavra-chave', +'Online examples': 'Exemplos online', +'Administrative interface': 'Painel administrativo', +'create new category': 'create new category', +'create new comment': 'create new comment', +'create new post': 'create new post', +'customize me!': 'Personaliza-me!', +'data uploaded': 'informação enviada', +'database': 'base de dados', +'database %s select': 'selecção de base de dados %s', +'db': 'bd', +'design': 'design', +'done!': 'concluído!', +'edit category': 'edit category', +'edit comment': 'edit comment', +'edit post': 'edit post', +'edit profile': 'Editar perfil', +'export as csv file': 'exportar como ficheiro csv', +'insert new': 'inserir novo', +'insert new %s': 'inserir novo %s', +'invalid request': 'Pedido Inválido', +'login': 'login', +'logout': 'logout', +'new record inserted': 'novo registo inserido', +'next 100 rows': 'próximas 100 linhas', +'or import from csv file': 'ou importe a partir de ficheiro csv', +'previous 100 rows': '100 linhas anteriores', +'record': 'registo', +'record does not exist': 'registo inexistente', +'record id': 'id de registo', +'register': 'register', +'search category': 'search category', +'search comment': 'search comment', +'search post': 'search post', +'select category': 'select category', +'select comment': 'select comment', +'select post': 'select post', +'selected': 'seleccionado(s)', +'show category': 'show category', +'show comment': 'show comment', +'show post': 'show post', +'state': 'estado', +'table': 'tabela', +'unable to parse csv file': 'não foi possível carregar ficheiro csv', +} ADDED applications/welcome/languages/ru-ru.py Index: applications/welcome/languages/ru-ru.py ================================================================== --- applications/welcome/languages/ru-ru.py +++ applications/welcome/languages/ru-ru.py @@ -0,0 +1,96 @@ +# coding: utf8 +{ +'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"Изменить" - необязательное выражение вида "field1=\'новое значение\'". Результаты операции JOIN нельзя изменить или удалить.', +'%Y-%m-%d': '%Y-%m-%d', +'%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S', +'%s rows deleted': '%s строк удалено', +'%s rows updated': '%s строк изменено', +'Available databases and tables': 'Базы данных и таблицы', +'Cannot be empty': 'Пустое значение недопустимо', +'Change Password': 'Смените пароль', +'Check to delete': 'Удалить', +'Check to delete:': 'Удалить:', +'Client IP': 'Client IP', +'Current request': 'Текущий запрос', +'Current response': 'Текущий ответ', +'Current session': 'Текущая сессия', +'Delete:': 'Удалить:', +'Description': 'Описание', +'E-mail': 'E-mail', +'Edit Profile': 'Редактировать профиль', +'Edit current record': 'Редактировать текущую запись', +'First name': 'Имя', +'Group ID': 'Group ID', +'Hello World': 'Заработало!', +'Import/Export': 'Импорт/экспорт', +'Internal State': 'Внутренне состояние', +'Invalid Query': 'Неверный запрос', +'Invalid email': 'Неверный email', +'Invalid login': 'Неверный логин', +'Invalid password': 'Неверный пароль', +'Last name': 'Фамилия', +'Logged in': 'Вход выполнен', +'Logged out': 'Выход выполнен', +'Login': 'Вход', +'Logout': 'Выход', +'Lost Password': 'Забыли пароль?', +'Name': 'Name', +'New Record': 'Новая запись', +'New password': 'Новый пароль', +'No databases in this application': 'В приложении нет баз данных', +'Old password': 'Старый пароль', +'Origin': 'Происхождение', +'Password': 'Пароль', +"Password fields don't match": 'Пароли не совпадают', +'Query:': 'Запрос:', +'Record ID': 'ID записи', +'Register': 'Зарегистрироваться', +'Registration key': 'Ключ регистрации', +'Remember me (for 30 days)': 'Запомнить меня (на 30 дней)', +'Reset Password key': 'Сбросить ключ пароля', +'Role': 'Роль', +'Rows in table': 'Строк в таблице', +'Rows selected': 'Выделено строк', +'Submit': 'Отправить', +'Sure you want to delete this object?': 'Подтвердите удаление объекта', +'Table name': 'Имя таблицы', +'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': '"Запрос" - это условие вида "db.table1.field1==\'значение\'". Выражение вида "db.table1.field1==db.table2.field2" формирует SQL JOIN.', +'Timestamp': 'Отметка времени', +'Update:': 'Изменить:', +'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Для построение сложных запросов используйте операторы "И": (...)&(...), "ИЛИ": (...)|(...), "НЕ": ~(...).', +'User %(id)s Logged-in': 'Пользователь %(id)s вошёл', +'User %(id)s Logged-out': 'Пользователь %(id)s вышел', +'User %(id)s Password changed': 'Пользователь %(id)s сменил пароль', +'User %(id)s Profile updated': 'Пользователь %(id)s обновил профиль', +'User %(id)s Registered': 'Пользователь %(id)s зарегистрировался', +'User ID': 'ID пользователя', +'Verify Password': 'Повторите пароль', +'Welcome to web2py': 'Добро пожаловать в web2py', +'Online examples': 'примеры он-лайн', +'Administrative interface': 'административный интерфейс', +'customize me!': 'настройте внешний вид!', +'data uploaded': 'данные загружены', +'database': 'база данных', +'database %s select': 'выбор базы данных %s', +'db': 'БД', +'design': 'дизайн', +'done!': 'готово!', +'export as csv file': 'экспорт в csv-файл', +'insert new': 'добавить', +'insert new %s': 'добавить %s', +'invalid request': 'неверный запрос', +'login': 'вход', +'logout': 'выход', +'new record inserted': 'новая запись добавлена', +'next 100 rows': 'следующие 100 строк', +'or import from csv file': 'или импорт из csv-файла', +'password': 'пароль', +'previous 100 rows': 'предыдущие 100 строк', +'profile': 'профиль', +'record does not exist': 'запись не найдена', +'record id': 'id записи', +'selected': 'выбрано', +'state': 'состояние', +'table': 'таблица', +'unable to parse csv file': 'нечитаемый csv-файл', +} ADDED applications/welcome/languages/sk-sk.py Index: applications/welcome/languages/sk-sk.py ================================================================== --- applications/welcome/languages/sk-sk.py +++ applications/welcome/languages/sk-sk.py @@ -0,0 +1,111 @@ +# coding: utf8 +{ +'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"update" je voliteľný výraz ako "field1=\'newvalue\'". Nemôžete upravovať alebo zmazať výsledky JOINu', +'%Y-%m-%d': '%d.%m.%Y', +'%Y-%m-%d %H:%M:%S': '%d.%m.%Y %H:%M:%S', +'%s rows deleted': '%s zmazaných záznamov', +'%s rows updated': '%s upravených záznamov', +'Available databases and tables': 'Dostupné databázy a tabuľky', +'Cannot be empty': 'Nemôže byť prázdne', +'Check to delete': 'Označiť na zmazanie', +'Controller': 'Controller', +'Copyright': 'Copyright', +'Current request': 'Aktuálna požiadavka', +'Current response': 'Aktuálna odpoveď', +'Current session': 'Aktuálne sedenie', +'DB Model': 'DB Model', +'Database': 'Databáza', +'Delete:': 'Zmazať:', +'Description': 'Popis', +'Edit': 'Upraviť', +'Edit Profile': 'Upraviť profil', +'Edit current record': 'Upraviť aktuálny záznam', +'First name': 'Krstné meno', +'Group ID': 'ID skupiny', +'Hello World': 'Ahoj svet', +'Import/Export': 'Import/Export', +'Index': 'Index', +'Internal State': 'Vnútorný stav', +'Invalid email': 'Neplatný email', +'Invalid Query': 'Neplatná otázka', +'Invalid password': 'Nesprávne heslo', +'Last name': 'Priezvisko', +'Layout': 'Layout', +'Logged in': 'Prihlásený', +'Logged out': 'Odhlásený', +'Lost Password': 'Stratené heslo?', +'Menu Model': 'Menu Model', +'Name': 'Meno', +'New Record': 'Nový záznam', +'New password': 'Nové heslo', +'No databases in this application': 'V tejto aplikácii nie sú databázy', +'Old password': 'Staré heslo', +'Origin': 'Pôvod', +'Password': 'Heslo', +'Powered by': 'Powered by', +'Query:': 'Otázka:', +'Record ID': 'ID záznamu', +'Register': 'Zaregistrovať sa', +'Registration key': 'Registračný kľúč', +'Remember me (for 30 days)': 'Zapamätaj si ma (na 30 dní)', +'Reset Password key': 'Nastaviť registračný kľúč', +'Role': 'Rola', +'Rows in table': 'riadkov v tabuľke', +'Rows selected': 'označených riadkov', +'Submit': 'Odoslať', +'Stylesheet': 'Stylesheet', +'Sure you want to delete this object?': 'Ste si istí, že chcete zmazať tento objekt?', +'Table name': 'Názov tabuľky', +'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': '"query" je podmienka ako "db.table1.field1==\'value\'". Niečo ako "db.table1.field1==db.table2.field2" má za výsledok SQL JOIN.', +'The output of the file is a dictionary that was rendered by the view': 'Výstup zo súboru je slovník, ktorý bol zobrazený vo view', +'This is a copy of the scaffolding application': 'Toto je kópia skeletu aplikácie', +'Timestamp': 'Časová pečiatka', +'Update:': 'Upraviť:', +'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Použite (...)&(...) pre AND, (...)|(...) pre OR a ~(...) pre NOT na poskladanie komplexnejších otázok.', +'User %(id)s Logged-in': 'Používateľ %(id)s prihlásený', +'User %(id)s Logged-out': 'Používateľ %(id)s odhlásený', +'User %(id)s Password changed': 'Používateľ %(id)s zmenil heslo', +'User %(id)s Profile updated': 'Používateľ %(id)s upravil profil', +'User %(id)s Registered': 'Používateľ %(id)s sa zaregistroval', +'User ID': 'ID používateľa', +'Verify Password': 'Zopakujte heslo', +'View': 'Zobraziť', +'Welcome to web2py': 'Vitajte vo web2py', +'Which called the function': 'Ktorý zavolal funkciu', +'You are successfully running web2py': 'Úspešne ste spustili web2py', +'You can modify this application and adapt it to your needs': 'Môžete upraviť túto aplikáciu a prispôsobiť ju svojim potrebám', +'You visited the url': 'Navštívili ste URL', +'appadmin is disabled because insecure channel': 'appadmin je zakázaný bez zabezpečeného spojenia', +'cache': 'cache', +'Online examples': 'pre online príklady kliknite sem', +'Administrative interface': 'pre administrátorské rozhranie kliknite sem', +'customize me!': 'prispôsob ma!', +'data uploaded': 'údaje naplnené', +'database': 'databáza', +'database %s select': 'databáza %s výber', +'db': 'db', +'design': 'návrh', +'Documentation': 'Dokumentácia', +'done!': 'hotovo!', +'export as csv file': 'exportovať do csv súboru', +'insert new': 'vložiť nový záznam ', +'insert new %s': 'vložiť nový záznam %s', +'invalid request': 'Neplatná požiadavka', +'located in the file': 'nachádzajúci sa v súbore ', +'login': 'prihlásiť', +'logout': 'odhlásiť', +'lost password?': 'stratené heslo?', +'new record inserted': 'nový záznam bol vložený', +'next 100 rows': 'ďalších 100 riadkov', +'or import from csv file': 'alebo naimportovať z csv súboru', +'password': 'heslo', +'previous 100 rows': 'predchádzajúcich 100 riadkov', +'record': 'záznam', +'record does not exist': 'záznam neexistuje', +'record id': 'id záznamu', +'register': 'registrovať', +'selected': 'označených', +'state': 'stav', +'table': 'tabuľka', +'unable to parse csv file': 'nedá sa načítať csv súbor', +} ADDED applications/welcome/languages/zh-tw.py Index: applications/welcome/languages/zh-tw.py ================================================================== --- applications/welcome/languages/zh-tw.py +++ applications/welcome/languages/zh-tw.py @@ -0,0 +1,165 @@ +# coding: utf8 +{ +'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"更新" 是選擇性的條件式, 格式就像 "欄位1=\'值\'". 但是 JOIN 的資料不可以使用 update 或是 delete"', +'%Y-%m-%d': '%Y-%m-%d', +'%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S', +'%s rows deleted': '已刪除 %s 筆', +'%s rows updated': '已更新 %s 筆', +'(something like "it-it")': '(格式類似 "zh-tw")', +'A new version of web2py is available': '新版的 web2py 已發行', +'A new version of web2py is available: %s': '新版的 web2py 已發行: %s', +'ATTENTION: Login requires a secure (HTTPS) connection or running on localhost.': '注意: 登入管理帳號需要安全連線(HTTPS)或是在本機連線(localhost).', +'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': '注意: 因為在測試模式不保證多執行緒安全性,也就是說不可以同時執行多個測試案例', +'ATTENTION: you cannot edit the running application!': '注意:不可編輯正在執行的應用程式!', +'About': '關於', +'About application': '關於本應用程式', +'Admin is disabled because insecure channel': '管理功能(Admin)在不安全連線環境下自動關閉', +'Admin is disabled because unsecure channel': '管理功能(Admin)在不安全連線環境下自動關閉', +'Administrator Password:': '管理員密碼:', +'Are you sure you want to delete file "%s"?': '確定要刪除檔案"%s"?', +'Are you sure you want to uninstall application "%s"': '確定要移除應用程式 "%s"', +'Are you sure you want to uninstall application "%s"?': '確定要移除應用程式 "%s"', +'Authentication': '驗證', +'Available databases and tables': '可提供的資料庫和資料表', +'Cannot be empty': '不可空白', +'Cannot compile: there are errors in your app. Debug it, correct errors and try again.': '無法編譯:應用程式中含有錯誤,請除錯後再試一次.', +'Change Password': '變更密碼', +'Check to delete': '打勾代表刪除', +'Check to delete:': '點選以示刪除:', +'Client IP': '客戶端網址(IP)', +'Controller': '控件', +'Controllers': '控件', +'Copyright': '版權所有', +'Create new application': '創建應用程式', +'Current request': '目前網路資料要求(request)', +'Current response': '目前網路資料回應(response)', +'Current session': '目前網路連線資訊(session)', +'DB Model': '資料庫模組', +'DESIGN': '設計', +'Database': '資料庫', +'Date and Time': '日期和時間', +'Delete': '刪除', +'Delete:': '刪除:', +'Deploy on Google App Engine': '配置到 Google App Engine', +'Description': '描述', +'Design for': '設計為了', +'E-mail': '電子郵件', +'EDIT': '編輯', +'Edit': '編輯', +'Edit Profile': '編輯設定檔', +'Edit This App': '編輯本應用程式', +'Edit application': '編輯應用程式', +'Edit current record': '編輯當前紀錄', +'Editing file': '編輯檔案', +'Editing file "%s"': '編輯檔案"%s"', +'Error logs for "%(app)s"': '"%(app)s"的錯誤紀錄', +'First name': '名', +'Functions with no doctests will result in [passed] tests.': '沒有 doctests 的函式會顯示 [passed].', +'Group ID': '群組編號', +'Hello World': '嗨! 世界', +'Import/Export': '匯入/匯出', +'Index': '索引', +'Installed applications': '已安裝應用程式', +'Internal State': '內部狀態', +'Invalid Query': '不合法的查詢', +'Invalid action': '不合法的動作(action)', +'Invalid email': '不合法的電子郵件', +'Language files (static strings) updated': '語言檔已更新', +'Languages': '各國語言', +'Last name': '姓', +'Last saved on:': '最後儲存時間:', +'Layout': '網頁配置', +'License for': '軟體版權為', +'Login': '登入', +'Login to the Administrative Interface': '登入到管理員介面', +'Logout': '登出', +'Lost Password': '密碼遺忘', +'Main Menu': '主選單', +'Menu Model': '選單模組(menu)', +'Models': '資料模組', +'Modules': '程式模組', +'NO': '否', +'Name': '名字', +'New Record': '新紀錄', +'No databases in this application': '這應用程式不含資料庫', +'Origin': '原文', +'Original/Translation': '原文/翻譯', +'Password': '密碼', +"Password fields don't match": '密碼欄不匹配', +'Peeking at file': '選擇檔案', +'Powered by': '基於以下技術構建:', +'Query:': '查詢:', +'Record ID': '紀錄編號', +'Register': '註冊', +'Registration key': '註冊金鑰', +'Remember me (for 30 days)': '記住我(30 天)', +'Reset Password key': '重設密碼', +'Resolve Conflict file': '解決衝突檔案', +'Role': '角色', +'Rows in table': '在資料表裏的資料', +'Rows selected': '筆資料被選擇', +'Saved file hash:': '檔案雜湊值已紀錄:', +'Static files': '靜態檔案', +'Stylesheet': '網頁風格檔', +'Submit': '傳送', +'Sure you want to delete this object?': '確定要刪除此物件?', +'Table name': '資料表名稱', +'Testing application': '測試中的應用程式', +'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': '"查詢"是一個像 "db.表1.欄位1==\'值\'" 的條件式. 以"db.表1.欄位1==db.表2.欄位2"方式則相當於執行 JOIN SQL.', +'There are no controllers': '沒有控件(controllers)', +'There are no models': '沒有資料庫模組(models)', +'There are no modules': '沒有程式模組(modules)', +'There are no static files': '沒有靜態檔案', +'There are no translators, only default language is supported': '沒有翻譯檔,只支援原始語言', +'There are no views': '沒有視圖', +'This is the %(filename)s template': '這是%(filename)s檔案的樣板(template)', +'Ticket': '問題單', +'Timestamp': '時間標記', +'Unable to check for upgrades': '無法做升級檢查', +'Unable to download': '無法下載', +'Unable to download app': '無法下載應用程式', +'Update:': '更新:', +'Upload existing application': '更新存在的應用程式', +'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': '使用下列方式來組合更複雜的條件式, (...)&(...) 代表同時存在的條件, (...)|(...) 代表擇一的條件, ~(...)則代表反向條件.', +'User %(id)s Logged-in': '使用者 %(id)s 已登入', +'User %(id)s Registered': '使用者 %(id)s 已註冊', +'User ID': '使用者編號', +'Verify Password': '驗證密碼', +'View': '視圖', +'Views': '視圖', +'Welcome %s': '歡迎 %s', +'Welcome to web2py': '歡迎使用 web2py', +'YES': '是', +'about': '關於', +'appadmin is disabled because insecure channel': '因為來自非安全通道,管理介面關閉', +'cache': '快取記憶體', +'change password': '變更密碼', +'Online examples': '點此處進入線上範例', +'Administrative interface': '點此處進入管理介面', +'customize me!': '請調整我!', +'data uploaded': '資料已上傳', +'database': '資料庫', +'database %s select': '已選擇 %s 資料庫', +'db': 'db', +'design': '設計', +'done!': '完成!', +'edit profile': '編輯設定檔', +'export as csv file': '以逗號分隔檔(csv)格式匯出', +'insert new': '插入新資料', +'insert new %s': '插入新資料 %s', +'invalid request': '不合法的網路要求(request)', +'login': '登入', +'logout': '登出', +'new record inserted': '已插入新紀錄', +'next 100 rows': '往後 100 筆', +'or import from csv file': '或是從逗號分隔檔(CSV)匯入', +'previous 100 rows': '往前 100 筆', +'record': '紀錄', +'record does not exist': '紀錄不存在', +'record id': '紀錄編號', +'register': '註冊', +'selected': '已選擇', +'state': '狀態', +'table': '資料表', +'unable to parse csv file': '無法解析逗號分隔檔(csv)', +} ADDED applications/welcome/models/db.py Index: applications/welcome/models/db.py ================================================================== --- applications/welcome/models/db.py +++ applications/welcome/models/db.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- +# this file is released under public domain and you can use without limitations + +######################################################################### +## This scaffolding model makes your app work on Google App Engine too +######################################################################### + +if request.env.web2py_runtime_gae: # if running on Google App Engine + db = DAL('google:datastore') # connect to Google BigTable + # optional DAL('gae://namespace') + session.connect(request, response, db = db) # and store sessions and tickets there + ### or use the following lines to store sessions in Memcache + # from gluon.contrib.memdb import MEMDB + # from google.appengine.api.memcache import Client + # session.connect(request, response, db = MEMDB(Client())) +else: # else use a normal relational database + db = DAL('sqlite://storage.sqlite') # if not, use SQLite or other DB + +# by default give a view/generic.extension to all actions from localhost +# none otherwise. a pattern can be 'controller/function.extension' +response.generic_patterns = ['*'] if request.is_local else [] + +######################################################################### +## Here is sample code if you need for +## - email capabilities +## - authentication (registration, login, logout, ... ) +## - authorization (role based authorization) +## - services (xml, csv, json, xmlrpc, jsonrpc, amf, rss) +## - crud actions +## (more options discussed in gluon/tools.py) +######################################################################### + +from gluon.tools import Mail, Auth, Crud, Service, PluginManager, prettydate +mail = Mail() # mailer +auth = Auth(db) # authentication/authorization +crud = Crud(db) # for CRUD helpers using auth +service = Service() # for json, xml, jsonrpc, xmlrpc, amfrpc +plugins = PluginManager() # for configuring plugins + +mail.settings.server = 'logging' or 'smtp.gmail.com:587' # your SMTP server +mail.settings.sender = 'you@gmail.com' # your email +mail.settings.login = 'username:password' # your credentials or None + +auth.settings.hmac_key = '' # before define_tables() +auth.define_tables() # creates all needed tables +auth.settings.mailer = mail # for user email verification +auth.settings.registration_requires_verification = False +auth.settings.registration_requires_approval = False +auth.messages.verify_email = 'Click on the link http://'+request.env.http_host+URL('default','user',args=['verify_email'])+'/%(key)s to verify your email' +auth.settings.reset_password_requires_verification = True +auth.messages.reset_password = 'Click on the link http://'+request.env.http_host+URL('default','user',args=['reset_password'])+'/%(key)s to reset your password' + +######################################################################### +## If you need to use OpenID, Facebook, MySpace, Twitter, Linkedin, etc. +## register with janrain.com, uncomment and customize following +# from gluon.contrib.login_methods.rpx_account import RPXAccount +# auth.settings.actions_disabled = \ +# ['register','change_password','request_reset_password'] +# auth.settings.login_form = RPXAccount(request, api_key='...',domain='...', +# url = "http://localhost:8000/%s/default/user/login" % request.application) +## other login methods are in gluon/contrib/login_methods +######################################################################### + +crud.settings.auth = None # =auth to enforce authorization on crud + +######################################################################### +## Define your tables below (or better in another model file) for example +## +## >>> db.define_table('mytable',Field('myfield','string')) +## +## Fields can be 'string','text','password','integer','double','boolean' +## 'date','time','datetime','blob','upload', 'reference TABLENAME' +## There is an implicit 'id integer autoincrement' field +## Consult manual for more options, validators, etc. +## +## More API examples for controllers: +## +## >>> db.mytable.insert(myfield='value') +## >>> rows=db(db.mytable.myfield=='value').select(db.mytable.ALL) +## >>> for row in rows: print row.id, row.myfield +######################################################################### ADDED applications/welcome/models/menu.py Index: applications/welcome/models/menu.py ================================================================== --- applications/welcome/models/menu.py +++ applications/welcome/models/menu.py @@ -0,0 +1,126 @@ +# -*- coding: utf-8 -*- +# this file is released under public domain and you can use without limitations +######################################################################### +## Customize your APP title, subtitle and menus here +######################################################################### + +response.title = request.application +response.subtitle = T('customize me!') + +#http://dev.w3.org/html5/markup/meta.name.html +response.meta.author = 'you' +response.meta.description = 'Free and open source full-stack enterprise framework for agile development of fast, scalable, secure and portable database-driven web-based applications. Written and programmable in Python' +response.meta.keywords = 'web2py, python, framework' +response.meta.generator = 'Web2py Enterprise Framework' +response.meta.copyright = 'Copyright 2007-2010' + + +########################################## +## this is the main application menu +## add/remove items as required +########################################## + +response.menu = [ + (T('Home'), False, URL('default','index'), []) + ] + +########################################## +## this is here to provide shortcuts +## during development. remove in production +## +## mind that plugins may also affect menu +########################################## + +######################################### +## Make your own menus +########################################## + +response.menu+=[ + (T('This App'), False, URL('admin', 'default', 'design/%s' % request.application), + [ + (T('Controller'), False, + URL('admin', 'default', 'edit/%s/controllers/%s.py' \ + % (request.application,request.controller=='appadmin' and + 'default' or request.controller))), + (T('View'), False, + URL('admin', 'default', 'edit/%s/views/%s' \ + % (request.application,response.view))), + (T('Layout'), False, + URL('admin', 'default', 'edit/%s/views/layout.html' \ + % request.application)), + (T('Stylesheet'), False, + URL('admin', 'default', 'edit/%s/static/base.css' \ + % request.application)), + (T('DB Model'), False, + URL('admin', 'default', 'edit/%s/models/db.py' \ + % request.application)), + (T('Menu Model'), False, + URL('admin', 'default', 'edit/%s/models/menu.py' \ + % request.application)), + (T('Database'), False, + URL(request.application, 'appadmin', 'index')), + + (T('Errors'), False, + URL('admin', 'default', 'errors/%s' \ + % request.application)), + + (T('About'), False, + URL('admin', 'default', 'about/%s' \ + % request.application)), + + ] + )] + + +########################################## +## this is here to provide shortcuts to some resources +## during development. remove in production +## +## mind that plugins may also affect menu +########################################## + + +response.menu+=[(T('Resources'), False, None, + [ + (T('Documentation'), False, 'http://www.web2py.com/book', + [ + (T('Preface'), False, 'http://www.web2py.com/book/default/chapter/00'), + (T('Introduction'), False, 'http://www.web2py.com/book/default/chapter/01'), + (T('Python'), False, 'http://www.web2py.com/book/default/chapter/02'), + (T('Overview'), False, 'http://www.web2py.com/book/default/chapter/03'), + (T('The Core'), False, 'http://www.web2py.com/book/default/chapter/04'), + (T('The Views'), False, 'http://www.web2py.com/book/default/chapter/05'), + (T('Database'), False, 'http://www.web2py.com/book/default/chapter/06'), + (T('Forms and Validators'), False, 'http://www.web2py.com/book/default/chapter/07'), + (T('Access Control'), False, 'http://www.web2py.com/book/default/chapter/08'), + (T('Services'), False, 'http://www.web2py.com/book/default/chapter/09'), + (T('Ajax Recipes'), False, 'http://www.web2py.com/book/default/chapter/10'), + (T('Deployment Recipes'), False, 'http://www.web2py.com/book/default/chapter/11'), + (T('Other Recipes'), False, 'http://www.web2py.com/book/default/chapter/12'), + (T('Buy this book'), False, 'http://stores.lulu.com/web2py'), + ]), + + (T('Community'), False, None, + [ + (T('Groups'), False, 'http://www.web2py.com/examples/default/usergroups'), + (T('Twitter'), False, 'http://twitter.com/web2py'), + (T('Live chat'), False, 'http://mibbit.com/?channel=%23web2py&server=irc.mibbit.net'), + (T('User Voice'), False, 'http://web2py.uservoice.com/'), + ]), + + (T('Web2py'), False, 'http://www.web2py.com', + [ + (T('Download'), False, 'http://www.web2py.com/examples/default/download'), + (T('Support'), False, 'http://www.web2py.com/examples/default/support'), + (T('Quick Examples'), False, 'http://web2py.com/examples/default/examples'), + (T('FAQ'), False, 'http://web2py.com/AlterEgo'), + (T('Free Applications'), False, 'http://web2py.com/appliances'), + (T('Plugins'), False, 'http://web2py.com/plugins'), + (T('Recipes'), False, 'http://web2pyslices.com/'), + (T('Demo'), False, 'http://web2py.com/demo_admin'), + (T('Semantic'), False, 'http://web2py.com/semantic'), + (T('Layouts'), False, 'http://web2py.com/layouts'), + (T('Videos'), False, 'http://www.web2py.com/examples/default/videos/'), + ]), + ] + )] ADDED applications/welcome/modules/__init__.py Index: applications/welcome/modules/__init__.py ================================================================== --- applications/welcome/modules/__init__.py +++ applications/welcome/modules/__init__.py ADDED applications/welcome/static/css/base.css Index: applications/welcome/static/css/base.css ================================================================== --- applications/welcome/static/css/base.css +++ applications/welcome/static/css/base.css @@ -0,0 +1,556 @@ +@charset "UTF-8"; + +/* This file is contains the following sections: + +Update: Revision: 20101102 by Martin Mulone + +- The new revision contains: +- Html5, good practice and normalization support. +- Diferent hacks. +- The normalization and some tags come from + diferent sites so i keep the credits and comments. + but the base of support html5 come from: + http://html5boilerplate.com/ + +- ez.css (http://www.ez-css.org/layouts) +- reset common tags +- choose default fonts +- choose link style +- add bottom line to table rows +- labels bold and occasionally centered +- make all input fields the same size +- add proper separation between h1-h6 and text +- always indent the first line and add space below paragraphs +- bullets and numbers style and indent +- form and table padding +- code blocks +- left and right padding to quoted text +- page layout alignment, width and padding (change this for spaces) +- column widths (change this to use left_sidebar and right_sidebar) +- backrgound images and colors (change this for colors) +- web2py specific (.flash, .error) + +Notice: +- even if you use a different layout/css you may need classes .flash and .error +- this is all color neutral except for #349C01 (header, links, lines) +- there are two backrgound images: images/background.png and images/header.png + +License: This file is released under BSD and MIT + +*/ + +/* + credit is left where credit is due. + additionally, much inspiration was taken from these projects: + yui.yahooapis.com/2.8.1/build/base/base.css + camendesign.com/design/ + praegnanz.de/weblog/htmlcssjs-kickstart +*/ + +/* + html5doctor.com Reset Stylesheet (Eric Meyer's Reset Reloaded + HTML5 baseline) + v1.4 2009-07-27 | Authors: Eric Meyer & Richard Clark + html5doctor.com/html-5-reset-stylesheet/ +*/ + +html, body, div, span, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +abbr, address, cite, code, +del, dfn, em, img, ins, kbd, q, samp, +small, strong, sub, sup, var, +b, i, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, figcaption, figure, +footer, header, hgroup, menu, nav, section, summary, +time, mark, audio, video { + margin:0; + padding:0; + border:0; + outline:0; + font-size:100%; + vertical-align:baseline; + background:transparent; +} + +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section { + display:block; +} + +nav ul { list-style:none; } + +blockquote, q { quotes:none; } + +blockquote:before, blockquote:after, +q:before, q:after { content:''; content:none; } + +a { margin:0; padding:0; font-size:100%; vertical-align:baseline; background:transparent; } +a:hover { text-decoration: underline } + +ins { background-color:#ff9; color:#000; text-decoration:none; } + +mark { background-color:#ff9; color:#000; font-style:italic; font-weight:bold; } + +del { text-decoration: line-through; } + +abbr[title], dfn[title] { border-bottom:1px dotted #000; cursor:help; } + +/* tables still need cellspacing="0" in the markup */ +table { border-collapse:collapse; border-spacing:0; } + +hr { display:block; height:1px; border:0; border-top:1px solid #ccc; margin:1em 0; padding:0; } + +input, select { vertical-align:middle; } +/* END RESET CSS */ + + +/* +fonts.css from the YUI Library: developer.yahoo.com/yui/ + Please refer to developer.yahoo.com/yui/fonts/ for font sizing percentages + +There are three custom edits: + * remove arial, helvetica from explicit font stack + * make the line-height relative and unit-less + * remove the pre, code styles +*/ +body { font:13px/1.231 sans-serif; *font-size:small; } /* hack retained to preserve specificity */ + +/*table { font-size:inherit; font:100%; }*/ + +select, input, textarea, button { font:99% sans-serif; } + + +/* normalize monospace sizing + * en.wikipedia.org/wiki/MediaWiki_talk:Common.css/Archive_11#Teletype_style_fix_for_Chrome + */ +pre, code, kbd, samp { font-family: monospace, sans-serif; } + +/* + * minimal base styles + */ + +/* #444 looks better than black: twitter.com/H_FJ/statuses/11800719859 */ +body, select, input, textarea { color:#444; } + +/* Headers (h1,h2,etc) have no default font-size or margin, + you'll want to define those yourself. */ + +/* www.aestheticallyloyal.com/public/optimize-legibility/ */ +h1,h2,h3,h4,h5,h6 { font-weight: bold; } + +/* always force a scrollbar in non-IE */ +html { overflow-y: scroll; } + +/* Accessible focus treatment: people.opera.com/patrickl/experiments/keyboard/test */ +a:hover, a:active { outline: none; } + +a, a:active, a:visited { color:#607890; } +a:hover { color:#036; } + +ul, ol { margin-left: 1.8em; } +ol { list-style-type: decimal; } + +/* Remove margins for navigation lists */ +nav ul, nav li { margin: 0; } + +small { font-size:85%; } +strong, th { font-weight: bold; } + +td, td img { vertical-align:top; } + +sub { vertical-align: sub; font-size: smaller; } +sup { vertical-align: super; font-size: smaller; } + +pre { + padding: 15px; + + /* www.pathf.com/blogs/2008/05/formatting-quoted-code-in-blog-posts-css21-white-space-pre-wrap/ */ + white-space: pre; /* CSS2 */ + white-space: pre-wrap; /* CSS 2.1 */ + white-space: pre-line; /* CSS 3 (and 2.1 as well, actually) */ + word-wrap: break-word; /* IE */ +} + +textarea { overflow: auto; } /* thnx ivannikolic! www.sitepoint.com/blogs/2010/08/20/ie-remove-textarea-scrollbars/ */ + +.ie6 legend, .ie7 legend { margin-left: -7px; } /* thnx ivannikolic! */ + +/* align checkboxes, radios, text inputs with their label + by: Thierry Koblentz tjkdesign.com/ez-css/css/base.css */ +input[type="radio"] { vertical-align: text-bottom; } +input[type="checkbox"] { vertical-align: bottom; } +.ie7 input[type="checkbox"] { vertical-align: baseline; } +.ie6 input { vertical-align: text-bottom; } + +/* hand cursor on clickable input elements */ +label, input[type=button], input[type=submit], button { cursor: pointer; } + +/* webkit browsers add a 2px margin outside the chrome of form elements */ +button, input, select, textarea { margin: 0; } + +/* colors for form validity */ +input:valid, textarea:valid { } +input:invalid, textarea:invalid { + border-radius: 1px; + -moz-box-shadow: 0px 0px 5px red; + -webkit-box-shadow: 0px 0px 5px red; + box-shadow: 0px 0px 5px red; +} +.no-boxshadow input:invalid, +.no-boxshadow textarea:invalid { background-color: #f0dddd; } + + +/* These selection declarations have to be separate. + No text-shadow: twitter.com/miketaylr/status/12228805301 + Also: hot pink. */ +::-moz-selection{ background: #555; color:#fff; text-shadow: none; } +::selection { background:#555; color:#fff; text-shadow: none; } + +/* j.mp/webkit-tap-highlight-color */ +a:link { -webkit-tap-highlight-color: #555; } + + +/* make buttons play nice in IE: + www.viget.com/inspire/styling-the-button-element-in-internet-explorer/ */ +button { width: auto; overflow: visible; } + +/* bicubic resizing for non-native sized IMG: + code.flickr.com/blog/2008/11/12/on-ui-quality-the-little-things-client-side-image-resizing/ */ +.ie7 img { -ms-interpolation-mode: bicubic; } + +/* + * Non-semantic helper classes + */ + +/* for image replacement */ +.ir { display:block; text-indent:-999em; overflow:hidden; background-repeat: no-repeat; } + +/* Hide for both screenreaders and browsers + css-discuss.incutio.com/wiki/Screenreader_Visibility */ +.hidden { display:none; } + +/* Hide only visually, but have it available for screenreaders + www.webaim.org/techniques/css/invisiblecontent/ + Solution from: j.mp/visuallyhidden - Thanks Jonathan Neal! */ +.visuallyhidden { position:absolute !important; + clip: rect(1px 1px 1px 1px); /* IE6, IE7 */ + clip: rect(1px, 1px, 1px, 1px); } + +/* Hide visually and from screenreaders, but maintain layout */ +.invisible { visibility: hidden; } + +/* >> The Magnificent CLEARFIX: Updated to prevent margin-collapsing on child elements << j.mp/bestclearfix */ +.clearfix:before, .clearfix:after { + content: "\0020"; display: block; height: 0; visibility: hidden; +} + +.clearfix:after { clear: both; } +/* Fix clearfix: blueprintcss.lighthouseapp.com/projects/15318/tickets/5-extra-margin-padding-bottom-of-page */ +.clearfix { zoom: 1; } + + +/*********** layout info (ez.css) ***********/ +/* 2009 -2010 (c) | ez-css.org + * ez-plug-min.css :: version 1.1 :: 01182010 + */ +.ez-wr:after,.ez-box:after{content:".";display:block;height:0;clear:both;visibility:hidden}.ez-wr,.ez-box,.ez-last{display:inline-block;min-height:0}/* \*/ * html .ez-wr,* html .ez-box,* html .ez-last{height:1%}.ez-wr,.ez-box,.ez-last{display:block}/* */.ez-oh{overflow:hidden}* html .ez-oh{overflow:visible}.ez-oa{overflow:auto}.ez-dt{display:table}.ez-it{display:inline-table}.ez-tc{display:table-cell}.ez-ib{display:inline-block}.ez-fl{float:left}* html .ez-fl{margin-right:-3px}.ez-fr{float:right}* html .ez-fr{margin-left:-3px}.ez-25{width:25%}.ez-33{width:33.33%}.ez-50{width:50%}.ez-66{width:66.66%}.ez-75{width:75%}.ez-negmr{margin-right:-1px}* html .ez-negmr{margin-right:-4px}.ez-negmx{margin-right:-1px}.ez-negml{margin-left:-1px}* html .ez-negml{margin-left:-4px} + + +/*********** add bottom line to table rows ***********/ +th, td { padding: 0.1em 0.5em 0.1em 0.5em;} + +/*********** labels bold and occasionally centered ***********/ +label { + white-space: nowrap; +} +label, b, th { + font-weight: bold; +} +thead th { + text-align: center; + border-bottom: 1px solid #444; +} +/*********** forms and table padding ***********/ +form, table { + padding: 5px 10px 5px 10px; +} + +/*********** code blocks ***********/ +code { + padding: 3px 5px; + font-family: Andale Mono, monospace; + font-size: 0.9em; +} + +/*********** left and right padding to quoted text ***********/ +blockquote { + background: #cccccc; + border-left: 30px transparent; + border-right: 30px transparent; + /*padding: 5px;*/ +} + +input[type=text], input[type=password], textarea, select { + margin: 2px 15px 2px 5px; + width: 280px; + background: #fff; + color: #555; + border: 1px solid #dedede; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + border-radius: 2px; + font-size: 12px; +} + +input[type=text], input[type=password] { + height: 16px; +} + +select[multiple=multiple] { + height: 90px; +} + +input[type=submit], input[type=button], button { + margin: 0px; + /*width: 85px;*/ + height: 22px; + background: #eaeaea; + color: #555; + border: 1px solid #dedede; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + border-radius: 2px; +} + +fieldset { border: 1px solid #dedede; padding: 6px; } +legend { font-weight: bold; } + +input:focus, textarea:focus { background: #fafafa; } + +p {text-indent:30px;} + +p, blockquote { + margin-bottom: 10px; +} + +h1,h2,h3,h4,h5,h6 { line-height: 170%; } +h1 {font-size: 2.0em;} +h2 {font-size: 1.8em;} +h3 {font-size: 1.4em;} +h4 {font-size: 1.2em;} +h5 {font-size: 1.0em;} +h6 {font-size: 0.8em;} + +/*********** page layout alignment, width and padding ***********/ +/*body {background-color: #000;}*/ +#container, #header, #page, #content, #statusbar, +#footer, #wrapper { display:block; line-height: 170%; } +#wrapper {width: 900px;} +#container { + margin: 0 auto; + padding: 0; +} +#wrapper {margin: 0 auto;} +#wrapper {background-color: #fff; padding: 5px;} +#statusbar { margin: 5px 0px 20px 0px;} +#footer { + margin-top: 30px; + padding: 5px; +} +#statusbar, #footer { + background: #eaeaea; + border-top: 1px #aaa solid; +} +#logo { + width: 68px; + height: 62px; + background: url(../images/logo.png); +} +#appname { + color: #cccccc; +} + +#right_sidebar { width: 160px; float:right; display: none; } +#left_sidebar { width: 160px; float:left; display: none; } +#content { float: left; /*width: 740px;*//*width: 63%;*/ /*width: 640px; float:left;*/ } /* uncomment this if you are going to use sidebars */ + +.auth_navbar { + top: 0px; + float: right; + padding: 3px 10px 3px 10px; +} + +/*********** web2py specific ***********/ +div.flash { + font-weight: bold; + display: none; + position: fixed; + padding: 10px; + top: 40px; + right: 10px; + min-width: 280px; + opacity: 0.85; + margin: 0px 0px 10px 10px; + color: #fff; + vertical-align: middle; + cursor: pointer; + background: #000; + border: 2px solid #fff; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + z-index: 2; +} +div.error { + background-color: red; + color: white; + padding: 3px; +} + +/*************************** + * CSS 3 Buttons + * http://github.com/michenriksen/css3buttons + * created by Michael Henriksen + * License: Unlicense + * + * *******************/ + +a.button { display: inline-block; padding: 3px 5px 3px 5px; font-family: 'lucida grande', tahoma, verdana, arial, sans-serif; font-size: 12px; color: #3C3C3D; text-shadow: 1px 1px 0 #FFFFFF; background: #ECECEC url('../images/css3buttons_backgrounds.png') 0 0 no-repeat; white-space: nowrap; overflow: visible; cursor: pointer; text-decoration: none; border: 1px solid #CACACA; -webkit-border-radius: 2px; -moz-border-radius: 2px; -webkit-background-clip: padding-box; border-radius: 2px; outline: none; position: relative; zoom: 1; *display: inline; } +a.button.primary { font-weight: bold } +a.button:hover { color: #FFFFFF; border-color: #388AD4; text-decoration: none; text-shadow: -1px -1px 0 rgba(0,0,0,0.3); background-position: 0 -40px; background-color: #2D7DC5; } +a.button:active, +a.button.active { background-position: 0 -81px; border-color: #347BBA; background-color: #0F5EA2; color: #FFFFFF; text-shadow: none; } +a.button:active { top: 1px } +a.button.negative:hover { color: #FFFFFF; background-position: 0 -121px; background-color: #D84743; border-color: #911D1B; } +a.button.negative:active, +a.button.negative.active { background-position: 0 -161px; background-color: #A5211E; border-color: #911D1B; } +a.button.pill { -webkit-border-radius: 19px; -moz-border-radius: 19px; border-radius: 19px; padding: 2px 10px 2px 10px; } +a.button.left { -webkit-border-bottom-right-radius: 0px; -webkit-border-top-right-radius: 0px; -moz-border-radius-bottomright: 0px; -moz-border-radius-topright: 0px; border-bottom-right-radius: 0px; border-top-right-radius: 0px; margin-right: 0px; } +a.button.middle { margin-right: 0px; margin-left: 0px; -webkit-border-radius: 0px; -moz-border-radius: 0px; border-radius: 0px; border-right: none; border-left: none; } +a.button.right { -webkit-border-bottom-left-radius: 0px; -webkit-border-top-left-radius: 0px; -moz-border-radius-bottomleft: 0px; -moz-border-radius-topleft: 0px; border-top-left-radius: 0px; border-bottom-left-radius: 0px; margin-left: 0px; } +a.button.left:active, +a.button.middle:active, +a.button.right:active { top: 0px } +a.button.big { font-size: 16px; padding-left: 17px; padding-right: 17px; } +a.button span.icon { display: inline-block; width: 14px; height: 12px; margin: auto 7px auto auto; position: relative; top: 2px; background-image: url('../images/css3buttons_icons.png'); background-repeat: no-repeat; } +a.big.button span.icon { top: 0px } +a.button span.icon.book { background-position: 0 0 } +a.button:hover span.icon.book { background-position: 0 -15px } +a.button span.icon.calendar { background-position: 0 -30px } +a.button:hover span.icon.calendar { background-position: 0 -45px } +a.button span.icon.chat { background-position: 0 -60px } +a.button:hover span.icon.chat { background-position: 0 -75px } +a.button span.icon.check { background-position: 0 -90px } +a.button:hover span.icon.check { background-position: 0 -103px } +a.button span.icon.clock { background-position: 0 -116px } +a.button:hover span.icon.clock { background-position: 0 -131px } +a.button span.icon.cog { background-position: 0 -146px } +a.button:hover span.icon.cog { background-position: 0 -161px } +a.button span.icon.comment { background-position: 0 -176px } +a.button:hover span.icon.comment { background-position: 0 -190px } +a.button span.icon.cross { background-position: 0 -204px } +a.button:hover span.icon.cross { background-position: 0 -219px } +a.button span.icon.downarrow { background-position: 0 -234px } +a.button:hover span.icon.downarrow { background-position: 0 -249px } +a.button span.icon.fork { background-position: 0 -264px } +a.button:hover span.icon.fork { background-position: 0 -279px } +a.button span.icon.heart { background-position: 0 -294px } +a.button:hover span.icon.heart { background-position: 0 -308px } +a.button span.icon.home { background-position: 0 -322px } +a.button:hover span.icon.home { background-position: 0 -337px } +a.button span.icon.key { background-position: 0 -352px } +a.button:hover span.icon.key { background-position: 0 -367px } +a.button span.icon.leftarrow { background-position: 0 -382px } +a.button:hover span.icon.leftarrow { background-position: 0 -397px } +a.button span.icon.lock { background-position: 0 -412px } +a.button:hover span.icon.lock { background-position: 0 -427px } +a.button span.icon.loop { background-position: 0 -442px } +a.button:hover span.icon.loop { background-position: 0 -457px } +a.button span.icon.magnifier { background-position: 0 -472px } +a.button:hover span.icon.magnifier { background-position: 0 -487px } +a.button span.icon.mail { background-position: 0 -502px } +a.button:hover span.icon.mail { background-position: 0 -514px } +a.button span.icon.move { background-position: 0 -526px } +a.button:hover span.icon.move { background-position: 0 -541px } +a.button span.icon.pen { background-position: 0 -556px } +a.button:hover span.icon.pen { background-position: 0 -571px } +a.button span.icon.pin { background-position: 0 -586px } +a.button:hover span.icon.pin { background-position: 0 -601px } +a.button span.icon.plus { background-position: 0 -616px } +a.button:hover span.icon.plus { background-position: 0 -631px } +a.button span.icon.reload { background-position: 0 -646px } +a.button:hover span.icon.reload { background-position: 0 -660px } +a.button span.icon.rightarrow { background-position: 0 -674px } +a.button:hover span.icon.rightarrow { background-position: 0 -689px } +a.button span.icon.rss { background-position: 0 -704px } +a.button:hover span.icon.rss { background-position: 0 -719px } +a.button span.icon.tag { background-position: 0 -734px } +a.button:hover span.icon.tag { background-position: 0 -749px } +a.button span.icon.trash { background-position: 0 -764px } +a.button:hover span.icon.trash { background-position: 0 -779px } +a.button span.icon.unlock { background-position: 0 -794px } +a.button:hover span.icon.unlock { background-position: 0 -809px } +a.button span.icon.uparrow { background-position: 0 -824px } +a.button:hover span.icon.uparrow { background-position: 0 -839px } +a.button span.icon.user { background-position: 0 -854px } +a.button:hover span.icon.user { background-position: 0 -869px } + + + + + + +/***************************************************** + * HERE YOU CAN START TO WRITE YOUR OWN DIVS + */ + + + + + + + +/* + * Media queries for responsive design + */ + +@media all and (orientation:portrait) { + /* Style adjustments for portrait mode goes here */ + +} + +@media all and (orientation:landscape) { + /* Style adjustments for landscape mode goes here */ + +} + +/* Grade-A Mobile Browsers (Opera Mobile, iPhone Safari, Android Chrome) + Consider this: www.cloudfour.com/css-media-query-for-mobile-is-fools-gold/ */ +@media screen and (max-device-width: 480px) { + + + /* Uncomment if you don't want iOS and WinMobile to mobile-optimize the text for you + j.mp/textsizeadjust + html { -webkit-text-size-adjust:none; -ms-text-size-adjust:none; } */ +} + + +/* + * print styles + * inlined to avoid required HTTP connection www.phpied.com/delay-loading-your-print-css/ + */ +@media print { + * { background: transparent !important; color: #444 !important; text-shadow: none !important; } + a, a:visited { color: #444 !important; text-decoration: underline; } + a:after { content: " (" attr(href) ")"; } + abbr:after { content: " (" attr(title) ")"; } + .ir a:after { content: ""; } /* Don't show links for images */ + pre, blockquote { border: 1px solid #999; page-break-inside: avoid; } + thead { display: table-header-group; } /* css-discuss.incutio.com/wiki/Printing_Tables */ + tr, img { page-break-inside: avoid; } + @page { margin: 0.5cm; } + p, h2, h3 { orphans: 3; widows: 3; } + h2, h3{ page-break-after: avoid; } +} + ADDED applications/welcome/static/css/calendar.css Index: applications/welcome/static/css/calendar.css ================================================================== --- applications/welcome/static/css/calendar.css +++ applications/welcome/static/css/calendar.css @@ -0,0 +1,1 @@ +.calendar{z-index:99;position:relative;display:none;border-top:2px solid #fff;border-right:2px solid #000;border-bottom:2px solid #000;border-left:2px solid #fff;font-size:11px;color:#000;cursor:default;background:#d4d0c8;font-family:tahoma,verdana,sans-serif;}.calendar table{border-top:1px solid #000;border-right:1px solid #fff;border-bottom:1px solid #fff;border-left:1px solid #000;font-size:11px;color:#000;cursor:default;background:#d4d0c8;font-family:tahoma,verdana,sans-serif;}.calendar .button{text-align:center;padding:1px;border-top:1px solid #fff;border-right:1px solid #000;border-bottom:1px solid #000;border-left:1px solid #fff;}.calendar .nav{background:transparent}.calendar thead .title{font-weight:bold;padding:1px;border:1px solid #000;background:#848078;color:#fff;text-align:center;}.calendar thead .name{border-bottom:1px solid #000;padding:2px;text-align:center;background:#f4f0e8;}.calendar thead .weekend{color:#f00;}.calendar thead .hilite{border-top:2px solid #fff;border-right:2px solid #000;border-bottom:2px solid #000;border-left:2px solid #fff;padding:0;background-color:#e4e0d8;}.calendar thead .active{padding:2px 0 0 2px;border-top:1px solid #000;border-right:1px solid #fff;border-bottom:1px solid #fff;border-left:1px solid #000;background-color:#c4c0b8;}.calendar tbody .day{width:2em;text-align:right;padding:2px 4px 2px 2px;}.calendar tbody .day.othermonth{font-size:80%;color:#aaa;}.calendar tbody .day.othermonth.oweekend{color:#faa;}.calendar table .wn{padding:2px 3px 2px 2px;border-right:1px solid #000;background:#f4f0e8;}.calendar tbody .rowhilite td{background:#e4e0d8;}.calendar tbody .rowhilite td.wn{background:#d4d0c8;}.calendar tbody td.hilite{padding:1px 3px 1px 1px;border-top:1px solid #fff;border-right:1px solid #000;border-bottom:1px solid #000;border-left:1px solid #fff;}.calendar tbody td.active{padding:2px 2px 0 2px;border-top:1px solid #000;border-right:1px solid #fff;border-bottom:1px solid #fff;border-left:1px solid #000;}.calendar tbody td.selected{font-weight:bold;border-top:1px solid #000;border-right:1px solid #fff;border-bottom:1px solid #fff;border-left:1px solid #000;padding:2px 2px 0 2px;background:#e4e0d8;}.calendar tbody td.weekend{color:#f00;}.calendar tbody td.today{font-weight:bold;color:#00f;}.calendar tbody .disabled{color:#999;}.calendar tbody .emptycell{visibility:hidden;}.calendar tbody .emptyrow{display:none;}.calendar tfoot .ttip{background:#f4f0e8;padding:1px;border:1px solid #000;background:#848078;color:#fff;text-align:center;}.calendar tfoot .hilite{border-top:1px solid #fff;border-right:1px solid #000;border-bottom:1px solid #000;border-left:1px solid #fff;padding:1px;background:#e4e0d8;}.calendar tfoot .active{padding:2px 0 0 2px;border-top:1px solid #000;border-right:1px solid #fff;border-bottom:1px solid #fff;border-left:1px solid #000;}.calendar .combo{position:absolute;display:none;width:4em;top:0;left:0;cursor:default;border-top:1px solid #fff;border-right:1px solid #000;border-bottom:1px solid #000;border-left:1px solid #fff;background:#e4e0d8;font-size:90%;padding:1px;z-index:100;}.calendar .combo .label,.calendar .combo .label-IEfix{text-align:center;padding:1px;}.calendar .combo .label-IEfix{width:4em;}.calendar .combo .active{background:#c4c0b8;padding:0;border-top:1px solid #000;border-right:1px solid #fff;border-bottom:1px solid #fff;border-left:1px solid #000;}.calendar .combo .hilite{background:#048;color:#fea;}.calendar td.time{border-top:1px solid #000;padding:1px 0;text-align:center;background-color:#f4f0e8;}.calendar td.time .hour,.calendar td.time .minute,.calendar td.time .ampm{padding:0 3px 0 4px;border:1px solid #889;font-weight:bold;background-color:#fff;}.calendar td.time .ampm{text-align:center;}.calendar td.time .colon{padding:0 2px 0 3px;font-weight:bold;}.calendar td.time span.hilite{border-color:#000;background-color:#766;color:#fff;}.calendar td.time span.active{border-color:#f00;background-color:#000;color:#0f0;}#CP_hourcont{z-index:99;padding:0;position:absolute;border:1px dashed #666;background-color:#eee;display:none;}#CP_minutecont{z-index:99;background-color:#ddd;padding:1px;position:absolute;width:45px;display:none;}.floatleft{float:left;}.CP_hour{z-index:99;padding:1px;font-family:Arial,Helvetica,sans-serif;font-size:9px;white-space:nowrap;cursor:pointer;width:35px;}.CP_minute{z-index:99;padding:1px;font-family:Arial,Helvetica,sans-serif;font-size:9px;white-space:nowrap;cursor:pointer;width:auto;}.CP_over{background-color:#fff;z-index:99} ADDED applications/welcome/static/css/handheld.css Index: applications/welcome/static/css/handheld.css ================================================================== --- applications/welcome/static/css/handheld.css +++ applications/welcome/static/css/handheld.css @@ -0,0 +1,7 @@ + +* { + float: none; /* Screens are not big enough to account for floats */ + font-size: 80%; /* Slightly reducing font size to reduce need to scroll */ + background: #fff; /* As much contrast as possible */ + color: #000; +} ADDED applications/welcome/static/css/superfish-navbar.css Index: applications/welcome/static/css/superfish-navbar.css ================================================================== --- applications/welcome/static/css/superfish-navbar.css +++ applications/welcome/static/css/superfish-navbar.css @@ -0,0 +1,93 @@ + +/*** adding the class sf-navbar in addition to sf-menu creates an all-horizontal nav-bar menu ***/ +.sf-navbar { + background: #BDD2FF; + height: 2.5em; + padding-bottom: 2.5em; + position: relative; +} +.sf-navbar li { + background: #AABDE6; + position: static; +} +.sf-navbar a { + border-top: none; +} +.sf-navbar li ul { + width: 44em; /*IE6 soils itself without this*/ +} +.sf-navbar li li { + background: #BDD2FF; + position: relative; +} +.sf-navbar li li ul { + width: 13em; +} +.sf-navbar li li li { + width: 100%; +} +.sf-navbar ul li { + width: auto; + float: left; +} +.sf-navbar a, .sf-navbar a:visited { + border: none; +} +.sf-navbar li.current { + background: #BDD2FF; +} +.sf-navbar li:hover, +.sf-navbar li.sfHover, +.sf-navbar li li.current, +.sf-navbar a:focus, .sf-navbar a:hover, .sf-navbar a:active { + background: #BDD2FF; +} +.sf-navbar ul li:hover, +.sf-navbar ul li.sfHover, +ul.sf-navbar ul li:hover li, +ul.sf-navbar ul li.sfHover li, +.sf-navbar ul a:focus, .sf-navbar ul a:hover, .sf-navbar ul a:active { + background: #D1DFFF; +} +ul.sf-navbar li li li:hover, +ul.sf-navbar li li li.sfHover, +.sf-navbar li li.current li.current, +.sf-navbar ul li li a:focus, .sf-navbar ul li li a:hover, .sf-navbar ul li li a:active { + background: #E6EEFF; +} +ul.sf-navbar .current ul, +ul.sf-navbar ul li:hover ul, +ul.sf-navbar ul li.sfHover ul { + left: 0; + top: 2.5em; /* match top ul list item height */ +} +ul.sf-navbar .current ul ul { + top: -999em; +} + +.sf-navbar li li.current > a { + font-weight: bold; +} + +/*** point all arrows down ***/ +/* point right for anchors in subs */ +.sf-navbar ul .sf-sub-indicator { background-position: -10px -100px; } +.sf-navbar ul a > .sf-sub-indicator { background-position: 0 -100px; } +/* apply hovers to modern browsers */ +.sf-navbar ul a:focus > .sf-sub-indicator, +.sf-navbar ul a:hover > .sf-sub-indicator, +.sf-navbar ul a:active > .sf-sub-indicator, +.sf-navbar ul li:hover > a > .sf-sub-indicator, +.sf-navbar ul li.sfHover > a > .sf-sub-indicator { + background-position: -10px -100px; /* arrow hovers for modern browsers*/ +} + +/*** remove shadow on first submenu ***/ +.sf-navbar > li > ul { + background: transparent; + padding: 0; + -moz-border-radius-bottomleft: 0; + -moz-border-radius-topright: 0; + -webkit-border-top-right-radius: 0; + -webkit-border-bottom-left-radius: 0; +} ADDED applications/welcome/static/css/superfish-vertical.css Index: applications/welcome/static/css/superfish-vertical.css ================================================================== --- applications/welcome/static/css/superfish-vertical.css +++ applications/welcome/static/css/superfish-vertical.css @@ -0,0 +1,23 @@ +/*** adding sf-vertical in addition to sf-menu creates a vertical menu ***/ +.sf-vertical, .sf-vertical li { + width: 10em; +} +/* this lacks ul at the start of the selector, so the styles from the main CSS file override it where needed */ +.sf-vertical li:hover ul, +.sf-vertical li.sfHover ul { + left: 10em; /* match ul width */ + top: 0; +} + +/*** alter arrow directions ***/ +.sf-vertical .sf-sub-indicator { background-position: -10px 0; } /* IE6 gets solid image only */ +.sf-vertical a > .sf-sub-indicator { background-position: 0 0; } /* use translucent arrow for modern browsers*/ + +/* hover arrow direction for modern browsers*/ +.sf-vertical a:focus > .sf-sub-indicator, +.sf-vertical a:hover > .sf-sub-indicator, +.sf-vertical a:active > .sf-sub-indicator, +.sf-vertical li:hover > a > .sf-sub-indicator, +.sf-vertical li.sfHover > a > .sf-sub-indicator { + background-position: -10px 0; /* arrow hovers for modern browsers*/ +} ADDED applications/welcome/static/css/superfish.css Index: applications/welcome/static/css/superfish.css ================================================================== --- applications/welcome/static/css/superfish.css +++ applications/welcome/static/css/superfish.css @@ -0,0 +1,139 @@ + +/*** ESSENTIAL STYLES ***/ +.sf-menu, .sf-menu * { + margin: 0; + padding: 0; + list-style: none; +} +.sf-menu { + line-height: 1.0; +} +.sf-menu ul { + position: absolute; + top: -999em; + width: 10em; /* left offset of submenus need to match (see below) */ +} +.sf-menu ul li { + width: 100%; +} +.sf-menu li:hover { + visibility: inherit; /* fixes IE7 'sticky bug' */ +} +.sf-menu li { + float: left; + position: relative; +} +.sf-menu a { + display: block; + position: relative; +} +.sf-menu li:hover ul, +.sf-menu li.sfHover ul { + left: 0; + top: 2.5em; /* match top ul list item height */ + z-index: 99; +} +ul.sf-menu li:hover li ul, +ul.sf-menu li.sfHover li ul { + top: -999em; +} +ul.sf-menu li li:hover ul, +ul.sf-menu li li.sfHover ul { + left: 10em; /* match ul width */ + top: 0; +} +ul.sf-menu li li:hover li ul, +ul.sf-menu li li.sfHover li ul { + top: -999em; +} +ul.sf-menu li li li:hover ul, +ul.sf-menu li li li.sfHover ul { + left: 10em; /* match ul width */ + top: 0; +} + +/*** DEMO SKIN ***/ +.sf-menu { + float: left; + /*margin-bottom: 1em;*/ +} +.sf-menu a { + border-left: 1px solid #fff; + /*border-top: 1px solid #CFDEFF;*/ + padding: .75em 1em; + text-decoration:none; +} +.sf-menu a, .sf-menu a:visited { /* visited pseudo selector so IE6 applies text colour*/ + color: #275b90;/*#13a;*/ +} +.sf-menu li { + background: #dadada;/*#BDD2FF;*/ +} +.sf-menu li li { + background: #AABDE6; +} +.sf-menu li li a { + /*color: #13a;*/ +} +.sf-menu li li li { + background: #9AAEDB; +} +.sf-menu li:hover, .sf-menu li.sfHover, +.sf-menu a:focus, .sf-menu a:hover, .sf-menu a:active { + background: #CFDEFF; + outline: 0; +} + +/*** arrows **/ +.sf-menu a.sf-with-ul { + padding-right: 2.25em; + min-width: 1px; /* trigger IE7 hasLayout so spans position accurately */ +} +.sf-sub-indicator { + position: absolute; + display: block; + right: .75em; + top: 1.05em; /* IE6 only */ + width: 10px; + height: 10px; + text-indent: -999em; + overflow: hidden; + background: url('../images/arrows-ffffff.png') no-repeat -10px -100px; /* 8-bit indexed alpha png. IE6 gets solid image only */ +} +a > .sf-sub-indicator { /* give all except IE6 the correct values */ + top: .8em; + background-position: 0 -100px; /* use translucent arrow for modern browsers*/ +} +/* apply hovers to modern browsers */ +a:focus > .sf-sub-indicator, +a:hover > .sf-sub-indicator, +a:active > .sf-sub-indicator, +li:hover > a > .sf-sub-indicator, +li.sfHover > a > .sf-sub-indicator { + background-position: -10px -100px; /* arrow hovers for modern browsers*/ +} + +/* point right for anchors in subs */ +.sf-menu ul .sf-sub-indicator { background-position: -10px 0; } +.sf-menu ul a > .sf-sub-indicator { background-position: 0 0; } +/* apply hovers to modern browsers */ +.sf-menu ul a:focus > .sf-sub-indicator, +.sf-menu ul a:hover > .sf-sub-indicator, +.sf-menu ul a:active > .sf-sub-indicator, +.sf-menu ul li:hover > a > .sf-sub-indicator, +.sf-menu ul li.sfHover > a > .sf-sub-indicator { + background-position: -10px 0; /* arrow hovers for modern browsers*/ +} + +/*** shadows for all but IE6 ***/ +.sf-shadow ul { + background: url('../images/shadow.png') no-repeat bottom right; + padding: 0 8px 9px 0; + -moz-border-radius-bottomleft: 17px; + -moz-border-radius-topright: 17px; + -webkit-border-top-right-radius: 17px; + -webkit-border-bottom-left-radius: 17px; +} +.sf-shadow ul.sf-shadow-off { + background: transparent; +} ADDED applications/welcome/static/favicon.ico Index: applications/welcome/static/favicon.ico ================================================================== --- applications/welcome/static/favicon.ico +++ applications/welcome/static/favicon.ico cannot compute difference between binary files ADDED applications/welcome/static/images/arrows-ffffff.png Index: applications/welcome/static/images/arrows-ffffff.png ================================================================== --- applications/welcome/static/images/arrows-ffffff.png +++ applications/welcome/static/images/arrows-ffffff.png cannot compute difference between binary files ADDED applications/welcome/static/images/css3buttons_backgrounds.png Index: applications/welcome/static/images/css3buttons_backgrounds.png ================================================================== --- applications/welcome/static/images/css3buttons_backgrounds.png +++ applications/welcome/static/images/css3buttons_backgrounds.png cannot compute difference between binary files ADDED applications/welcome/static/images/css3buttons_icons.png Index: applications/welcome/static/images/css3buttons_icons.png ================================================================== --- applications/welcome/static/images/css3buttons_icons.png +++ applications/welcome/static/images/css3buttons_icons.png cannot compute difference between binary files ADDED applications/welcome/static/images/error.png Index: applications/welcome/static/images/error.png ================================================================== --- applications/welcome/static/images/error.png +++ applications/welcome/static/images/error.png cannot compute difference between binary files ADDED applications/welcome/static/images/ok.png Index: applications/welcome/static/images/ok.png ================================================================== --- applications/welcome/static/images/ok.png +++ applications/welcome/static/images/ok.png cannot compute difference between binary files ADDED applications/welcome/static/images/poweredby.png Index: applications/welcome/static/images/poweredby.png ================================================================== --- applications/welcome/static/images/poweredby.png +++ applications/welcome/static/images/poweredby.png cannot compute difference between binary files ADDED applications/welcome/static/images/shadow.png Index: applications/welcome/static/images/shadow.png ================================================================== --- applications/welcome/static/images/shadow.png +++ applications/welcome/static/images/shadow.png cannot compute difference between binary files ADDED applications/welcome/static/images/warn.png Index: applications/welcome/static/images/warn.png ================================================================== --- applications/welcome/static/images/warn.png +++ applications/welcome/static/images/warn.png cannot compute difference between binary files ADDED applications/welcome/static/images/warning.png Index: applications/welcome/static/images/warning.png ================================================================== --- applications/welcome/static/images/warning.png +++ applications/welcome/static/images/warning.png cannot compute difference between binary files ADDED applications/welcome/static/js/calendar.js Index: applications/welcome/static/js/calendar.js ================================================================== --- applications/welcome/static/js/calendar.js +++ applications/welcome/static/js/calendar.js cannot compute difference between binary files ADDED applications/welcome/static/js/dd_belatedpng.js Index: applications/welcome/static/js/dd_belatedpng.js ================================================================== --- applications/welcome/static/js/dd_belatedpng.js +++ applications/welcome/static/js/dd_belatedpng.js @@ -0,0 +1,13 @@ +/** +* DD_belatedPNG: Adds IE6 support: PNG images for CSS background-image and HTML . +* Author: Drew Diller +* Email: drew.diller@gmail.com +* URL: http://www.dillerdesign.com/experiment/DD_belatedPNG/ +* Version: 0.0.8a +* Licensed under the MIT License: http://dillerdesign.com/experiment/DD_belatedPNG/#license +* +* Example usage: +* DD_belatedPNG.fix('.png_bg'); // argument is a CSS selector +* DD_belatedPNG.fixPng( someNode ); // argument is an HTMLDomElement +**/ +var DD_belatedPNG={ns:"DD_belatedPNG",imgSize:{},delay:10,nodesFixed:0,createVmlNameSpace:function(){if(document.namespaces&&!document.namespaces[this.ns]){document.namespaces.add(this.ns,"urn:schemas-microsoft-com:vml")}},createVmlStyleSheet:function(){var b,a;b=document.createElement("style");b.setAttribute("media","screen");document.documentElement.firstChild.insertBefore(b,document.documentElement.firstChild.firstChild);if(b.styleSheet){b=b.styleSheet;b.addRule(this.ns+"\\:*","{behavior:url(#default#VML)}");b.addRule(this.ns+"\\:shape","position:absolute;");b.addRule("img."+this.ns+"_sizeFinder","behavior:none; border:none; position:absolute; z-index:-1; top:-10000px; visibility:hidden;");this.screenStyleSheet=b;a=document.createElement("style");a.setAttribute("media","print");document.documentElement.firstChild.insertBefore(a,document.documentElement.firstChild.firstChild);a=a.styleSheet;a.addRule(this.ns+"\\:*","{display: none !important;}");a.addRule("img."+this.ns+"_sizeFinder","{display: none !important;}")}},readPropertyChange:function(){var b,c,a;b=event.srcElement;if(!b.vmlInitiated){return}if(event.propertyName.search("background")!=-1||event.propertyName.search("border")!=-1){DD_belatedPNG.applyVML(b)}if(event.propertyName=="style.display"){c=(b.currentStyle.display=="none")?"none":"block";for(a in b.vml){if(b.vml.hasOwnProperty(a)){b.vml[a].shape.style.display=c}}}if(event.propertyName.search("filter")!=-1){DD_belatedPNG.vmlOpacity(b)}},vmlOpacity:function(b){if(b.currentStyle.filter.search("lpha")!=-1){var a=b.currentStyle.filter;a=parseInt(a.substring(a.lastIndexOf("=")+1,a.lastIndexOf(")")),10)/100;b.vml.color.shape.style.filter=b.currentStyle.filter;b.vml.image.fill.opacity=a}},handlePseudoHover:function(a){setTimeout(function(){DD_belatedPNG.applyVML(a)},1)},fix:function(a){if(this.screenStyleSheet){var c,b;c=a.split(",");for(b=0;bn.H){i.B=n.H}d.vml.image.shape.style.clip="rect("+i.T+"px "+(i.R+a)+"px "+i.B+"px "+(i.L+a)+"px)"}else{d.vml.image.shape.style.clip="rect("+f.T+"px "+f.R+"px "+f.B+"px "+f.L+"px)"}},figurePercentage:function(d,c,f,a){var b,e;e=true;b=(f=="X");switch(a){case"left":case"top":d[f]=0;break;case"center":d[f]=0.5;break;case"right":case"bottom":d[f]=1;break;default:if(a.search("%")!=-1){d[f]=parseInt(a,10)/100}else{e=false}}d[f]=Math.ceil(e?((c[b?"W":"H"]*d[f])-(c[b?"w":"h"]*d[f])):parseInt(a,10));if(d[f]%2===0){d[f]++}return d[f]},fixPng:function(c){c.style.behavior="none";var g,b,f,a,d;if(c.nodeName=="BODY"||c.nodeName=="TD"||c.nodeName=="TR"){return}c.isImg=false;if(c.nodeName=="IMG"){if(c.src.toLowerCase().search(/\.png$/)!=-1){c.isImg=true;c.style.visibility="hidden"}else{return}}else{if(c.currentStyle.backgroundImage.toLowerCase().search(".png")==-1){return}}g=DD_belatedPNG;c.vml={color:{},image:{}};b={shape:{},fill:{}};for(a in c.vml){if(c.vml.hasOwnProperty(a)){for(d in b){if(b.hasOwnProperty(d)){f=g.ns+":"+d;c.vml[a][d]=document.createElement(f)}}c.vml[a].shape.stroked=false;c.vml[a].shape.appendChild(c.vml[a].fill);c.parentNode.insertBefore(c.vml[a].shape,c)}}c.vml.image.shape.fillcolor="none";c.vml.image.fill.type="tile";c.vml.color.fill.on=false;g.attachHandlers(c);g.giveLayout(c);g.giveLayout(c.offsetParent);c.vmlInitiated=true;g.applyVML(c)}};try{document.execCommand("BackgroundImageCache",false,true)}catch(r){}DD_belatedPNG.createVmlNameSpace();DD_belatedPNG.createVmlStyleSheet(); ADDED applications/welcome/static/js/jquery.js Index: applications/welcome/static/js/jquery.js ================================================================== --- applications/welcome/static/js/jquery.js +++ applications/welcome/static/js/jquery.js cannot compute difference between binary files ADDED applications/welcome/static/js/modernizr-1.7.min.js Index: applications/welcome/static/js/modernizr-1.7.min.js ================================================================== --- applications/welcome/static/js/modernizr-1.7.min.js +++ applications/welcome/static/js/modernizr-1.7.min.js @@ -0,0 +1,2 @@ +// Modernizr v1.7 www.modernizr.com +window.Modernizr=function(a,b,c){function G(){e.input=function(a){for(var b=0,c=a.length;b7)},r.history=function(){return !!(a.history&&history.pushState)},r.draganddrop=function(){return x("dragstart")&&x("drop")},r.websockets=function(){return"WebSocket"in a},r.rgba=function(){A("background-color:rgba(150,255,150,.5)");return D(k.backgroundColor,"rgba")},r.hsla=function(){A("background-color:hsla(120,40%,100%,.5)");return D(k.backgroundColor,"rgba")||D(k.backgroundColor,"hsla")},r.multiplebgs=function(){A("background:url(//:),url(//:),red url(//:)");return(new RegExp("(url\\s*\\(.*?){3}")).test(k.background)},r.backgroundsize=function(){return F("backgroundSize")},r.borderimage=function(){return F("borderImage")},r.borderradius=function(){return F("borderRadius","",function(a){return D(a,"orderRadius")})},r.boxshadow=function(){return F("boxShadow")},r.textshadow=function(){return b.createElement("div").style.textShadow===""},r.opacity=function(){B("opacity:.55");return/^0.55$/.test(k.opacity)},r.cssanimations=function(){return F("animationName")},r.csscolumns=function(){return F("columnCount")},r.cssgradients=function(){var a="background-image:",b="gradient(linear,left top,right bottom,from(#9f9),to(white));",c="linear-gradient(left top,#9f9, white);";A((a+o.join(b+a)+o.join(c+a)).slice(0,-a.length));return D(k.backgroundImage,"gradient")},r.cssreflections=function(){return F("boxReflect")},r.csstransforms=function(){return!!E(["transformProperty","WebkitTransform","MozTransform","OTransform","msTransform"])},r.csstransforms3d=function(){var a=!!E(["perspectiveProperty","WebkitPerspective","MozPerspective","OPerspective","msPerspective"]);a&&"webkitPerspective"in g.style&&(a=w("@media ("+o.join("transform-3d),(")+"modernizr)"));return a},r.csstransitions=function(){return F("transitionProperty")},r.fontface=function(){var a,c,d=h||g,e=b.createElement("style"),f=b.implementation||{hasFeature:function(){return!1}};e.type="text/css",d.insertBefore(e,d.firstChild),a=e.sheet||e.styleSheet;var i=f.hasFeature("CSS2","")?function(b){if(!a||!b)return!1;var c=!1;try{a.insertRule(b,0),c=/src/i.test(a.cssRules[0].cssText),a.deleteRule(a.cssRules.length-1)}catch(d){}return c}:function(b){if(!a||!b)return!1;a.cssText=b;return a.cssText.length!==0&&/src/i.test(a.cssText)&&a.cssText.replace(/\r+|\n+/g,"").indexOf(b.split(" ")[0])===0};c=i('@font-face { font-family: "font"; src: url(data:,); }'),d.removeChild(e);return c},r.video=function(){var a=b.createElement("video"),c=!!a.canPlayType;if(c){c=new Boolean(c),c.ogg=a.canPlayType('video/ogg; codecs="theora"');var d='video/mp4; codecs="avc1.42E01E';c.h264=a.canPlayType(d+'"')||a.canPlayType(d+', mp4a.40.2"'),c.webm=a.canPlayType('video/webm; codecs="vp8, vorbis"')}return c},r.audio=function(){var a=b.createElement("audio"),c=!!a.canPlayType;c&&(c=new Boolean(c),c.ogg=a.canPlayType('audio/ogg; codecs="vorbis"'),c.mp3=a.canPlayType("audio/mpeg;"),c.wav=a.canPlayType('audio/wav; codecs="1"'),c.m4a=a.canPlayType("audio/x-m4a;")||a.canPlayType("audio/aac;"));return c},r.localstorage=function(){try{return!!localStorage.getItem}catch(a){return!1}},r.sessionstorage=function(){try{return!!sessionStorage.getItem}catch(a){return!1}},r.webWorkers=function(){return!!a.Worker},r.applicationcache=function(){return!!a.applicationCache},r.svg=function(){return!!b.createElementNS&&!!b.createElementNS(q.svg,"svg").createSVGRect},r.inlinesvg=function(){var a=b.createElement("div");a.innerHTML="";return(a.firstChild&&a.firstChild.namespaceURI)==q.svg},r.smil=function(){return!!b.createElementNS&&/SVG/.test(n.call(b.createElementNS(q.svg,"animate")))},r.svgclippaths=function(){return!!b.createElementNS&&/SVG/.test(n.call(b.createElementNS(q.svg,"clipPath")))};for(var H in r)z(r,H)&&(v=H.toLowerCase(),e[v]=r[H](),u.push((e[v]?"":"no-")+v));e.input||G(),e.crosswindowmessaging=e.postmessage,e.historymanagement=e.history,e.addTest=function(a,b){a=a.toLowerCase();if(!e[a]){b=!!b(),g.className+=" "+(b?"":"no-")+a,e[a]=b;return e}},A(""),j=l=null,f&&a.attachEvent&&function(){var a=b.createElement("div");a.innerHTML="";return a.childNodes.length!==1}()&&function(a,b){function p(a,b){var c=-1,d=a.length,e,f=[];while(++c »'].join('')), + over = function(){ + var $$ = $(this), menu = getMenu($$); + clearTimeout(menu.sfTimer); + $$.showSuperfishUl().siblings().hideSuperfishUl(); + }, + out = function(){ + var $$ = $(this), menu = getMenu($$), o = sf.op; + clearTimeout(menu.sfTimer); + menu.sfTimer=setTimeout(function(){ + o.retainPath=($.inArray($$[0],o.$path)>-1); + $$.hideSuperfishUl(); + if (o.$path.length && $$.parents(['li.',o.hoverClass].join('')).length<1){over.call(o.$path);} + },o.delay); + }, + getMenu = function($menu){ + var menu = $menu.parents(['ul.',c.menuClass,':first'].join(''))[0]; + sf.op = sf.o[menu.serial]; + return menu; + }, + addArrow = function($a){ $a.addClass(c.anchorClass).append($arrow.clone()); }; + + return this.each(function() { + var s = this.serial = sf.o.length; + var o = $.extend({},sf.defaults,op); + o.$path = $('li.'+o.pathClass,this).slice(0,o.pathLevels).each(function(){ + $(this).addClass([o.hoverClass,c.bcClass].join(' ')) + .filter('li:has(ul)').removeClass(o.pathClass); + }); + sf.o[s] = sf.op = o; + + $('li:has(ul)',this)[($.fn.hoverIntent && !o.disableHI) ? 'hoverIntent' : 'hover'](over,out).each(function() { + if (o.autoArrows) addArrow( $('>a:first-child',this) ); + }) + .not('.'+c.bcClass) + .hideSuperfishUl(); + + var $a = $('a',this); + $a.each(function(i){ + var $li = $a.eq(i).parents('li'); + $a.eq(i).focus(function(){over.call($li);}).blur(function(){out.call($li);}); + }); + o.onInit.call(this); + + }).each(function() { + var menuClasses = [c.menuClass]; + if (sf.op.dropShadows && !($.browser.msie && $.browser.version < 7)) menuClasses.push(c.shadowClass); + $(this).addClass(menuClasses.join(' ')); + }); + }; + + var sf = $.fn.superfish; + sf.o = []; + sf.op = {}; + sf.IE7fix = function(){ + var o = sf.op; + if ($.browser.msie && $.browser.version > 6 && o.dropShadows && o.animation.opacity!=undefined) + this.toggleClass(sf.c.shadowClass+'-off'); + }; + sf.c = { + bcClass : 'sf-breadcrumb', + menuClass : 'sf-js-enabled', + anchorClass : 'sf-with-ul', + arrowClass : 'sf-sub-indicator', + shadowClass : 'sf-shadow' + }; + sf.defaults = { + hoverClass : 'sfHover', + pathClass : 'overideThisToUse', + pathLevels : 1, + delay : 800, + animation : {opacity:'show'}, + speed : 'normal', + autoArrows : true, + dropShadows : true, + disableHI : false, // true disables hoverIntent detection + onInit : function(){}, // callback functions + onBeforeShow: function(){}, + onShow : function(){}, + onHide : function(){} + }; + $.fn.extend({ + hideSuperfishUl : function(){ + var o = sf.op, + not = (o.retainPath===true) ? o.$path : ''; + o.retainPath = false; + var $ul = $(['li.',o.hoverClass].join(''),this).add(this).not(not).removeClass(o.hoverClass) + .find('>ul').hide().css('visibility','hidden'); + o.onHide.call($ul); + return this; + }, + showSuperfishUl : function(){ + var o = sf.op, + sh = sf.c.shadowClass+'-off', + $ul = this.addClass(o.hoverClass) + .find('>ul:hidden').css('visibility','visible'); + sf.IE7fix.call($ul); + o.onBeforeShow.call($ul); + $ul.animate(o.animation,o.speed,function(){ sf.IE7fix.call($ul); o.onShow.call($ul); }); + return this; + } + }); + +})(jQuery); ADDED applications/welcome/static/js/web2py_ajax.js Index: applications/welcome/static/js/web2py_ajax.js ================================================================== --- applications/welcome/static/js/web2py_ajax.js +++ applications/welcome/static/js/web2py_ajax.js @@ -0,0 +1,97 @@ +function popup(url) { + newwindow=window.open(url,'name','height=400,width=600'); + if (window.focus) newwindow.focus(); + return false; +} +function collapse(id) { jQuery('#'+id).slideToggle(); } +function fade(id,value) { if(value>0) jQuery('#'+id).hide().fadeIn('slow'); else jQuery('#'+id).show().fadeOut('slow'); } +function ajax(u,s,t) { + query = ''; + if (typeof s == "string") { + d = jQuery(s).serialize(); + if(d){ query = d; } + } else { + pcs = []; + for(i=0; i0){query = pcs.join("&");} + } + jQuery.ajax({type: "POST", url: u, data: query, success: function(msg) { if(t) { if(t==':eval') eval(msg); else jQuery("#" + t).html(msg); } } }); +} + +String.prototype.reverse = function () { return this.split('').reverse().join('');}; +function web2py_ajax_init() { + jQuery('.hidden').hide(); + jQuery('.error').hide().slideDown('slow'); + jQuery('.flash').click(function() { jQuery(this).fadeOut('slow'); return false; }); + // jQuery('input[type=submit]').click(function(){var t=jQuery(this);t.hide();t.after('')}); + jQuery('input.integer').live('keyup', function(){this.value=this.value.reverse().replace(/[^0-9\-]|\-(?=.)/g,'').reverse();}); + jQuery('input.double,input.decimal').live('keyup', function(){this.value=this.value.reverse().replace(/[^0-9\-\.,]|[\-](?=.)|[\.,](?=[0-9]*[\.,])/g,'').reverse();}); + var confirm_message = (typeof w2p_ajax_confirm_message != 'undefined') ? w2p_ajax_confirm_message : "Are you sure you want to delete this object?"; + jQuery("input[type='checkbox'].delete").live('click', function(){ if(this.checked) if(!confirm(confirm_message)) this.checked=false; }); + var date_format = (typeof w2p_ajax_date_format != 'undefined') ? w2p_ajax_date_format : "%Y-%m-%d"; + try {jQuery("input.date").live('focus',function() {Calendar.setup({ + inputField:this, ifFormat:date_format, showsTime:false + }); }); } catch(e) {}; + var datetime_format = (typeof w2p_ajax_datetime_format != 'undefined') ? w2p_ajax_datetime_format : "%Y-%m-%d %H:%M:%S"; + try { jQuery("input.datetime").live('focus', function() {Calendar.setup({ + inputField:this, ifFormat:datetime_format, showsTime: true,timeFormat: "24" + }); }); } catch(e) {}; + + jQuery("input.time").live('focus', function() { var el = jQuery(this); + if (!el.hasClass('hasTimeEntry')) try { el.timeEntry(); } catch(e) {}; + }); +}; + +jQuery(function() { + var flash = jQuery('.flash'); + flash.hide(); + if(flash.html()) flash.slideDown(); + web2py_ajax_init(); +}); +function web2py_trap_form(action,target) { + jQuery('#'+target+' form').each(function(i){ + var form=jQuery(this); + if(!form.hasClass('no_trap')) + form.submit(function(obj){ + jQuery('.flash').hide().html(''); + web2py_ajax_page('post',action,form.serialize(),target); + return false; + }); + }); +} +function web2py_ajax_page(method,action,data,target) { + jQuery.ajax({'type':method,'url':action,'data':data, + 'beforeSend':function(xhr) { + xhr.setRequestHeader('web2py-component-location',document.location); + xhr.setRequestHeader('web2py-component-element',target);}, + 'complete':function(xhr,text){ + var html=xhr.responseText; + var content=xhr.getResponseHeader('web2py-component-content'); + var command=xhr.getResponseHeader('web2py-component-command'); + var flash=xhr.getResponseHeader('web2py-component-flash'); + var t = jQuery('#'+target); + if(content=='prepend') t.prepend(html); + else if(content=='append') t.append(html); + else if(content!='hide') t.html(html); + web2py_trap_form(action,target); + web2py_ajax_init(); + if(command) eval(command); + if(flash) jQuery('.flash').html(flash).slideDown(); + } + }); +} +function web2py_component(action,target) { + jQuery(function(){ web2py_ajax_page('get',action,null,target); }); +} +function web2py_comet(url,onmessage,onopen,onclose) { + if ("WebSocket" in window) { + var ws = new WebSocket(url); + ws.onopen = onopen?onopen:(function(){}); + ws.onmessage = onmessage; + ws.onclose = onclose?onclose:(function(){}); + return true; // supported + } else return false; // not supported +} ADDED applications/welcome/static/robots.txt Index: applications/welcome/static/robots.txt ================================================================== --- applications/welcome/static/robots.txt +++ applications/welcome/static/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: /welcome/default/user ADDED applications/welcome/views/__init__.py Index: applications/welcome/views/__init__.py ================================================================== --- applications/welcome/views/__init__.py +++ applications/welcome/views/__init__.py ADDED applications/welcome/views/appadmin.html Index: applications/welcome/views/appadmin.html ================================================================== --- applications/welcome/views/appadmin.html +++ applications/welcome/views/appadmin.html @@ -0,0 +1,198 @@ +{{extend 'layout.html'}} + + +{{if request.function=='index':}} +

    {{=T("Available databases and tables")}}

    + {{if not databases:}}{{=T("No databases in this application")}}{{pass}} + {{for db in sorted(databases):}} + {{for table in databases[db].tables:}} + {{qry='%s.%s.id>0'%(db,table)}} + {{tbl=databases[db][table]}} + {{if hasattr(tbl,'_primarykey'):}} + {{if tbl._primarykey:}} + {{firstkey=tbl[tbl._primarykey[0]]}} + {{if firstkey.type in ['string','text']:}} + {{qry='%s.%s.%s!=""'%(db,table,firstkey.name)}} + {{else:}} + {{qry='%s.%s.%s>0'%(db,table,firstkey.name)}} + {{pass}} + {{else:}} + {{qry=''}} + {{pass}} + {{pass}} +

    {{=A("%s.%s" % (db,table),_href=URL('select',args=[db],vars=dict(query=qry)))}} +

    + [ {{=A(str(T('insert new'))+' '+table,_href=URL('insert',args=[db,table]))}} ] +

    + {{pass}} + {{pass}} + +{{elif request.function=='select':}} +

    {{=XML(str(T("database %s select"))%A(request.args[0],_href=URL('index'))) }} +

    + {{if table:}} + [ {{=A(str(T('insert new %s'))%table,_href=URL('insert',args=[request.args[0],table]))}} ]

    +

    {{=T("Rows in table")}}


    + {{else:}} +

    {{=T("Rows selected")}}


    + {{pass}} + {{=form}} +

    {{=T('The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.')}}
    + {{=T('Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.')}}
    + {{=T('"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN')}}

    +

    +

    {{=nrows}} {{=T("selected")}}

    + {{if start>0:}}[ {{=A(T('previous 100 rows'),_href=URL('select',args=request.args[0],vars=dict(start=start-100)))}} ]{{pass}} + {{if stop + {{linkto=URL('update',args=request.args[0])}} + {{upload=URL('download',args=request.args[0])}} + {{=SQLTABLE(rows,linkto,upload,orderby=True,_class='sortable')}} + + {{pass}} +

    {{=T("Import/Export")}}


    + [ {{=T("export as csv file")}} ] + {{if table:}} + {{=FORM(str(T('or import from csv file'))+" ",INPUT(_type='file',_name='csvfile'),INPUT(_type='hidden',_value=table,_name='table'),INPUT(_type='submit',_value='import'))}} + {{pass}} + + +{{elif request.function=='insert':}} +

    {{=T("database")}} {{=A(request.args[0],_href=URL('index'))}} + {{if hasattr(table,'_primarykey'):}} + {{fieldname=table._primarykey[0]}} + {{dbname=request.args[0]}} + {{tablename=request.args[1]}} + {{cond = table[fieldname].type in ['string','text'] and '!=""' or '>0'}} + {{=T("table")}} {{=A(tablename,_href=URL('select',args=dbname,vars=dict(query='%s.%s.%s%s'%(dbname,tablename,fieldname,cond))))}} + {{else:}} + {{=T("table")}} {{=A(request.args[1],_href=URL('select',args=request.args[0],vars=dict(query='%s.%s.id>0'%tuple(request.args[:2]))))}} + {{pass}} +

    +

    {{=T("New Record")}}


    + {{=form}} + + + +{{elif request.function=='update':}} +

    {{=T("database")}} {{=A(request.args[0],_href=URL('index'))}} + {{if hasattr(table,'_primarykey'):}} + {{fieldname=request.vars.keys()[0]}} + {{dbname=request.args[0]}} + {{tablename=request.args[1]}} + {{cond = table[fieldname].type in ['string','text'] and '!=""' or '>0'}} + {{=T("table")}} {{=A(tablename,_href=URL('select',args=dbname,vars=dict(query='%s.%s.%s%s'%(dbname,tablename,fieldname,cond))))}} + {{=T("record")}} {{=A('%s=%s'%request.vars.items()[0],_href=URL('update',args=request.args[:2],vars=request.vars))}} + {{else:}} + {{=T("table")}} {{=A(request.args[1],_href=URL('select',args=request.args[0],vars=dict(query='%s.%s.id>0'%tuple(request.args[:2]))))}} + {{=T("record id")}} {{=A(request.args[2],_href=URL('update',args=request.args[:3]))}} + {{pass}} +

    +

    {{=T("Edit current record")}}



    {{=form}} + + + +{{elif request.function=='state':}} +

    {{=T("Internal State")}}

    +

    {{=T("Current request")}}

    + {{=BEAUTIFY(request)}} +

    {{=T("Current response")}}

    + {{=BEAUTIFY(response)}} +

    {{=T("Current session")}}

    + {{=BEAUTIFY(session)}} + + +{{elif request.function == 'ccache':}} +

    Cache

    +
    +
    +
    + Statistics +
    +
    +

    Overview

    +

    + Hit Ratio: + {{=total['ratio']}}% + ({{=total['hits']}} hits + and {{=total['misses']}} misses) +

    +

    + Size of cache: + {{=total['objects']}} items, + {{=total['bytes']}} bytes + {{if total['bytes'] > 524287:}} + ({{="%.0d" % (total['bytes'] / 1048576)}} MB) + {{pass}} +

    +

    + Cache contains items up to + {{="%02d" % total['oldest'][0]}} hours + {{="%02d" % total['oldest'][1]}} minutes + {{="%02d" % total['oldest'][2]}} seconds old. +

    +

    RAM

    +

    + Hit Ratio: + {{=ram['ratio']}}% + ({{=ram['hits']}} hits + and {{=ram['misses']}} misses) +

    +

    + Size of cache: + {{=ram['objects']}} items, + {{=ram['bytes']}} bytes + {{if ram['bytes'] > 524287:}} + ({{=ram['bytes'] / 1048576}} MB) + {{pass}} +

    +

    + RAM contains items up to + {{="%02d" % ram['oldest'][0]}} hours + {{="%02d" % ram['oldest'][1]}} minutes + {{="%02d" % ram['oldest'][2]}} seconds old. +

    +

    DISK

    +

    + Hit Ratio: + {{=disk['ratio']}}% + ({{=disk['hits']}} hits + and {{=disk['misses']}} misses) +

    +

    + Size of cache: + {{=disk['objects']}} items, + {{=disk['bytes']}} bytes + {{if disk['bytes'] > 524287:}} + ({{=disk['bytes'] / 1048576}} MB) + {{pass}} +

    +

    + DISK contains items up to + {{="%02d" % disk['oldest'][0]}} hours + {{="%02d" % disk['oldest'][1]}} minutes + {{="%02d" % disk['oldest'][2]}} seconds old. +

    +
    + +
    + Manage Cache +
    +
    +

    + {{=form}} +

    +
    +
    +
    +
    +{{pass}} ADDED applications/welcome/views/default/index.html Index: applications/welcome/views/default/index.html ================================================================== --- applications/welcome/views/default/index.html +++ applications/welcome/views/default/index.html @@ -0,0 +1,33 @@ +{{left_sidebar_enabled=right_sidebar_enabled=False}} +{{extend 'layout.html'}} + +{{if 'message' in globals():}} + +

    {{=message}}

    + +
    +

    {{=T("Readme")}}

    +
      +
    • {{=T('You are successfully running web2py')}}
    • +
    • {{=T('This is a copy of the scaffolding application')}}
    • +
    • {{=T('You can modify this application and adapt it to your needs')}}
    • +
    • {{=A(B(T("Administrative interface")), _href=URL('admin','default','index'))}}
    • +
    • {{=A(T("Online examples"), _href=URL('examples','default','index'))}}
    • +
    • web2py.com
    • +
    • {{=T('Documentation')}}
    • +
    + +
      +
    1. {{=T('You visited the url')}} {{=A(request.env.path_info,_href=request.env.path_info)}}
    2. +
    3. {{=T('Which called the function')}} {{=A(request.function+'()',_href='#')}} {{=T('located in the file')}} + {{=A('web2py/applications/%(application)s/controllers/%(controller)s.py'%request,_href=URL('admin','default','peek',args=(request.application,'controllers',request.controller+'.py')))}}
    4. +
    5. {{=T('The output of the file is a dictionary that was rendered by the view')}} + {{=A('web2py/applications/%(application)s/views/%(controller)s/index.html'%request,_href=URL('admin','default','peek',args=(request.application,'views',request.controller,'index.html')))}}
    6. +
    + +{{else:}} +{{=BEAUTIFY(response._vars)}} +{{pass}} + +{{block left_sidebar}}New Left Sidebar Content{{end}} +{{block right_sidebar}}New Right Sidebar Content{{end}} ADDED applications/welcome/views/default/user.html Index: applications/welcome/views/default/user.html ================================================================== --- applications/welcome/views/default/user.html +++ applications/welcome/views/default/user.html @@ -0,0 +1,19 @@ +{{extend 'layout.html'}} +

    {{=T( request.args(0).replace('_',' ').capitalize() )}}

    +
    +{{=form}} +{{if request.args(0)=='login':}} +{{if not 'register' in auth.settings.actions_disabled:}} +
    register +{{pass}} +{{if not 'request_reset_password' in auth.settings.actions_disabled:}} +
    lost password +{{pass}} +{{pass}} +
    + + ADDED applications/welcome/views/generic.html Index: applications/welcome/views/generic.html ================================================================== --- applications/welcome/views/generic.html +++ applications/welcome/views/generic.html @@ -0,0 +1,16 @@ +{{extend 'layout.html'}} +{{""" + +You should not modify this file. +It is used as default when a view is not provided for your controllers + +"""}} +

    {{=' '.join(x.capitalize() for x in request.function.split('_'))}}

    +{{if len(response._vars)==1:}} +{{=response._vars.values()[0]}} +{{elif len(response._vars)>1:}} +{{=BEAUTIFY(response._vars)}} +{{pass}} +{{if request.is_local:}} +{{=response.toolbar()}} +{{pass}} ADDED applications/welcome/views/generic.json Index: applications/welcome/views/generic.json ================================================================== --- applications/welcome/views/generic.json +++ applications/welcome/views/generic.json @@ -0,0 +1,1 @@ +{{from gluon.serializers import json}}{{=XML(json(response._vars))}} ADDED applications/welcome/views/generic.load Index: applications/welcome/views/generic.load ================================================================== --- applications/welcome/views/generic.load +++ applications/welcome/views/generic.load @@ -0,0 +1,30 @@ +{{''' +# License: Public Domain +# Author: Iceberg at 21cn dot com + +With this generic.load file, you can use same function to serve two purposes. + += regular action +- ajax callback (when called with .load) + +Example modified from http://www.web2py.com/AlterEgo/default/show/252: + +def index(): + return dict( + part1='hello world', + part2=LOAD(url=URL(r=request,f='auxiliary.load'),ajax=True)) + +def auxiliary(): + form=SQLFORM.factory(Field('name')) + if form.accepts(request.vars): + response.flash = 'ok' + return dict(message="Hello %s" % form.vars.name) + return dict(form=form) + +Notice: + +- no need to set response.headers['web2py-response-flash'] +- no need to return a string +even if the function is called via ajax. + +'''}}{{if len(response._vars)==1:}}{{=response._vars.values()[0]}}{{else:}}{{=BEAUTIFY(response._vars)}}{{pass}} ADDED applications/welcome/views/generic.pdf Index: applications/welcome/views/generic.pdf ================================================================== --- applications/welcome/views/generic.pdf +++ applications/welcome/views/generic.pdf @@ -0,0 +1,11 @@ +{{ +import os +from gluon.contrib.generics import pdf_from_html +filename = '%s/%s.html' % (request.controller,request.function) +if os.path.exists(os.path.join(request.folder,'views',filename)): + html=response.render(filename) +else: + html=BODY(BEAUTIFY(response._vars)).xml() +pass +=pdf_from_html(html) +}} ADDED applications/welcome/views/generic.rss Index: applications/welcome/views/generic.rss ================================================================== --- applications/welcome/views/generic.rss +++ applications/welcome/views/generic.rss @@ -0,0 +1,10 @@ +{{ +### +# response._vars contains the dictionary returned by the controller action +# for this to work the action must return something like +# +# dict(title=...,link=...,description=...,created_on='...',items=...) +# +# items is a list of dictionaries each with title, link, description, pub_date. +### +from gluon.serializers import rss}}{{=XML(rss(response._vars))}} ADDED applications/welcome/views/generic.xml Index: applications/welcome/views/generic.xml ================================================================== --- applications/welcome/views/generic.xml +++ applications/welcome/views/generic.xml @@ -0,0 +1,1 @@ +{{from gluon.serializers import xml}}{{=XML(xml(response._vars))}} ADDED applications/welcome/views/layout.html Index: applications/welcome/views/layout.html ================================================================== --- applications/welcome/views/layout.html +++ applications/welcome/views/layout.html @@ -0,0 +1,159 @@ + + + + + + + + + + + {{=response.title or request.application}} + + + + + + + + + + + + + + + + + + + + + {{#------ require CSS and JS files for this page (read info in base.css) ------}} + {{response.files.append(URL('static','css/base.css'))}} + {{response.files.append(URL('static','css/superfish.css'))}} + {{response.files.append(URL('static','js/superfish.js'))}} + {{#------ include web2py specific js code (jquery, calendar, form stuff) ------}} + {{include 'web2py_ajax.html'}} + + {{ + #using sidebars need to know what sidebar you want to use + #prior of using it, because of static width size of content, you can use + #left_sidebar, right_sidebar, both or none (False left and right) + left_sidebar_enabled = globals().get('left_sidebar_enabled',False) + right_sidebar_enabled = globals().get('right_sidebar_enabled',False) + if left_sidebar_enabled and right_sidebar_enabled: width_content='63%' + elif left_sidebar_enabled != right_sidebar_enabled: width_content='740px' + else: width_content='100%' + if left_sidebar_enabled: left_sidebar_style = 'style="display: block;"' + else: left_sidebar_style = 'style="display: none;"' + if right_sidebar_enabled: right_sidebar_style = 'style="display: block;"' + else: right_sidebar_style = 'style="display: none;"' + style_content = 'style="width: %s"' % width_content + }} + + + + + + + + + +
    {{=response.flash or ''}}
    + +
    + +
    + + + +
    + {{block statusbar}} + {{#------ superfish menu ------}} + {{=MENU(response.menu,_class='sf-menu')}} + +
    + {{end}} +
    + +
    + + {{if left_sidebar_enabled:}} + + {{pass}} + + +
    + {{include}} +
    + + + {{if right_sidebar_enabled:}} + + {{pass}} + + +
    + +
    + + +
    +
    + + + + + + + ADDED applications/welcome/views/web2py_ajax.html Index: applications/welcome/views/web2py_ajax.html ================================================================== --- applications/welcome/views/web2py_ajax.html +++ applications/welcome/views/web2py_ajax.html @@ -0,0 +1,25 @@ +{{ +response.files.insert(0,URL('static','js/jquery.js')) +response.files.insert(1,URL('static','css/calendar.css')) +response.files.insert(2,URL('static','js/calendar.js')) +for _item in response.meta or []:}} + {{ +pass +for _k,_file in enumerate(response.files or []): + if _file in response.files[:_k]: + continue + _file0=_file.lower().split('?')[0] + if _file0.endswith('.css'):}} + {{ + elif _file0.endswith('.js'):}} + {{ + pass +pass +}} + + ADDED cgihandler.py Index: cgihandler.py ================================================================== --- cgihandler.py +++ cgihandler.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +This file is part of the web2py Web Framework +Copyrighted by Massimo Di Pierro +License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) +""" + +import os +import sys +import wsgiref.handlers + +path = os.path.dirname(os.path.abspath(__file__)) +os.chdir(path) +sys.path = [path]+[p for p in sys.path if not p==path] + +import gluon.main + +wsgiref.handlers.CGIHandler().run(gluon.main.wsgibase) ADDED epydoc.conf Index: epydoc.conf ================================================================== --- epydoc.conf +++ epydoc.conf @@ -0,0 +1,150 @@ +[epydoc] # Epydoc section marker (required by ConfigParser) + +# The list of objects to document. Objects can be named using +# dotted names, module filenames, or package directory names. +# Aliases for this option include "objects" and "values". +modules: gluon/*.py + +# The type of output that should be generated. Should be one +# of: html, text, latex, dvi, ps, pdf. +#output: latex +output: html + +# The path to the output directory. May be relative or absolute. +target: applications/examples/static/epydoc +#target: docs + +# An integer indicating how verbose epydoc should be. The default +# value is 0; negative values will suppress warnings and errors; +# positive values will give more verbose output. +verbosity: 0 + +# A boolean value indicating that Epydoc should show a traceback +# in case of unexpected error. By default don't show tracebacks +debug: 0 + +# If True, don't try to use colors or cursor control when doing +# textual output. The default False assumes a rich text prompt +simple-term: 0 + +### Generation options + +# The default markup language for docstrings, for modules that do +# not define __docformat__. Defaults to epytext. +docformat: epytext + +# Whether or not parsing should be used to examine objects. +parse: yes + +# Whether or not introspection should be used to examine objects. +introspect: yes + +# Don't examine in any way the modules whose dotted name match this +# regular expression pattern. +#exclude: + +# Don't perform introspection on the modules whose dotted name match this +# regular expression pattern. +#exclude-introspect: + +# Don't perform parsing on the modules whose dotted name match this +# regular expression pattern. +#exclude-parse: + +# The format for showing inheritance objects. +# It should be one of: 'grouped', 'listed', 'included'. +inheritance: listed + +# Whether or not to include private variables. (Even if included, +# private variables will be hidden by default.) +private: yes + +# Whether or not to list each module's imports. +#imports: no + +# Whether or not to include syntax highlighted source code in +# the output (HTML only). +sourcecode: yes + +# Whether or not to include a page with Epydoc log, containing +# effective option at the time of generation and the reported logs. +include-log: no + +### Output options + +# The documented project's name. +name: web2py Web Framework + +# The CSS stylesheet for HTML output. Can be the name of a built-in +# stylesheet, or the name of a file. +css: epydoc.css + +# The documented project's URL. +url: http://www.web2py.com + +# HTML code for the project link in the navigation bar. If left +# unspecified, the project link will be generated based on the +# project's name and URL. +# link: web2py + +# The "top" page for the documentation. Can be a URL, the name +# of a module or class, or one of the special names "trees.html", +# "indices.html", or "help.html" +#top: os.path + +# An alternative help file. The named file should contain the +# body of an HTML file; navigation bars will be added to it. +#help: my_helpfile.html + +# Whether or not to include a frames-based table of contents. +frames: yes + +# Whether each class should be listed in its own section when +# generating LaTeX or PDF output. +separate-classes: no + + +### API linking options + +# Define a new API document. A new interpreted text role +# will be created +#external-api: epydoc + +# Use the records in this file to resolve objects in the API named NAME. +#external-api-file: epydoc:api-objects.txt + +# Use this URL prefix to configure the string returned for external API. +#external-api-root: epydoc:http://epydoc.sourceforge.net/api + + +### Graph options + +# The list of graph types that should be automatically included +# in the output. Graphs are generated using the Graphviz "dot" +# executable. Graph types include: "classtree", "callgraph", +# "umlclasstree". Use "all" to include all graph types +# graph: umlclasstree +# graph: + +# The path to the Graphviz "dot" executable, used to generate +# graphs. +#dotpath: C:/home/graphviz/bin/dot.exe +#dotpath: /Applications/Graphviz.app/Contents/MacOS/dot + +# The name of one or more pstat files (generated by the profile +# or hotshot module). These are used to generate call graphs. +#pstat: profile.out + +# Specify the font used to generate Graphviz graphs. +# (e.g., helvetica or times). +# graph-font: Bitstream Vera Sans +graph-font: Helvetica + +# Specify the font size used to generate Graphviz graphs. +graph-font-size: 10 + +### Return value options + +# The condition upon which Epydoc should exit with a non-zero +# exit status. Possible values are error, warning, docstring_warning +#fail-on: error ADDED epydoc.css Index: epydoc.css ================================================================== --- epydoc.css +++ epydoc.css @@ -0,0 +1,410 @@ + + +/* Epydoc CSS Stylesheet + * + * This stylesheet can be used to customize the appearance of epydoc's + * HTML output. + * + */ + +/* Default Colors & Styles + * - Set the default foreground & background color with 'body'; and + * link colors with 'a:link' and 'a:visited'. + * - Use bold for decision list terms. + * - The heading styles defined here are used for headings *within* + * docstring descriptions. All headings used by epydoc itself use + * either class='epydoc' or class='toc' (CSS styles for both + * defined below). +body { background: #ffffff; color: #000000; } +a:link { color: #0000ff; } +a:visited { color: #204080; } +dt { font-weight: bold; } +h1 { font-size: +140%; font-style: italic; + font-weight: bold; } +h2 { font-size: +125%; font-style: italic; + font-weight: bold; } +h3 { font-size: +110%; font-style: italic; + font-weight: normal; } +code { font-size: 100%; } + */ + +body { background-color: #fff; color: #585858; font-size: 10pt; font-family: georgia, serif; } +a {color: #FF5C1F; } +a:hover { text-decoration: underline; } +a:visited { color: #FF5C1F;} +dt { font-weight: bold; } +h1 { font-size: +140%; font-style: italic; + font-weight: bold; } +h2 { color: #185360; font-size: +125%; font-style: italic; + font-weight: bold; } +h3 { color: #185360; font-size: +110%; font-style: italic; + font-weight: normal; } +code { font-size: 100%; } + +/* Page Header & Footer + * - The standard page header consists of a navigation bar (with + * pointers to standard pages such as 'home' and 'trees'); a + * breadcrumbs list, which can be used to navigate to containing + * classes or modules; options links, to show/hide private + * variables and to show/hide frames; and a page title (using + *

    ). The page title may be followed by a link to the + * corresponding source code (using 'span.codelink'). + * - The footer consists of a navigation bar, a timestamp, and a + * pointer to epydoc's homepage. + +h1.epydoc { margin: 0; font-size: +140%; font-weight: bold; } +h2.epydoc { font-size: +130%; font-weight: bold; } +h3.epydoc { font-size: +115%; font-weight: bold; } +td h3.epydoc { font-size: +115%; font-weight: bold; + margin-bottom: 0; } +table.navbar { background: #a0c0ff; color: #000000; + border: 2px groove #c0d0d0; } +table.navbar table { color: #000000; } +th.navbar-select { background: #70b0ff; + color: #000000; } +table.navbar a { text-decoration: none; } +table.navbar a:link { color: #0000ff; } +table.navbar a:visited { color: #204080; } +span.breadcrumbs { font-size: 85%; font-weight: bold; } +span.options { font-size: 70%; } +span.codelink { font-size: 85%; } +td.footer { font-size: 85%; } +*/ + +h1.epydoc { margin: 0; font-size: +140%; font-weight: bold; } +h2.epydoc { font-size: +130%; font-weight: bold; } +h3.epydoc { font-size: +115%; font-weight: bold; } +td h3.epydoc { font-size: +115%; font-weight: bold; + margin-bottom: 0; } +table.navbar { background: url('title.png') repeat-x; + #background: #a0c0ff; + color: #FF5C1F; + #border: 2px groove #c0d0d0; } + +table.navbar table { color: #FF5C1F; } +th.navbar-select { background: #fff; + color: #195866; } + +table.navbar a { text-decoration: none; + color: #FF5C1F;} +table.navbar a:link { color: #FF5C1F; } +table.navbar a:visited { color: #FF5C1F; } + +span.breadcrumbs { font-size: 85%; font-weight: bold; } +span.options { font-size: 70%; } +span.codelink { font-size: 85%; } +td.footer { font-size: 85%; } + + +/* Table Headers + * - Each summary table and details section begins with a 'header' + * row. This row contains a section title (marked by + * 'span.table-header') as well as a show/hide private link + * (marked by 'span.options', defined above). + * - Summary tables that contain user-defined groups mark those + * groups using 'group header' rows. + +td.table-header { background: #70b0ff; color: #000000; + border: 1px solid #608090; } +td.table-header table { color: #000000; } +td.table-header table a:link { color: #0000ff; } +td.table-header table a:visited { color: #204080; } +span.table-header { font-size: 120%; font-weight: bold; } +th.group-header { background: #c0e0f8; color: #000000; + text-align: left; font-style: italic; + font-size: 115%; + border: 1px solid #608090; } +*/ +td.table-header { background: #258396; color: #000000; + border: 1px solid #608090; } + +td.table-header table { color: #fff; } +td.table-header table a:link { color: #FF5C1F; } +td.table-header table a:visited { color: #FF5C1F; } +span.table-header { font-size: 120%; font-weight: bold; } +th.group-header { background: #185360; color: #fff; + text-align: left; font-style: italic; + font-size: 115%; + border: 1px solid #608090; } + +/* Summary Tables (functions, variables, etc) + * - Each object is described by a single row of the table with + * two cells. The left cell gives the object's type, and is + * marked with 'code.summary-type'. The right cell gives the + * object's name and a summary description. + * - CSS styles for the table's header and group headers are + * defined above, under 'Table Headers' + */ +table.summary { border-collapse: collapse; + background: #e8f0f8; color: #000000; + border: 1px solid #608090; + margin-bottom: 0.5em; } +td.summary { border: 1px solid #608090; } +code.summary-type { font-size: 85%; } +table.summary a:link { color: #FF5C1F; } +table.summary a:visited { color: #FF5C1F; } + + +/* Details Tables (functions, variables, etc) + * - Each object is described in its own div. + * - A single-row summary table w/ table-header is used as + * a header for each details section (CSS style for table-header + * is defined above, under 'Table Headers'). + */ +table.details { border-collapse: collapse; + background: #e8f0f8; color: #585858; + border: 1px solid #608090; + margin: .2em 0 0 0; } +table.details table { color: #fff; } +table.details a:link { color: #FF5C1F; } +table.details a:visited { color: #FF5C1F; } + +/* Fields */ +dl.fields { margin-left: 2em; margin-top: 1em; + margin-bottom: 1em; } +dl.fields dd ul { margin-left: 0em; padding-left: 0em; } +div.fields { margin-left: 2em; } +div.fields p { margin-bottom: 0.5em; } + +/* Index tables (identifier index, term index, etc) + * - link-index is used for indices containing lists of links + * (namely, the identifier index & term index). + * - index-where is used in link indices for the text indicating + * the container/source for each link. + * - metadata-index is used for indices containing metadata + * extracted from fields (namely, the bug index & todo index). + */ +table.link-index { border-collapse: collapse; + background: #e8f0f8; color: #000000; + border: 1px solid #608090; } +td.link-index { border-width: 0px; } +table.link-index a:link { color: #FF5C1F; } +table.link-index a:visited { color: #FF5C1F; } +span.index-where { font-size: 70%; } +table.metadata-index { border-collapse: collapse; + background: #e8f0f8; color: #000000; + border: 1px solid #608090; + margin: .2em 0 0 0; } +td.metadata-index { border-width: 1px; border-style: solid; } +table.metadata-index a:link { color: #FF5C1F; } +table.metadata-index a:visited { color: #FF5C1F; } + +/* Function signatures + * - sig* is used for the signature in the details section. + * - .summary-sig* is used for the signature in the summary + * table, and when listing property accessor functions. +.sig-name { color: #006080; } +.sig-arg { color: #008060; } +.sig-default { color: #602000; } +.summary-sig { font-family: monospace; } +.summary-sig-name { color: #006080; font-weight: bold; } +table.summary a.summary-sig-name:link + { color: #006080; font-weight: bold; } +table.summary a.summary-sig-name:visited + { color: #006080; font-weight: bold; } +.summary-sig-arg { color: #006040; } +.summary-sig-default { color: #501800; } + * */ +.sig-name { color: #FF5C1F; } +.sig-arg { color: #008060; } +.sig-default { color: #602000; } +.summary-sig { font-family: monospace; } +.summary-sig-name { color: #FF5C1F; font-weight: bold; } +table.summary a.summary-sig-name:link + { color: #FF5C1F; font-weight: bold; } +table.summary a.summary-sig-name:visited + { color: #FF5C1F; font-weight: bold; } +.summary-sig-arg { color: #006040; } +.summary-sig-default { color: #FF5C1F; } + + +/* To render variables, classes etc. like functions */ +table.summary .summary-name { color: #FF5C1F; font-weight: bold; + font-family: monospace; } +table.summary + a.summary-name:link { color: #FF5C1F; font-weight: bold; + font-family: monospace; } +table.summary + a.summary-name:visited { color: #FF5C1F; font-weight: bold; + font-family: monospace; } + +/* Variable values + * - In the 'variable details' sections, each variable's value is + * listed in a 'pre.variable' box. The width of this box is + * restricted to 80 chars; if the value's repr is longer than + * this it will be wrapped, using a backslash marked with + * class 'variable-linewrap'. If the value's repr is longer + * than 3 lines, the rest will be elided; and an ellipsis + * marker ('...' marked with 'variable-ellipsis') will be used. + * - If the value is a string, its quote marks will be marked + * with 'variable-quote'. + * - If the variable is a regexp, it is syntax-highlighted using + * the re* CSS classes. + */ +pre.variable { padding: .5em; margin: 0; + background: #dce4ec; color: #000000; + border: 1px solid #708890; } +.variable-linewrap { color: #604000; font-weight: bold; } +.variable-ellipsis { color: #604000; font-weight: bold; } +.variable-quote { color: #604000; font-weight: bold; } +.variable-group { color: #008000; font-weight: bold; } +.variable-op { color: #604000; font-weight: bold; } +.variable-string { color: #006030; } +.variable-unknown { color: #a00000; font-weight: bold; } +.re { color: #000000; } +.re-char { color: #006030; } +.re-op { color: #600000; } +.re-group { color: #003060; } +.re-ref { color: #404040; } + +/* Base tree + * - Used by class pages to display the base class hierarchy. + */ +pre.base-tree { font-size: 80%; margin: 0; } + +/* Frames-based table of contents headers + * - Consists of two frames: one for selecting modules; and + * the other listing the contents of the selected module. + * - h1.toc is used for each frame's heading + * - h2.toc is used for subheadings within each frame. + */ +h1.toc { text-align: center; font-size: 105%; + margin: 0; font-weight: bold; + padding: 0; } +h2.toc { font-size: 100%; font-weight: bold; + margin: 0.5em 0 0 -0.3em; } + +/* Syntax Highlighting for Source Code + * - doctest examples are displayed in a 'pre.py-doctest' block. + * If the example is in a details table entry, then it will use + * the colors specified by the 'table pre.py-doctest' line. + * - Source code listings are displayed in a 'pre.py-src' block. + * Each line is marked with 'span.py-line' (used to draw a line + * down the left margin, separating the code from the line + * numbers). Line numbers are displayed with 'span.py-lineno'. + * The expand/collapse block toggle button is displayed with + * 'a.py-toggle' (Note: the CSS style for 'a.py-toggle' should not + * modify the font size of the text.) + * - If a source code page is opened with an anchor, then the + * corresponding code block will be highlighted. The code + * block's header is highlighted with 'py-highlight-hdr'; and + * the code block's body is highlighted with 'py-highlight'. + * - The remaining py-* classes are used to perform syntax + * highlighting (py-string for string literals, py-name for names, + * etc.) + +pre.py-doctest { padding: .5em; margin: 1em; + background: #e8f0f8; color: #000000; + border: 1px solid #708890; } +table pre.py-doctest { background: #dce4ec; + color: #000000; } +pre.py-src { border: 2px solid #000000; + background: #f0f0f0; color: #000000; } +.py-line { border-left: 2px solid #000000; + margin-left: .2em; padding-left: .4em; } +.py-lineno { font-style: italic; font-size: 90%; + padding-left: .5em; } +a.py-toggle { text-decoration: none; } +div.py-highlight-hdr { border-top: 2px solid #000000; + border-bottom: 2px solid #000000; + background: #d8e8e8; } +div.py-highlight { border-bottom: 2px solid #000000; + background: #d0e0e0; } +.py-prompt { color: #005050; font-weight: bold;} +.py-more { color: #005050; font-weight: bold;} +.py-string { color: #006030; } +.py-comment { color: #003060; } +.py-keyword { color: #600000; } +.py-output { color: #404040; } +.py-name { color: #000050; } +.py-name:link { color: #000050 !important; } +.py-name:visited { color: #000050 !important; } +.py-number { color: #005000; } +.py-defname { color: #000060; font-weight: bold; } +.py-def-name { color: #000060; font-weight: bold; } +.py-base-class { color: #000060; } +.py-param { color: #000060; } +.py-docstring { color: #006030; } +.py-decorator { color: #804020; } + */ +/* Use this if you don't want links to names underlined: */ +/*a.py-name { text-decoration: none; }*/ + +pre.py-doctest { padding: .5em; margin: 1em; + background: #e8f0f8; color: #000000; + border: 1px solid #708890; } +table pre.py-doctest { background: #dce4ec; + color: #000000; } +pre.py-src { border: 2px solid #000000; + background: #f0f0f0; color: #000000; } +.py-line { border-left: 2px solid #000000; + margin-left: .2em; padding-left: .4em; } +.py-lineno { font-style: italic; font-size: 90%; + padding-left: .5em; } +a.py-toggle { text-decoration: none; } +div.py-highlight-hdr { border-top: 2px solid #000000; + border-bottom: 2px solid #000000; + background: #d8e8e8; } +div.py-highlight { border-bottom: 2px solid #000000; + background: #d0e0e0; } +.py-prompt { color: #005050; font-weight: bold;} +.py-more { color: #005050; font-weight: bold;} +.py-string { color: green; } +.py-comment { color: green; } +.py-keyword { color: blue; } +.py-output { color: #404040; } +.py-name { color: #585858;} +.py-name:link { color: #FF5C1F !important; } +.py-name:visited { color: #FF5C1F !important; } +.py-number { color: #005000; } +.py-defname { color: #FF5C1F; font-weight: bold; } +.py-def-name { color: #FF5C1F; font-weight: bold; } +.py-base-class { color: #FF5C1F; } +.py-param { color: #000060; } +.py-docstring { color: green; } +.py-decorator { color: #804020; } + +/* Graphs & Diagrams + * - These CSS styles are used for graphs & diagrams generated using + * Graphviz dot. 'img.graph-without-title' is used for bare + * diagrams (to remove the border created by making the image + * clickable). + */ +img.graph-without-title { border: none; } +img.graph-with-title { border: 1px solid #000000; } +span.graph-title { font-weight: bold; } +span.graph-caption { } + +/* General-purpose classes + * - 'p.indent-wrapped-lines' defines a paragraph whose first line + * is not indented, but whose subsequent lines are. + * - The 'nomargin-top' class is used to remove the top margin (e.g. + * from lists). The 'nomargin' class is used to remove both the + * top and bottom margin (but not the left or right margin -- + * for lists, that would cause the bullets to disappear.) + */ +p.indent-wrapped-lines { padding: 0 0 0 7em; text-indent: -7em; + margin: 0; } +.nomargin-top { margin-top: 0; } +.nomargin { margin-top: 0; margin-bottom: 0; } + +/* HTML Log */ +div.log-block { padding: 0; margin: .5em 0 .5em 0; + background: #e8f0f8; color: #000000; + border: 1px solid #000000; } +div.log-error { padding: .1em .3em .1em .3em; margin: 4px; + background: #ffb0b0; color: #000000; + border: 1px solid #000000; } +div.log-warning { padding: .1em .3em .1em .3em; margin: 4px; + background: #ffffb0; color: #000000; + border: 1px solid #000000; } +div.log-info { padding: .1em .3em .1em .3em; margin: 4px; + background: #b0ffb0; color: #000000; + border: 1px solid #000000; } +h2.log-hdr { background: #70b0ff; color: #000000; + margin: 0; padding: 0em 0.5em 0em 0.5em; + border-bottom: 1px solid #000000; font-size: 110%; } +p.log { font-weight: bold; margin: .5em 0 .5em 0; } +tr.opt-changed { color: #000000; font-weight: bold; } +tr.opt-default { color: #606060; } +pre.log { margin: 0; padding: 0; padding-left: 1em; } ADDED fcgihandler.py Index: fcgihandler.py ================================================================== --- fcgihandler.py +++ fcgihandler.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +This file is part of the web2py Web Framework +Copyrighted by Massimo Di Pierro +License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) + +This is a handler for lighttpd+fastcgi +This file has to be in the PYTHONPATH +Put something like this in the lighttpd.conf file: + +server.port = 8000 +server.bind = '127.0.0.1' +server.event-handler = 'freebsd-kqueue' +server.modules = ('mod_rewrite', 'mod_fastcgi') +server.error-handler-404 = '/test.fcgi' +server.document-root = '/somewhere/web2py' +server.errorlog = '/tmp/error.log' +fastcgi.server = ('.fcgi' => + ('localhost' => + ('min-procs' => 1, + 'socket' => '/tmp/fcgi.sock' + ) + ) + ) +""" + +LOGGING = False +SOFTCRON = False + +import sys +import os + +path = os.path.dirname(os.path.abspath(__file__)) +os.chdir(path) +sys.path = [path]+[p for p in sys.path if not p==path] + +import gluon.main +import gluon.contrib.gateways.fcgi as fcgi + +if LOGGING: + application = gluon.main.appfactory(wsgiapp=gluon.main.wsgibase, + logfilename='httpserver.log', + profilerfilename=None) +else: + application = gluon.main.wsgibase + +if SOFTCRON: + from gluon.settings import global_settings + global_settings.web2py_crontype = 'soft' + +fcgi.WSGIServer(application, bindAddress='/tmp/fcgi.sock').run() ADDED gaehandler.py Index: gaehandler.py ================================================================== --- gaehandler.py +++ gaehandler.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +This file is part of the web2py Web Framework +Copyrighted by Massimo Di Pierro +License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) +""" + +############################################################################## +# Configuration parameters for Google App Engine +############################################################################## +KEEP_CACHED = False # request a dummy url every 10secs to force caching app +LOG_STATS = False # web2py level log statistics +APPSTATS = True # GAE level usage statistics and profiling +DEBUG = False # debug mode +AUTO_RETRY = True # force gae to retry commit on failure +# +# Read more about APPSTATS here +# http://googleappengine.blogspot.com/2010/03/easy-performance-profiling-with.html +# can be accessed from: +# http://localhost:8080/_ah/stats +############################################################################## +# All tricks in this file developed by Robin Bhattacharyya +############################################################################## + + +import time +import os +import sys +import logging +import cPickle +import pickle +import wsgiref.handlers +import datetime + +path = os.path.dirname(os.path.abspath(__file__)) +sys.path = [path]+[p for p in sys.path if not p==path] + +sys.modules['cPickle'] = sys.modules['pickle'] + + +from gluon.settings import global_settings +from google.appengine.api.labs import taskqueue +from google.appengine.ext import webapp +from google.appengine.ext.webapp.util import run_wsgi_app + + +global_settings.web2py_runtime_gae = True +global_settings.db_sessions = True +if os.environ.get('SERVER_SOFTWARE', '').startswith('Devel'): + (global_settings.web2py_runtime, DEBUG) = \ + ('gae:development', True) +else: + (global_settings.web2py_runtime, DEBUG) = \ + ('gae:production', False) + + +import gluon.main + + +def log_stats(fun): + """Function that will act as a decorator to make logging""" + def newfun(env, res): + """Log the execution time of the passed function""" + timer = lambda t: (t.time(), t.clock()) + (t0, c0) = timer(time) + executed_function = fun(env, res) + (t1, c1) = timer(time) + log_info = """**** Request: %.2fms/%.2fms (real time/cpu time)""" + log_info = log_info % ((t1 - t0) * 1000, (c1 - c0) * 1000) + logging.info(log_info) + return executed_function + return newfun + + +logging.basicConfig(level=logging.INFO) + + +def wsgiapp(env, res): + """Return the wsgiapp""" + if env['PATH_INFO'] == '/_ah/queue/default': + if KEEP_CACHED: + delta = datetime.timedelta(seconds=10) + taskqueue.add(eta=datetime.datetime.now() + delta) + res('200 OK',[('Content-Type','text/plain')]) + return [''] + env['PATH_INFO'] = env['PATH_INFO'].encode('utf8') + + #this deals with a problem where GAE development server seems to forget + # the path between requests + if global_settings.web2py_runtime == 'gae:development': + gluon.admin.create_missing_folders() + + return gluon.main.wsgibase(env, res) + + +if LOG_STATS or DEBUG: + wsgiapp = log_stats(wsgiapp) + + +if AUTO_RETRY: + from gluon.contrib.gae_retry import autoretry_datastore_timeouts + autoretry_datastore_timeouts() + + +def main(): + """Run the wsgi app""" + if APPSTATS: + run_wsgi_app(wsgiapp) + else: + wsgiref.handlers.CGIHandler().run(wsgiapp) + +if __name__ == '__main__': + main() ADDED gluon/__init__.py Index: gluon/__init__.py ================================================================== --- gluon/__init__.py +++ gluon/__init__.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +This file is part of the web2py Web Framework +Copyrighted by Massimo Di Pierro +License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) + + +Web2Py framework modules +======================== +""" + +__all__ = ['A', 'B', 'BEAUTIFY', 'BODY', 'BR', 'CAT', 'CENTER', 'CLEANUP', 'CODE', 'CRYPT', 'DAL', 'DIV', 'EM', 'EMBED', 'FIELDSET', 'FORM', 'Field', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'HEAD', 'HR', 'HTML', 'HTTP', 'I', 'IFRAME', 'IMG', 'INPUT', 'IS_ALPHANUMERIC', 'IS_DATE', 'IS_DATETIME', 'IS_DATETIME_IN_RANGE', 'IS_DATE_IN_RANGE', 'IS_DECIMAL_IN_RANGE', 'IS_EMAIL', 'IS_EMPTY_OR', 'IS_EQUAL_TO', 'IS_EXPR', 'IS_FLOAT_IN_RANGE', 'IS_IMAGE', 'IS_INT_IN_RANGE', 'IS_IN_DB', 'IS_IN_SET', 'IS_IPV4', 'IS_LENGTH', 'IS_LIST_OF', 'IS_LOWER', 'IS_MATCH', 'IS_NOT_EMPTY', 'IS_NOT_IN_DB', 'IS_NULL_OR', 'IS_SLUG', 'IS_STRONG', 'IS_TIME', 'IS_UPLOAD_FILENAME', 'IS_UPPER', 'IS_URL', 'LABEL', 'LEGEND', 'LI', 'LINK', 'MARKMIN', 'MENU', 'META', 'OBJECT', 'OL', 'ON', 'OPTGROUP', 'OPTION', 'P', 'PRE', 'SCRIPT', 'SELECT', 'SPAN', 'SQLFORM', 'SQLTABLE', 'STYLE', 'TABLE', 'TAG', 'TBODY', 'TD', 'TEXTAREA', 'TFOOT', 'TH', 'THEAD', 'TITLE', 'TR', 'TT', 'UL', 'URL', 'XHTML', 'XML','redirect','current','embed64'] + +from globals import current +from html import * +from validators import * +from http import redirect, HTTP +from dal import DAL, Field +from sqlhtml import SQLFORM, SQLTABLE + ADDED gluon/admin.py Index: gluon/admin.py ================================================================== --- gluon/admin.py +++ gluon/admin.py @@ -0,0 +1,453 @@ +""" +This file is part of the web2py Web Framework +Copyrighted by Massimo Di Pierro +License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) + +Utility functions for the Admin application +=========================================== +""" +import os +import sys +import traceback +import zipfile +import urllib +from shutil import rmtree +from utils import web2py_uuid +from fileutils import w2p_pack, w2p_unpack, w2p_pack_plugin, w2p_unpack_plugin +from fileutils import up, fix_newlines, abspath, recursive_unlink +from fileutils import read_file, write_file +from restricted import RestrictedError +from settings import global_settings + +def apath(path='', r=None): + """ + Builds a path inside an application folder + + Parameters + ---------- + path: + path within the application folder + r: + the global request object + + """ + + opath = up(r.folder) + while path[:3] == '../': + (opath, path) = (up(opath), path[3:]) + return os.path.join(opath, path).replace('\\', '/') + + +def app_pack(app, request): + """ + Builds a w2p package for the application + + Parameters + ---------- + app: + application name + request: + the global request object + + Returns + ------- + filename: + filename of the w2p file or None on error + """ + try: + app_cleanup(app, request) + filename = apath('../deposit/%s.w2p' % app, request) + w2p_pack(filename, apath(app, request)) + return filename + except Exception: + return False + + +def app_pack_compiled(app, request): + """ + Builds a w2p bytecode-compiled package for the application + + Parameters + ---------- + app: + application name + request: + the global request object + + Returns + ------- + filename: + filename of the w2p file or None on error + """ + + try: + filename = apath('../deposit/%s.w2p' % app, request) + w2p_pack(filename, apath(app, request), compiled=True) + return filename + except Exception: + return None + +def app_cleanup(app, request): + """ + Removes session, cache and error files + + Parameters + ---------- + app: + application name + request: + the global request object + """ + r = True + + # Remove error files + path = apath('%s/errors/' % app, request) + if os.path.exists(path): + for f in os.listdir(path): + try: + if f[:1]!='.': os.unlink(os.path.join(path,f)) + except IOError: + r = False + + # Remove session files + path = apath('%s/sessions/' % app, request) + if os.path.exists(path): + for f in os.listdir(path): + try: + if f[:1]!='.': recursive_unlink(os.path.join(path,f)) + except IOError: + r = False + + # Remove cache files + path = apath('%s/sessions/' % app, request) + if os.path.exists(path): + for f in os.listdir(path): + try: + if f[:1]!='.': os.unlink(os.path.join(path,f)) + except IOError: + r = False + return r + + +def app_compile(app, request): + """ + Compiles the application + + Parameters + ---------- + app: + application name + request: + the global request object + """ + from compileapp import compile_application, remove_compiled_application + folder = apath(app, request) + try: + compile_application(folder) + return None + except (Exception, RestrictedError): + tb = traceback.format_exc(sys.exc_info) + remove_compiled_application(folder) + return tb + +def app_create(app, request,force=False,key=None): + """ + Create a copy of welcome.w2p (scaffolding) app + + Parameters + ---------- + app: + application name + request: + the global request object + + """ + try: + path = apath(app, request) + os.mkdir(path) + except: + if not force: + return False + try: + w2p_unpack('welcome.w2p', path) + for subfolder in ['models','views','controllers', 'databases', + 'modules','cron','errors','sessions', + 'languages','static','private','uploads']: + subpath = os.path.join(path,subfolder) + if not os.path.exists(subpath): + os.mkdir(subpath) + db = os.path.join(path, 'models', 'db.py') + if os.path.exists(db): + data = read_file(db) + data = data.replace('', + 'sha512:'+(key or web2py_uuid())) + write_file(db, data) + return True + except: + rmtree(path) + return False + + +def app_install(app, fobj, request, filename, overwrite=None): + """ + Installs an application: + + - Identifies file type by filename + - Writes `fobj` contents to the `../deposit/` folder + - Calls `w2p_unpack()` to do the job. + + Parameters + ---------- + app: + new application name + fobj: + file object containing the application to be installed + request: + the global request object + filename: + original filename of the `fobj`, required to determine extension + + Returns + ------- + upname: + name of the file where app is temporarily stored or `None` on failure + """ + did_mkdir = False + if filename[-4:] == '.w2p': + extension = 'w2p' + elif filename[-7:] == '.tar.gz': + extension = 'tar.gz' + else: + extension = 'tar' + upname = apath('../deposit/%s.%s' % (app, extension), request) + + try: + write_file(upname, fobj.read(), 'wb') + path = apath(app, request) + if not overwrite: + os.mkdir(path) + did_mkdir = True + w2p_unpack(upname, path) + if extension != 'tar': + os.unlink(upname) + fix_newlines(path) + return upname + except Exception: + if did_mkdir: + rmtree(path) + return False + + +def app_uninstall(app, request): + """ + Uninstalls the application. + + Parameters + ---------- + app: + application name + request: + the global request object + + Returns + ------- + `True` on success, `False` on failure + """ + try: + # Hey App, this is your end... + path = apath(app, request) + rmtree(path) + return True + except Exception: + return False + +def plugin_pack(app, plugin_name, request): + """ + Builds a w2p package for the application + + Parameters + ---------- + app: + application name + plugin_name: + the name of the plugin without plugin_ prefix + request: + the current request app + + Returns + ------- + filename: + filename of the w2p file or None on error + """ + try: + filename = apath('../deposit/web2py.plugin.%s.w2p' % plugin_name, request) + w2p_pack_plugin(filename, apath(app, request), plugin_name) + return filename + except Exception: + return False + +def plugin_install(app, fobj, request, filename): + """ + Installs an application: + + - Identifies file type by filename + - Writes `fobj` contents to the `../deposit/` folder + - Calls `w2p_unpack()` to do the job. + + Parameters + ---------- + app: + new application name + fobj: + file object containing the application to be installed + request: + the global request object + filename: + original filename of the `fobj`, required to determine extension + + Returns + ------- + upname: + name of the file where app is temporarily stored or `None` on failure + """ + + upname = apath('../deposit/%s' % filename, request) + + try: + write_file(upname, fobj.read(), 'wb') + path = apath(app, request) + w2p_unpack_plugin(upname, path) + fix_newlines(path) + return upname + except Exception: + os.unlink(upname) + return False + +def check_new_version(myversion, version_URL): + """ + Compares current web2py's version with the latest stable web2py version. + + Parameters + ---------- + myversion: + the current version as stored in file `web2py/VERSION` + version_URL: + the URL that contains the version of the latest stable release + + Returns + ------- + state: + `True` if upgrade available, `False` if current version if up-to-date, + -1 on error + version: + the most up-to-version available + """ + try: + from urllib import urlopen + version = urlopen(version_URL).read() + except Exception: + return -1, myversion + + if version > myversion: + return True, version + else: + return False, version + +def unzip(filename, dir, subfolder=''): + """ + Unzips filename into dir (.zip only, no .gz etc) + if subfolder!='' it unzip only files in subfolder + """ + filename = abspath(filename) + if not zipfile.is_zipfile(filename): + raise RuntimeError, 'Not a valid zipfile' + zf = zipfile.ZipFile(filename) + if not subfolder.endswith('/'): + subfolder = subfolder + '/' + n = len(subfolder) + for name in sorted(zf.namelist()): + if not name.startswith(subfolder): + continue + #print name[n:] + if name.endswith('/'): + folder = os.path.join(dir,name[n:]) + if not os.path.exists(folder): + os.mkdir(folder) + else: + write_file(os.path.join(dir, name[n:]), zf.read(name), 'wb') + + +def upgrade(request, url='http://web2py.com'): + """ + Upgrades web2py (src, osx, win) is a new version is posted. + It detects whether src, osx or win is running and downloads the right one + + Parameters + ---------- + request: + the current request object, required to determine version and path + url: + the incomplete url where to locate the latest web2py + actual url is url+'/examples/static/web2py_(src|osx|win).zip' + + Returns + ------- + True on success, False on failure (network problem or old version) + """ + web2py_version = request.env.web2py_version + gluon_parent = request.env.gluon_parent + if not gluon_parent.endswith('/'): + gluon_parent = gluon_parent + '/' + (check, version) = check_new_version(web2py_version, + url+'/examples/default/version') + if not check: + return (False, 'Already latest version') + if os.path.exists(os.path.join(gluon_parent, 'web2py.exe')): + version_type = 'win' + destination = gluon_parent + subfolder = 'web2py/' + elif gluon_parent.endswith('/Contents/Resources/'): + version_type = 'osx' + destination = gluon_parent[:-len('/Contents/Resources/')] + subfolder = 'web2py/web2py.app/' + else: + version_type = 'src' + destination = gluon_parent + subfolder = 'web2py/' + + full_url = url + '/examples/static/web2py_%s.zip' % version_type + filename = abspath('web2py_%s_downloaded.zip' % version_type) + file = None + try: + write_file(filename, urllib.urlopen(full_url).read(), 'wb') + except Exception,e: + return False, e + try: + unzip(filename, destination, subfolder) + return True, None + except Exception,e: + return False, e + +def add_path_first(path): + sys.path = [path]+[p for p in sys.path if (not p==path and not p==(path+'/'))] + +def create_missing_folders(): + if not global_settings.web2py_runtime_gae: + for path in ('applications', 'deposit', 'site-packages', 'logs'): + path = abspath(path, gluon=True) + if not os.path.exists(path): + os.mkdir(path) + paths = (global_settings.gluon_parent, abspath('site-packages', gluon=True), abspath('gluon', gluon=True), '') + [add_path_first(path) for path in paths] + +def create_missing_app_folders(request): + if not global_settings.web2py_runtime_gae: + if request.folder not in global_settings.app_folders: + for subfolder in ('models', 'views', 'controllers', 'databases', + 'modules', 'cron', 'errors', 'sessions', + 'languages', 'static', 'private', 'uploads'): + path = os.path.join(request.folder, subfolder) + if not os.path.exists(path): + os.mkdir(path) + global_settings.app_folders.add(request.folder) + ADDED gluon/cfs.py Index: gluon/cfs.py ================================================================== --- gluon/cfs.py +++ gluon/cfs.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +This file is part of the web2py Web Framework +Copyrighted by Massimo Di Pierro +License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) + +Functions required to execute app components +============================================ + +FOR INTERNAL USE ONLY +""" + +import os +import stat +import thread +from fileutils import read_file + +cfs = {} # for speed-up +cfs_lock = thread.allocate_lock() # and thread safety + + +def getcfs(key, filename, filter=None): + """ + Caches the *filtered* file `filename` with `key` until the file is + modified. + + :param key: the cache key + :param filename: the file to cache + :param filter: is the function used for filtering. Normally `filename` is a + .py file and `filter` is a function that bytecode compiles the file. + In this way the bytecode compiled file is cached. (Default = None) + + This is used on Google App Engine since pyc files cannot be saved. + """ + t = os.stat(filename)[stat.ST_MTIME] + cfs_lock.acquire() + item = cfs.get(key, None) + cfs_lock.release() + if item and item[0] == t: + return item[1] + if not filter: + data = read_file(filename) + else: + data = filter() + cfs_lock.acquire() + cfs[key] = (t, data) + cfs_lock.release() + return data + ADDED gluon/compileapp.py Index: gluon/compileapp.py ================================================================== --- gluon/compileapp.py +++ gluon/compileapp.py @@ -0,0 +1,571 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +This file is part of the web2py Web Framework +Copyrighted by Massimo Di Pierro +License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) + +Functions required to execute app components +============================================ + +FOR INTERNAL USE ONLY +""" + +import re +import sys +import fnmatch +import os +import copy +import random +import __builtin__ +from storage import Storage, List +from template import parse_template +from restricted import restricted, compile2 +from fileutils import mktree, listdir, read_file, write_file +from myregex import regex_expose +from languages import translator +from dal import BaseAdapter, SQLDB, SQLField, DAL, Field +from sqlhtml import SQLFORM, SQLTABLE +from cache import Cache +from globals import current +import settings +from cfs import getcfs +import html +import validators +from http import HTTP, redirect +import marshal +import shutil +import imp +import logging +logger = logging.getLogger("web2py") +import rewrite + +try: + import py_compile +except: + logger.warning('unable to import py_compile') + +is_gae = settings.global_settings.web2py_runtime_gae +is_jython = settings.global_settings.is_jython = 'java' in sys.platform.lower() or hasattr(sys, 'JYTHON_JAR') or str(sys.copyright).find('Jython') > 0 + +TEST_CODE = \ + r""" +def _TEST(): + import doctest, sys, cStringIO, types, cgi, gluon.fileutils + if not gluon.fileutils.check_credentials(request): + raise HTTP(401, web2py_error='invalid credentials') + stdout = sys.stdout + html = '

    Testing controller "%s.py" ... done.


    \n' \ + % request.controller + for key in sorted([key for key in globals() if not key in __symbols__+['_TEST']]): + eval_key = eval(key) + if type(eval_key) == types.FunctionType: + number_doctests = sum([len(ds.examples) for ds in doctest.DocTestFinder().find(eval_key)]) + if number_doctests>0: + sys.stdout = cStringIO.StringIO() + name = '%s/controllers/%s.py in %s.__doc__' \ + % (request.folder, request.controller, key) + doctest.run_docstring_examples(eval_key, + globals(), False, name=name) + report = sys.stdout.getvalue().strip() + if report: + pf = 'failed' + else: + pf = 'passed' + html += '

    Function %s [%s]

    \n' \ + % (pf, key, pf) + if report: + html += CODE(report, language='web2py', \ + link='/examples/global/vars/').xml() + html += '
    \n' + else: + html += \ + '

    Function %s [no doctests]


    \n' \ + % (key) + response._vars = html + sys.stdout = stdout +_TEST() +""" + +class mybuiltin(object): + """ + NOTE could simple use a dict and populate it, + NOTE not sure if this changes things though if monkey patching import..... + """ + #__builtins__ + def __getitem__(self, key): + try: + return getattr(__builtin__, key) + except AttributeError: + raise KeyError, key + def __setitem__(self, key, value): + setattr(self, key, value) + +class LoadFactory(object): + """ + Attention: this helper is new and experimental + """ + def __init__(self,environment): + self.environment = environment + def __call__(self, c=None, f='index', args=[], vars={}, + extension=None, target=None,ajax=False,ajax_trap=False, + url=None,user_signature=False, content='loading...',**attr): + import globals + target = target or 'c'+str(random.random())[2:] + attr['_id']=target + request = self.environment['request'] + if not isinstance(vars,Storage): + vars = Storage(vars) + if '.' in f: + f, extension = f.split('.',1) + if url or ajax: + url = url or html.URL(request.application, c, f, r=request, + args=args, vars=vars, extension=extension, + user_signature=user_signature) + script = html.SCRIPT('web2py_component("%s","%s")' % (url, target), + _type="text/javascript") + return html.TAG[''](script, html.DIV(content,**attr)) + else: + if not isinstance(args,(list,tuple)): + args = [args] + c = c or request.controller + + other_request = Storage() + for key, value in request.items(): + other_request[key] = value + other_request['env'] = Storage() + for key, value in request.env.items(): + other_request.env['key'] = value + other_request.controller = c + other_request.function = f + other_request.extension = extension or request.extension + other_request.args = List(args) + other_request.vars = vars + other_request.get_vars = vars + other_request.post_vars = Storage() + other_response = globals.Response() + other_request.env.path_info = '/' + \ + '/'.join([request.application,c,f] + \ + map(str, other_request.args)) + other_request.env.query_string = \ + vars and html.URL(vars=vars).split('?')[1] or '' + other_request.env.http_web2py_component_location = \ + request.env.path_info + other_request.cid = target + other_request.env.http_web2py_component_element = target + other_response.view = '%s/%s.%s' % (c,f, other_request.extension) + other_environment = copy.copy(self.environment) + other_response._view_environment = other_environment + other_response.generic_patterns = \ + copy.copy(current.response.generic_patterns) + other_environment['request'] = other_request + other_environment['response'] = other_response + + ## some magic here because current are thread-locals + + original_request, current.request = current.request, other_request + original_response, current.response = current.response, other_response + page = run_controller_in(c, f, other_environment) + if isinstance(page, dict): + other_response._vars = page + for key in page: + other_response._view_environment[key] = page[key] + run_view_in(other_response._view_environment) + page = other_response.body.getvalue() + current.request, current.response = original_request, original_response + js = None + if ajax_trap: + link = html.URL(request.application, c, f, r=request, + args=args, vars=vars, extension=extension, + user_signature=user_signature) + js = "web2py_trap_form('%s','%s');" % (link, target) + script = js and html.SCRIPT(js,_type="text/javascript") or '' + return html.TAG[''](html.DIV(html.XML(page),**attr),script) + + +def local_import_aux(name, force=False, app='welcome'): + """ + In apps, instead of importing a local module + (in applications/app/modules) with:: + + import a.b.c as d + + you should do:: + + d = local_import('a.b.c') + + or (to force a reload): + + d = local_import('a.b.c', reload=True) + + This prevents conflict between applications and un-necessary execs. + It can be used to import any module, including regular Python modules. + """ + items = name.replace('/','.') + name = "applications.%s.modules.%s" % (app, items) + module = __import__(name) + for item in name.split(".")[1:]: + module = getattr(module, item) + if force: + reload(module) + return module + + +""" +OLD IMPLEMENTATION: + items = name.replace('/','.').split('.') + filename, modulepath = items[-1], os.path.join(apath,'modules',*items[:-1]) + imp.acquire_lock() + try: + file=None + (file,path,desc) = imp.find_module(filename,[modulepath]+sys.path) + if not path in sys.modules or reload: + if is_gae: + module={} + execfile(path,{},module) + module=Storage(module) + else: + module = imp.load_module(path,file,path,desc) + sys.modules[path] = module + else: + module = sys.modules[path] + except Exception, e: + module = None + if file: + file.close() + imp.release_lock() + if not module: + raise ImportError, "cannot find module %s in %s" % (filename, modulepath) + return module +""" + +def build_environment(request, response, session, store_current=True): + """ + Build the environment dictionary into which web2py files are executed. + """ + + environment = {} + for key in html.__all__: + environment[key] = getattr(html, key) + for key in validators.__all__: + environment[key] = getattr(validators, key) + if not request.env: + request.env = Storage() + + t = environment['T'] = translator(request) + c = environment['cache'] = Cache(request) + if store_current: + current.request = request + current.response = response + current.session = session + current.T = t + current.cache = c + + global __builtins__ + if is_jython: # jython hack + __builtins__ = mybuiltin() + else: + __builtins__['__import__'] = __builtin__.__import__ + environment['__builtins__'] = __builtins__ + environment['HTTP'] = HTTP + environment['redirect'] = redirect + environment['request'] = request + environment['response'] = response + environment['session'] = session + environment['DAL'] = DAL + environment['Field'] = Field + environment['SQLDB'] = SQLDB # for backward compatibility + environment['SQLField'] = SQLField # for backward compatibility + environment['SQLFORM'] = SQLFORM + environment['SQLTABLE'] = SQLTABLE + environment['LOAD'] = LoadFactory(environment) + environment['local_import'] = \ + lambda name, reload=False, app=request.application:\ + local_import_aux(name,reload,app) + BaseAdapter.set_folder(os.path.join(request.folder, 'databases')) + response._view_environment = copy.copy(environment) + return environment + + +def save_pyc(filename): + """ + Bytecode compiles the file `filename` + """ + py_compile.compile(filename) + + +def read_pyc(filename): + """ + Read the code inside a bytecode compiled file if the MAGIC number is + compatible + + :returns: a code object + """ + data = read_file(filename, 'rb') + if not is_gae and data[:4] != imp.get_magic(): + raise SystemError, 'compiled code is incompatible' + return marshal.loads(data[8:]) + + +def compile_views(folder): + """ + Compiles all the views in the application specified by `folder` + """ + + path = os.path.join(folder, 'views') + for file in listdir(path, '^[\w/]+\.\w+$'): + data = parse_template(file, path) + filename = ('views/%s.py' % file).replace('/', '_').replace('\\', '_') + filename = os.path.join(folder, 'compiled', filename) + write_file(filename, data) + save_pyc(filename) + os.unlink(filename) + + +def compile_models(folder): + """ + Compiles all the models in the application specified by `folder` + """ + + path = os.path.join(folder, 'models') + for file in listdir(path, '.+\.py$'): + data = read_file(os.path.join(path, file)) + filename = os.path.join(folder, 'compiled','models',file) + mktree(filename) + write_file(filename, data) + save_pyc(filename) + os.unlink(filename) + + +def compile_controllers(folder): + """ + Compiles all the controllers in the application specified by `folder` + """ + + path = os.path.join(folder, 'controllers') + for file in listdir(path, '.+\.py$'): + ### why is this here? save_pyc(os.path.join(path, file)) + data = read_file(os.path.join(path,file)) + exposed = regex_expose.findall(data) + for function in exposed: + command = data + "\nresponse._vars=response._caller(%s)\n" % \ + function + filename = os.path.join(folder, 'compiled', ('controllers/' + + file[:-3]).replace('/', '_') + + '_' + function + '.py') + write_file(filename, command) + save_pyc(filename) + os.unlink(filename) + + +def run_models_in(environment): + """ + Runs all models (in the app specified by the current folder) + It tries pre-compiled models first before compiling them. + """ + + folder = environment['request'].folder + c = environment['request'].controller + f = environment['request'].function + cpath = os.path.join(folder, 'compiled') + if os.path.exists(cpath): + for model in listdir(cpath, '^models_\w+\.pyc$', 0): + restricted(read_pyc(model), environment, layer=model) + path = os.path.join(cpath, 'models') + models = listdir(path, '^\w+\.pyc$',0,sort=False) + compiled=True + else: + path = os.path.join(folder, 'models') + models = listdir(path, '^\w+\.py$',0,sort=False) + compiled=False + paths = (path, os.path.join(path,c), os.path.join(path,c,f)) + for model in models: + if not os.path.split(model)[0] in paths and c!='appadmin': + continue + elif compiled: + code = read_pyc(model) + elif is_gae: + code = getcfs(model, model, + lambda: compile2(read_file(model), model)) + else: + code = getcfs(model, model, None) + restricted(code, environment, layer=model) + + +def run_controller_in(controller, function, environment): + """ + Runs the controller.function() (for the app specified by + the current folder). + It tries pre-compiled controller_function.pyc first before compiling it. + """ + + # if compiled should run compiled! + + folder = environment['request'].folder + path = os.path.join(folder, 'compiled') + badc = 'invalid controller (%s/%s)' % (controller, function) + badf = 'invalid function (%s/%s)' % (controller, function) + if os.path.exists(path): + filename = os.path.join(path, 'controllers_%s_%s.pyc' + % (controller, function)) + if not os.path.exists(filename): + raise HTTP(404, + rewrite.thread.routes.error_message % badf, + web2py_error=badf) + restricted(read_pyc(filename), environment, layer=filename) + elif function == '_TEST': + # TESTING: adjust the path to include site packages + from settings import global_settings + from admin import abspath, add_path_first + paths = (global_settings.gluon_parent, abspath('site-packages', gluon=True), abspath('gluon', gluon=True), '') + [add_path_first(path) for path in paths] + # TESTING END + + filename = os.path.join(folder, 'controllers/%s.py' + % controller) + if not os.path.exists(filename): + raise HTTP(404, + rewrite.thread.routes.error_message % badc, + web2py_error=badc) + environment['__symbols__'] = environment.keys() + code = read_file(filename) + code += TEST_CODE + restricted(code, environment, layer=filename) + else: + filename = os.path.join(folder, 'controllers/%s.py' + % controller) + if not os.path.exists(filename): + raise HTTP(404, + rewrite.thread.routes.error_message % badc, + web2py_error=badc) + code = read_file(filename) + exposed = regex_expose.findall(code) + if not function in exposed: + raise HTTP(404, + rewrite.thread.routes.error_message % badf, + web2py_error=badf) + code = "%s\nresponse._vars=response._caller(%s)\n" % (code, function) + if is_gae: + layer = filename + ':' + function + code = getcfs(layer, filename, lambda: compile2(code,layer)) + restricted(code, environment, filename) + response = environment['response'] + vars=response._vars + if response.postprocessing: + for p in response.postprocessing: + vars = p(vars) + if isinstance(vars,unicode): + vars = vars.encode('utf8') + if hasattr(vars,'xml'): + vars = vars.xml() + return vars + +def run_view_in(environment): + """ + Executes the view for the requested action. + The view is the one specified in `response.view` or determined by the url + or `view/generic.extension` + It tries the pre-compiled views_controller_function.pyc before compiling it. + """ + + request = environment['request'] + response = environment['response'] + folder = request.folder + path = os.path.join(folder, 'compiled') + badv = 'invalid view (%s)' % response.view + patterns = response.generic_patterns or [] + regex = re.compile('|'.join(fnmatch.translate(r) for r in patterns)) + short_action = '%(controller)s/%(function)s.%(extension)s' % request + allow_generic = patterns and regex.search(short_action) + if not isinstance(response.view, str): + ccode = parse_template(response.view, os.path.join(folder, 'views'), + context=environment) + restricted(ccode, environment, 'file stream') + elif os.path.exists(path): + x = response.view.replace('/', '_') + files = ['views_%s.pyc' % x] + if allow_generic: + files.append('views_generic.%s.pyc' % request.extension) + # for backward compatibility + if request.extension == 'html': + files.append('views_%s.pyc' % x[:-5]) + if allow_generic: + files.append('views_generic.pyc') + # end backward compatibility code + for f in files: + filename = os.path.join(path,f) + if os.path.exists(filename): + code = read_pyc(filename) + restricted(code, environment, layer=filename) + return + raise HTTP(404, + rewrite.thread.routes.error_message % badv, + web2py_error=badv) + else: + filename = os.path.join(folder, 'views', response.view) + if not os.path.exists(filename) and allow_generic: + response.view = 'generic.' + request.extension + filename = os.path.join(folder, 'views', response.view) + if not os.path.exists(filename): + raise HTTP(404, + rewrite.thread.routes.error_message % badv, + web2py_error=badv) + layer = filename + if is_gae: + ccode = getcfs(layer, filename, + lambda: compile2(parse_template(response.view, + os.path.join(folder, 'views'), + context=environment),layer)) + else: + ccode = parse_template(response.view, + os.path.join(folder, 'views'), + context=environment) + restricted(ccode, environment, layer) + +def remove_compiled_application(folder): + """ + Deletes the folder `compiled` containing the compiled application. + """ + try: + shutil.rmtree(os.path.join(folder, 'compiled')) + path = os.path.join(folder, 'controllers') + for file in listdir(path,'.*\.pyc$',drop=False): + os.unlink(file) + except OSError: + pass + + +def compile_application(folder): + """ + Compiles all models, views, controller for the application in `folder`. + """ + remove_compiled_application(folder) + os.mkdir(os.path.join(folder, 'compiled')) + compile_models(folder) + compile_controllers(folder) + compile_views(folder) + + +def test(): + """ + Example:: + + >>> import traceback, types + >>> environment={'x':1} + >>> open('a.py', 'w').write('print 1/x') + >>> save_pyc('a.py') + >>> os.unlink('a.py') + >>> if type(read_pyc('a.pyc'))==types.CodeType: print 'code' + code + >>> exec read_pyc('a.pyc') in environment + 1 + """ + + return + + +if __name__ == '__main__': + import doctest + doctest.testmod() + ADDED gluon/contenttype.py Index: gluon/contenttype.py ================================================================== --- gluon/contenttype.py +++ gluon/contenttype.py @@ -0,0 +1,718 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +This file is part of the web2py Web Framework +Copyrighted by Massimo Di Pierro +License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) + +CONTENT_TYPE dictionary created against freedesktop.org' shared mime info +database version 0.70. +""" + +__all__ = ['contenttype'] + +CONTENT_TYPE = { + '.load': 'text/html', + '.123': 'application/vnd.lotus-1-2-3', + '.3ds': 'image/x-3ds', + '.3g2': 'video/3gpp', + '.3ga': 'video/3gpp', + '.3gp': 'video/3gpp', + '.3gpp': 'video/3gpp', + '.602': 'application/x-t602', + '.669': 'audio/x-mod', + '.7z': 'application/x-7z-compressed', + '.a': 'application/x-archive', + '.aac': 'audio/mp4', + '.abw': 'application/x-abiword', + '.abw.crashed': 'application/x-abiword', + '.abw.gz': 'application/x-abiword', + '.ac3': 'audio/ac3', + '.ace': 'application/x-ace', + '.adb': 'text/x-adasrc', + '.ads': 'text/x-adasrc', + '.afm': 'application/x-font-afm', + '.ag': 'image/x-applix-graphics', + '.ai': 'application/illustrator', + '.aif': 'audio/x-aiff', + '.aifc': 'audio/x-aiff', + '.aiff': 'audio/x-aiff', + '.al': 'application/x-perl', + '.alz': 'application/x-alz', + '.amr': 'audio/amr', + '.ani': 'application/x-navi-animation', + '.anim[1-9j]': 'video/x-anim', + '.anx': 'application/annodex', + '.ape': 'audio/x-ape', + '.arj': 'application/x-arj', + '.arw': 'image/x-sony-arw', + '.as': 'application/x-applix-spreadsheet', + '.asc': 'text/plain', + '.asf': 'video/x-ms-asf', + '.asp': 'application/x-asp', + '.ass': 'text/x-ssa', + '.asx': 'audio/x-ms-asx', + '.atom': 'application/atom+xml', + '.au': 'audio/basic', + '.avi': 'video/x-msvideo', + '.aw': 'application/x-applix-word', + '.awb': 'audio/amr-wb', + '.awk': 'application/x-awk', + '.axa': 'audio/annodex', + '.axv': 'video/annodex', + '.bak': 'application/x-trash', + '.bcpio': 'application/x-bcpio', + '.bdf': 'application/x-font-bdf', + '.bib': 'text/x-bibtex', + '.bin': 'application/octet-stream', + '.blend': 'application/x-blender', + '.blender': 'application/x-blender', + '.bmp': 'image/bmp', + '.bz': 'application/x-bzip', + '.bz2': 'application/x-bzip', + '.c': 'text/x-csrc', + '.c++': 'text/x-c++src', + '.cab': 'application/vnd.ms-cab-compressed', + '.cb7': 'application/x-cb7', + '.cbr': 'application/x-cbr', + '.cbt': 'application/x-cbt', + '.cbz': 'application/x-cbz', + '.cc': 'text/x-c++src', + '.cdf': 'application/x-netcdf', + '.cdr': 'application/vnd.corel-draw', + '.cer': 'application/x-x509-ca-cert', + '.cert': 'application/x-x509-ca-cert', + '.cgm': 'image/cgm', + '.chm': 'application/x-chm', + '.chrt': 'application/x-kchart', + '.class': 'application/x-java', + '.cls': 'text/x-tex', + '.cmake': 'text/x-cmake', + '.cpio': 'application/x-cpio', + '.cpio.gz': 'application/x-cpio-compressed', + '.cpp': 'text/x-c++src', + '.cr2': 'image/x-canon-cr2', + '.crt': 'application/x-x509-ca-cert', + '.crw': 'image/x-canon-crw', + '.cs': 'text/x-csharp', + '.csh': 'application/x-csh', + '.css': 'text/css', + '.cssl': 'text/css', + '.csv': 'text/csv', + '.cue': 'application/x-cue', + '.cur': 'image/x-win-bitmap', + '.cxx': 'text/x-c++src', + '.d': 'text/x-dsrc', + '.dar': 'application/x-dar', + '.dbf': 'application/x-dbf', + '.dc': 'application/x-dc-rom', + '.dcl': 'text/x-dcl', + '.dcm': 'application/dicom', + '.dcr': 'image/x-kodak-dcr', + '.dds': 'image/x-dds', + '.deb': 'application/x-deb', + '.der': 'application/x-x509-ca-cert', + '.desktop': 'application/x-desktop', + '.dia': 'application/x-dia-diagram', + '.diff': 'text/x-patch', + '.divx': 'video/x-msvideo', + '.djv': 'image/vnd.djvu', + '.djvu': 'image/vnd.djvu', + '.dng': 'image/x-adobe-dng', + '.doc': 'application/msword', + '.docbook': 'application/docbook+xml', + '.docm': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + '.dot': 'text/vnd.graphviz', + '.dsl': 'text/x-dsl', + '.dtd': 'application/xml-dtd', + '.dtx': 'text/x-tex', + '.dv': 'video/dv', + '.dvi': 'application/x-dvi', + '.dvi.bz2': 'application/x-bzdvi', + '.dvi.gz': 'application/x-gzdvi', + '.dwg': 'image/vnd.dwg', + '.dxf': 'image/vnd.dxf', + '.e': 'text/x-eiffel', + '.egon': 'application/x-egon', + '.eif': 'text/x-eiffel', + '.el': 'text/x-emacs-lisp', + '.emf': 'image/x-emf', + '.emp': 'application/vnd.emusic-emusic_package', + '.ent': 'application/xml-external-parsed-entity', + '.eps': 'image/x-eps', + '.eps.bz2': 'image/x-bzeps', + '.eps.gz': 'image/x-gzeps', + '.epsf': 'image/x-eps', + '.epsf.bz2': 'image/x-bzeps', + '.epsf.gz': 'image/x-gzeps', + '.epsi': 'image/x-eps', + '.epsi.bz2': 'image/x-bzeps', + '.epsi.gz': 'image/x-gzeps', + '.epub': 'application/epub+zip', + '.erl': 'text/x-erlang', + '.es': 'application/ecmascript', + '.etheme': 'application/x-e-theme', + '.etx': 'text/x-setext', + '.exe': 'application/x-ms-dos-executable', + '.exr': 'image/x-exr', + '.ez': 'application/andrew-inset', + '.f': 'text/x-fortran', + '.f90': 'text/x-fortran', + '.f95': 'text/x-fortran', + '.fb2': 'application/x-fictionbook+xml', + '.fig': 'image/x-xfig', + '.fits': 'image/fits', + '.fl': 'application/x-fluid', + '.flac': 'audio/x-flac', + '.flc': 'video/x-flic', + '.fli': 'video/x-flic', + '.flv': 'video/x-flv', + '.flw': 'application/x-kivio', + '.fo': 'text/x-xslfo', + '.for': 'text/x-fortran', + '.g3': 'image/fax-g3', + '.gb': 'application/x-gameboy-rom', + '.gba': 'application/x-gba-rom', + '.gcrd': 'text/directory', + '.ged': 'application/x-gedcom', + '.gedcom': 'application/x-gedcom', + '.gen': 'application/x-genesis-rom', + '.gf': 'application/x-tex-gf', + '.gg': 'application/x-sms-rom', + '.gif': 'image/gif', + '.glade': 'application/x-glade', + '.gmo': 'application/x-gettext-translation', + '.gnc': 'application/x-gnucash', + '.gnd': 'application/gnunet-directory', + '.gnucash': 'application/x-gnucash', + '.gnumeric': 'application/x-gnumeric', + '.gnuplot': 'application/x-gnuplot', + '.gp': 'application/x-gnuplot', + '.gpg': 'application/pgp-encrypted', + '.gplt': 'application/x-gnuplot', + '.gra': 'application/x-graphite', + '.gsf': 'application/x-font-type1', + '.gsm': 'audio/x-gsm', + '.gtar': 'application/x-tar', + '.gv': 'text/vnd.graphviz', + '.gvp': 'text/x-google-video-pointer', + '.gz': 'application/x-gzip', + '.h': 'text/x-chdr', + '.h++': 'text/x-c++hdr', + '.hdf': 'application/x-hdf', + '.hh': 'text/x-c++hdr', + '.hp': 'text/x-c++hdr', + '.hpgl': 'application/vnd.hp-hpgl', + '.hpp': 'text/x-c++hdr', + '.hs': 'text/x-haskell', + '.htm': 'text/html', + '.html': 'text/html', + '.hwp': 'application/x-hwp', + '.hwt': 'application/x-hwt', + '.hxx': 'text/x-c++hdr', + '.ica': 'application/x-ica', + '.icb': 'image/x-tga', + '.icns': 'image/x-icns', + '.ico': 'image/vnd.microsoft.icon', + '.ics': 'text/calendar', + '.idl': 'text/x-idl', + '.ief': 'image/ief', + '.iff': 'image/x-iff', + '.ilbm': 'image/x-ilbm', + '.ime': 'text/x-imelody', + '.imy': 'text/x-imelody', + '.ins': 'text/x-tex', + '.iptables': 'text/x-iptables', + '.iso': 'application/x-cd-image', + '.iso9660': 'application/x-cd-image', + '.it': 'audio/x-it', + '.j2k': 'image/jp2', + '.jad': 'text/vnd.sun.j2me.app-descriptor', + '.jar': 'application/x-java-archive', + '.java': 'text/x-java', + '.jng': 'image/x-jng', + '.jnlp': 'application/x-java-jnlp-file', + '.jp2': 'image/jp2', + '.jpc': 'image/jp2', + '.jpe': 'image/jpeg', + '.jpeg': 'image/jpeg', + '.jpf': 'image/jp2', + '.jpg': 'image/jpeg', + '.jpr': 'application/x-jbuilder-project', + '.jpx': 'image/jp2', + '.js': 'application/javascript', + '.json': 'application/json', + '.k25': 'image/x-kodak-k25', + '.kar': 'audio/midi', + '.karbon': 'application/x-karbon', + '.kdc': 'image/x-kodak-kdc', + '.kdelnk': 'application/x-desktop', + '.kexi': 'application/x-kexiproject-sqlite3', + '.kexic': 'application/x-kexi-connectiondata', + '.kexis': 'application/x-kexiproject-shortcut', + '.kfo': 'application/x-kformula', + '.kil': 'application/x-killustrator', + '.kino': 'application/smil', + '.kml': 'application/vnd.google-earth.kml+xml', + '.kmz': 'application/vnd.google-earth.kmz', + '.kon': 'application/x-kontour', + '.kpm': 'application/x-kpovmodeler', + '.kpr': 'application/x-kpresenter', + '.kpt': 'application/x-kpresenter', + '.kra': 'application/x-krita', + '.ksp': 'application/x-kspread', + '.kud': 'application/x-kugar', + '.kwd': 'application/x-kword', + '.kwt': 'application/x-kword', + '.la': 'application/x-shared-library-la', + '.latex': 'text/x-tex', + '.ldif': 'text/x-ldif', + '.lha': 'application/x-lha', + '.lhs': 'text/x-literate-haskell', + '.lhz': 'application/x-lhz', + '.log': 'text/x-log', + '.ltx': 'text/x-tex', + '.lua': 'text/x-lua', + '.lwo': 'image/x-lwo', + '.lwob': 'image/x-lwo', + '.lws': 'image/x-lws', + '.ly': 'text/x-lilypond', + '.lyx': 'application/x-lyx', + '.lz': 'application/x-lzip', + '.lzh': 'application/x-lha', + '.lzma': 'application/x-lzma', + '.lzo': 'application/x-lzop', + '.m': 'text/x-matlab', + '.m15': 'audio/x-mod', + '.m2t': 'video/mpeg', + '.m3u': 'audio/x-mpegurl', + '.m3u8': 'audio/x-mpegurl', + '.m4': 'application/x-m4', + '.m4a': 'audio/mp4', + '.m4b': 'audio/x-m4b', + '.m4v': 'video/mp4', + '.mab': 'application/x-markaby', + '.man': 'application/x-troff-man', + '.mbox': 'application/mbox', + '.md': 'application/x-genesis-rom', + '.mdb': 'application/vnd.ms-access', + '.mdi': 'image/vnd.ms-modi', + '.me': 'text/x-troff-me', + '.med': 'audio/x-mod', + '.metalink': 'application/metalink+xml', + '.mgp': 'application/x-magicpoint', + '.mid': 'audio/midi', + '.midi': 'audio/midi', + '.mif': 'application/x-mif', + '.minipsf': 'audio/x-minipsf', + '.mka': 'audio/x-matroska', + '.mkv': 'video/x-matroska', + '.ml': 'text/x-ocaml', + '.mli': 'text/x-ocaml', + '.mm': 'text/x-troff-mm', + '.mmf': 'application/x-smaf', + '.mml': 'text/mathml', + '.mng': 'video/x-mng', + '.mo': 'application/x-gettext-translation', + '.mo3': 'audio/x-mo3', + '.moc': 'text/x-moc', + '.mod': 'audio/x-mod', + '.mof': 'text/x-mof', + '.moov': 'video/quicktime', + '.mov': 'video/quicktime', + '.movie': 'video/x-sgi-movie', + '.mp+': 'audio/x-musepack', + '.mp2': 'video/mpeg', + '.mp3': 'audio/mpeg', + '.mp4': 'video/mp4', + '.mpc': 'audio/x-musepack', + '.mpe': 'video/mpeg', + '.mpeg': 'video/mpeg', + '.mpg': 'video/mpeg', + '.mpga': 'audio/mpeg', + '.mpp': 'audio/x-musepack', + '.mrl': 'text/x-mrml', + '.mrml': 'text/x-mrml', + '.mrw': 'image/x-minolta-mrw', + '.ms': 'text/x-troff-ms', + '.msi': 'application/x-msi', + '.msod': 'image/x-msod', + '.msx': 'application/x-msx-rom', + '.mtm': 'audio/x-mod', + '.mup': 'text/x-mup', + '.mxf': 'application/mxf', + '.n64': 'application/x-n64-rom', + '.nb': 'application/mathematica', + '.nc': 'application/x-netcdf', + '.nds': 'application/x-nintendo-ds-rom', + '.nef': 'image/x-nikon-nef', + '.nes': 'application/x-nes-rom', + '.nfo': 'text/x-nfo', + '.not': 'text/x-mup', + '.nsc': 'application/x-netshow-channel', + '.nsv': 'video/x-nsv', + '.o': 'application/x-object', + '.obj': 'application/x-tgif', + '.ocl': 'text/x-ocl', + '.oda': 'application/oda', + '.odb': 'application/vnd.oasis.opendocument.database', + '.odc': 'application/vnd.oasis.opendocument.chart', + '.odf': 'application/vnd.oasis.opendocument.formula', + '.odg': 'application/vnd.oasis.opendocument.graphics', + '.odi': 'application/vnd.oasis.opendocument.image', + '.odm': 'application/vnd.oasis.opendocument.text-master', + '.odp': 'application/vnd.oasis.opendocument.presentation', + '.ods': 'application/vnd.oasis.opendocument.spreadsheet', + '.odt': 'application/vnd.oasis.opendocument.text', + '.oga': 'audio/ogg', + '.ogg': 'video/x-theora+ogg', + '.ogm': 'video/x-ogm+ogg', + '.ogv': 'video/ogg', + '.ogx': 'application/ogg', + '.old': 'application/x-trash', + '.oleo': 'application/x-oleo', + '.opml': 'text/x-opml+xml', + '.ora': 'image/openraster', + '.orf': 'image/x-olympus-orf', + '.otc': 'application/vnd.oasis.opendocument.chart-template', + '.otf': 'application/x-font-otf', + '.otg': 'application/vnd.oasis.opendocument.graphics-template', + '.oth': 'application/vnd.oasis.opendocument.text-web', + '.otp': 'application/vnd.oasis.opendocument.presentation-template', + '.ots': 'application/vnd.oasis.opendocument.spreadsheet-template', + '.ott': 'application/vnd.oasis.opendocument.text-template', + '.owl': 'application/rdf+xml', + '.oxt': 'application/vnd.openofficeorg.extension', + '.p': 'text/x-pascal', + '.p10': 'application/pkcs10', + '.p12': 'application/x-pkcs12', + '.p7b': 'application/x-pkcs7-certificates', + '.p7s': 'application/pkcs7-signature', + '.pack': 'application/x-java-pack200', + '.pak': 'application/x-pak', + '.par2': 'application/x-par2', + '.pas': 'text/x-pascal', + '.patch': 'text/x-patch', + '.pbm': 'image/x-portable-bitmap', + '.pcd': 'image/x-photo-cd', + '.pcf': 'application/x-cisco-vpn-settings', + '.pcf.gz': 'application/x-font-pcf', + '.pcf.z': 'application/x-font-pcf', + '.pcl': 'application/vnd.hp-pcl', + '.pcx': 'image/x-pcx', + '.pdb': 'chemical/x-pdb', + '.pdc': 'application/x-aportisdoc', + '.pdf': 'application/pdf', + '.pdf.bz2': 'application/x-bzpdf', + '.pdf.gz': 'application/x-gzpdf', + '.pef': 'image/x-pentax-pef', + '.pem': 'application/x-x509-ca-cert', + '.perl': 'application/x-perl', + '.pfa': 'application/x-font-type1', + '.pfb': 'application/x-font-type1', + '.pfx': 'application/x-pkcs12', + '.pgm': 'image/x-portable-graymap', + '.pgn': 'application/x-chess-pgn', + '.pgp': 'application/pgp-encrypted', + '.php': 'application/x-php', + '.php3': 'application/x-php', + '.php4': 'application/x-php', + '.pict': 'image/x-pict', + '.pict1': 'image/x-pict', + '.pict2': 'image/x-pict', + '.pickle': 'application/python-pickle', + '.pk': 'application/x-tex-pk', + '.pkipath': 'application/pkix-pkipath', + '.pkr': 'application/pgp-keys', + '.pl': 'application/x-perl', + '.pla': 'audio/x-iriver-pla', + '.pln': 'application/x-planperfect', + '.pls': 'audio/x-scpls', + '.pm': 'application/x-perl', + '.png': 'image/png', + '.pnm': 'image/x-portable-anymap', + '.pntg': 'image/x-macpaint', + '.po': 'text/x-gettext-translation', + '.por': 'application/x-spss-por', + '.pot': 'text/x-gettext-translation-template', + '.ppm': 'image/x-portable-pixmap', + '.pps': 'application/vnd.ms-powerpoint', + '.ppt': 'application/vnd.ms-powerpoint', + '.pptm': 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + '.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + '.ppz': 'application/vnd.ms-powerpoint', + '.prc': 'application/x-palm-database', + '.ps': 'application/postscript', + '.ps.bz2': 'application/x-bzpostscript', + '.ps.gz': 'application/x-gzpostscript', + '.psd': 'image/vnd.adobe.photoshop', + '.psf': 'audio/x-psf', + '.psf.gz': 'application/x-gz-font-linux-psf', + '.psflib': 'audio/x-psflib', + '.psid': 'audio/prs.sid', + '.psw': 'application/x-pocket-word', + '.pw': 'application/x-pw', + '.py': 'text/x-python', + '.pyc': 'application/x-python-bytecode', + '.pyo': 'application/x-python-bytecode', + '.qif': 'image/x-quicktime', + '.qt': 'video/quicktime', + '.qtif': 'image/x-quicktime', + '.qtl': 'application/x-quicktime-media-link', + '.qtvr': 'video/quicktime', + '.ra': 'audio/vnd.rn-realaudio', + '.raf': 'image/x-fuji-raf', + '.ram': 'application/ram', + '.rar': 'application/x-rar', + '.ras': 'image/x-cmu-raster', + '.raw': 'image/x-panasonic-raw', + '.rax': 'audio/vnd.rn-realaudio', + '.rb': 'application/x-ruby', + '.rdf': 'application/rdf+xml', + '.rdfs': 'application/rdf+xml', + '.reg': 'text/x-ms-regedit', + '.rej': 'application/x-reject', + '.rgb': 'image/x-rgb', + '.rle': 'image/rle', + '.rm': 'application/vnd.rn-realmedia', + '.rmj': 'application/vnd.rn-realmedia', + '.rmm': 'application/vnd.rn-realmedia', + '.rms': 'application/vnd.rn-realmedia', + '.rmvb': 'application/vnd.rn-realmedia', + '.rmx': 'application/vnd.rn-realmedia', + '.roff': 'text/troff', + '.rp': 'image/vnd.rn-realpix', + '.rpm': 'application/x-rpm', + '.rss': 'application/rss+xml', + '.rt': 'text/vnd.rn-realtext', + '.rtf': 'application/rtf', + '.rtx': 'text/richtext', + '.rv': 'video/vnd.rn-realvideo', + '.rvx': 'video/vnd.rn-realvideo', + '.s3m': 'audio/x-s3m', + '.sam': 'application/x-amipro', + '.sami': 'application/x-sami', + '.sav': 'application/x-spss-sav', + '.scm': 'text/x-scheme', + '.sda': 'application/vnd.stardivision.draw', + '.sdc': 'application/vnd.stardivision.calc', + '.sdd': 'application/vnd.stardivision.impress', + '.sdp': 'application/sdp', + '.sds': 'application/vnd.stardivision.chart', + '.sdw': 'application/vnd.stardivision.writer', + '.sgf': 'application/x-go-sgf', + '.sgi': 'image/x-sgi', + '.sgl': 'application/vnd.stardivision.writer', + '.sgm': 'text/sgml', + '.sgml': 'text/sgml', + '.sh': 'application/x-shellscript', + '.shar': 'application/x-shar', + '.shn': 'application/x-shorten', + '.siag': 'application/x-siag', + '.sid': 'audio/prs.sid', + '.sik': 'application/x-trash', + '.sis': 'application/vnd.symbian.install', + '.sisx': 'x-epoc/x-sisx-app', + '.sit': 'application/x-stuffit', + '.siv': 'application/sieve', + '.sk': 'image/x-skencil', + '.sk1': 'image/x-skencil', + '.skr': 'application/pgp-keys', + '.slk': 'text/spreadsheet', + '.smaf': 'application/x-smaf', + '.smc': 'application/x-snes-rom', + '.smd': 'application/vnd.stardivision.mail', + '.smf': 'application/vnd.stardivision.math', + '.smi': 'application/x-sami', + '.smil': 'application/smil', + '.sml': 'application/smil', + '.sms': 'application/x-sms-rom', + '.snd': 'audio/basic', + '.so': 'application/x-sharedlib', + '.spc': 'application/x-pkcs7-certificates', + '.spd': 'application/x-font-speedo', + '.spec': 'text/x-rpm-spec', + '.spl': 'application/x-shockwave-flash', + '.spx': 'audio/x-speex', + '.sql': 'text/x-sql', + '.sr2': 'image/x-sony-sr2', + '.src': 'application/x-wais-source', + '.srf': 'image/x-sony-srf', + '.srt': 'application/x-subrip', + '.ssa': 'text/x-ssa', + '.stc': 'application/vnd.sun.xml.calc.template', + '.std': 'application/vnd.sun.xml.draw.template', + '.sti': 'application/vnd.sun.xml.impress.template', + '.stm': 'audio/x-stm', + '.stw': 'application/vnd.sun.xml.writer.template', + '.sty': 'text/x-tex', + '.sub': 'text/x-subviewer', + '.sun': 'image/x-sun-raster', + '.sv4cpio': 'application/x-sv4cpio', + '.sv4crc': 'application/x-sv4crc', + '.svg': 'image/svg+xml', + '.svgz': 'image/svg+xml-compressed', + '.swf': 'application/x-shockwave-flash', + '.sxc': 'application/vnd.sun.xml.calc', + '.sxd': 'application/vnd.sun.xml.draw', + '.sxg': 'application/vnd.sun.xml.writer.global', + '.sxi': 'application/vnd.sun.xml.impress', + '.sxm': 'application/vnd.sun.xml.math', + '.sxw': 'application/vnd.sun.xml.writer', + '.sylk': 'text/spreadsheet', + '.t': 'text/troff', + '.t2t': 'text/x-txt2tags', + '.tar': 'application/x-tar', + '.tar.bz': 'application/x-bzip-compressed-tar', + '.tar.bz2': 'application/x-bzip-compressed-tar', + '.tar.gz': 'application/x-compressed-tar', + '.tar.lzma': 'application/x-lzma-compressed-tar', + '.tar.lzo': 'application/x-tzo', + '.tar.xz': 'application/x-xz-compressed-tar', + '.tar.z': 'application/x-tarz', + '.tbz': 'application/x-bzip-compressed-tar', + '.tbz2': 'application/x-bzip-compressed-tar', + '.tcl': 'text/x-tcl', + '.tex': 'text/x-tex', + '.texi': 'text/x-texinfo', + '.texinfo': 'text/x-texinfo', + '.tga': 'image/x-tga', + '.tgz': 'application/x-compressed-tar', + '.theme': 'application/x-theme', + '.themepack': 'application/x-windows-themepack', + '.tif': 'image/tiff', + '.tiff': 'image/tiff', + '.tk': 'text/x-tcl', + '.tlz': 'application/x-lzma-compressed-tar', + '.tnef': 'application/vnd.ms-tnef', + '.tnf': 'application/vnd.ms-tnef', + '.toc': 'application/x-cdrdao-toc', + '.torrent': 'application/x-bittorrent', + '.tpic': 'image/x-tga', + '.tr': 'text/troff', + '.ts': 'application/x-linguist', + '.tsv': 'text/tab-separated-values', + '.tta': 'audio/x-tta', + '.ttc': 'application/x-font-ttf', + '.ttf': 'application/x-font-ttf', + '.ttx': 'application/x-font-ttx', + '.txt': 'text/plain', + '.txz': 'application/x-xz-compressed-tar', + '.tzo': 'application/x-tzo', + '.ufraw': 'application/x-ufraw', + '.ui': 'application/x-designer', + '.uil': 'text/x-uil', + '.ult': 'audio/x-mod', + '.uni': 'audio/x-mod', + '.uri': 'text/x-uri', + '.url': 'text/x-uri', + '.ustar': 'application/x-ustar', + '.vala': 'text/x-vala', + '.vapi': 'text/x-vala', + '.vcf': 'text/directory', + '.vcs': 'text/calendar', + '.vct': 'text/directory', + '.vda': 'image/x-tga', + '.vhd': 'text/x-vhdl', + '.vhdl': 'text/x-vhdl', + '.viv': 'video/vivo', + '.vivo': 'video/vivo', + '.vlc': 'audio/x-mpegurl', + '.vob': 'video/mpeg', + '.voc': 'audio/x-voc', + '.vor': 'application/vnd.stardivision.writer', + '.vst': 'image/x-tga', + '.wav': 'audio/x-wav', + '.wax': 'audio/x-ms-asx', + '.wb1': 'application/x-quattropro', + '.wb2': 'application/x-quattropro', + '.wb3': 'application/x-quattropro', + '.wbmp': 'image/vnd.wap.wbmp', + '.wcm': 'application/vnd.ms-works', + '.wdb': 'application/vnd.ms-works', + '.wk1': 'application/vnd.lotus-1-2-3', + '.wk3': 'application/vnd.lotus-1-2-3', + '.wk4': 'application/vnd.lotus-1-2-3', + '.wks': 'application/vnd.ms-works', + '.wma': 'audio/x-ms-wma', + '.wmf': 'image/x-wmf', + '.wml': 'text/vnd.wap.wml', + '.wmls': 'text/vnd.wap.wmlscript', + '.wmv': 'video/x-ms-wmv', + '.wmx': 'audio/x-ms-asx', + '.wp': 'application/vnd.wordperfect', + '.wp4': 'application/vnd.wordperfect', + '.wp5': 'application/vnd.wordperfect', + '.wp6': 'application/vnd.wordperfect', + '.wpd': 'application/vnd.wordperfect', + '.wpg': 'application/x-wpg', + '.wpl': 'application/vnd.ms-wpl', + '.wpp': 'application/vnd.wordperfect', + '.wps': 'application/vnd.ms-works', + '.wri': 'application/x-mswrite', + '.wrl': 'model/vrml', + '.wv': 'audio/x-wavpack', + '.wvc': 'audio/x-wavpack-correction', + '.wvp': 'audio/x-wavpack', + '.wvx': 'audio/x-ms-asx', + '.x3f': 'image/x-sigma-x3f', + '.xac': 'application/x-gnucash', + '.xbel': 'application/x-xbel', + '.xbl': 'application/xml', + '.xbm': 'image/x-xbitmap', + '.xcf': 'image/x-xcf', + '.xcf.bz2': 'image/x-compressed-xcf', + '.xcf.gz': 'image/x-compressed-xcf', + '.xhtml': 'application/xhtml+xml', + '.xi': 'audio/x-xi', + '.xla': 'application/vnd.ms-excel', + '.xlc': 'application/vnd.ms-excel', + '.xld': 'application/vnd.ms-excel', + '.xlf': 'application/x-xliff', + '.xliff': 'application/x-xliff', + '.xll': 'application/vnd.ms-excel', + '.xlm': 'application/vnd.ms-excel', + '.xls': 'application/vnd.ms-excel', + '.xlsm': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + '.xlt': 'application/vnd.ms-excel', + '.xlw': 'application/vnd.ms-excel', + '.xm': 'audio/x-xm', + '.xmf': 'audio/x-xmf', + '.xmi': 'text/x-xmi', + '.xml': 'application/xml', + '.xpm': 'image/x-xpixmap', + '.xps': 'application/vnd.ms-xpsdocument', + '.xsl': 'application/xml', + '.xslfo': 'text/x-xslfo', + '.xslt': 'application/xml', + '.xspf': 'application/xspf+xml', + '.xul': 'application/vnd.mozilla.xul+xml', + '.xwd': 'image/x-xwindowdump', + '.xyz': 'chemical/x-pdb', + '.xz': 'application/x-xz', + '.w2p': 'application/w2p', + '.z': 'application/x-compress', + '.zabw': 'application/x-abiword', + '.zip': 'application/zip', + '.zoo': 'application/x-zoo', + } + + +def contenttype(filename, default='text/plain'): + """ + Returns the Content-Type string matching extension of the given filename. + """ + + i = filename.rfind('.') + if i>=0: + default = CONTENT_TYPE.get(filename[i:].lower(),default) + j = filename.rfind('.', 0, i) + if j>=0: + default = CONTENT_TYPE.get(filename[j:].lower(),default) + if default.startswith('text/'): + default += '; charset=utf-8' + return default + ADDED gluon/contrib/AuthorizeNet.py Index: gluon/contrib/AuthorizeNet.py ================================================================== --- gluon/contrib/AuthorizeNet.py +++ gluon/contrib/AuthorizeNet.py @@ -0,0 +1,260 @@ +""" +AIM class to credit card payment with authorize.net + +Fork of authnet code written by John Conde +http://www.johnconde.net/blog/integrate-the-authorizenet-aim-api-with-python-3-2/ +Unkown license, assuming public domain + +Modifed by Massimo Di Pierro + +- ported from Python 3.x run on Python 2.4+ +- fixed a couple of bugs +- merged with test so single file +- namedtuple from http://code.activestate.com/recipes/500261/ + +""" + +__all__ = ['AIM'] + +from operator import itemgetter +import urllib + +_known_tuple_types = {} + +class NamedTupleBase(tuple): + """Base class for named tuples with the __new__ operator set, named tuples + yielded by the namedtuple() function will subclass this and add + properties.""" + def __new__(cls, *args, **kws): + """Create a new instance of this fielded tuple""" + # May need to unpack named field values here + if kws: + values = list(args) + [None]*(len(cls._fields) - len(args)) + fields = dict((val, idx) for idx, val in enumerate(cls._fields)) + for kw, val in kws.iteritems(): + assert kw in kws, "%r not in field list" % kw + values[fields[kw]] = val + args = tuple(values) + return tuple.__new__(cls, args) + +def namedtuple(typename, fieldnames): + """ + >>> import namedtuples + >>> tpl = namedtuples.namedtuple(['a', 'b', 'c']) + >>> tpl(1, 2, 3) + (1, 2, 3) + >>> tpl(1, 2, 3).b + 2 + >>> tpl(c=1, a=2, b=3) + (2, 3, 1) + >>> tpl(c=1, a=2, b=3).b + 3 + >>> tpl(c='pads with nones') + (None, None, 'pads with nones') + >>> tpl(b='pads with nones') + (None, 'pads with nones', None) + >>> + """ + # Split up a string, some people do this + if isinstance(fieldnames, basestring): + fieldnames = fieldnames.replace(',', ' ').split() + # Convert anything iterable that enumerates fields to a tuple now + fieldname_tuple = tuple(str(field) for field in fieldnames) + # See if we've cached this + if fieldname_tuple in _known_tuple_types: + return _known_tuple_types[fieldname_tuple] + # Make the type + new_tuple_type = type(typename, (NamedTupleBase,), {}) + # Set the hidden field + new_tuple_type._fields = fieldname_tuple + # Add the getters + for i, field in enumerate(fieldname_tuple): + setattr(new_tuple_type, field, property(itemgetter(i))) + # Cache + _known_tuple_types[fieldname_tuple] = new_tuple_type + # Done + return new_tuple_type + +class AIM: + + class AIMError(Exception): + def __init__(self, value): + self.parameter = value + def __str__(self): + return str(self.parameter) + + def __init__(self, login, transkey, testmode=False): + if str(login).strip() == '' or login == None: + raise AIM.AIMError('No login name provided') + if str(transkey).strip() == '' or transkey == None: + raise AIM.AIMError('No transaction key provided') + if testmode != True and testmode != False: + raise AIM.AIMError('Invalid value for testmode. Must be True or False. "{0}" given.'.format(testmode)) + + self.testmode = testmode + self.proxy = None; + self.delimiter = '|' + self.results = [] + self.error = True + self.success = False + self.declined = False + + self.parameters = {} + self.setParameter('x_delim_data', 'true') + self.setParameter('x_delim_char', self.delimiter) + self.setParameter('x_relay_response', 'FALSE') + self.setParameter('x_url', 'FALSE') + self.setParameter('x_version', '3.1') + self.setParameter('x_method', 'CC') + self.setParameter('x_type', 'AUTH_CAPTURE') + self.setParameter('x_login', login) + self.setParameter('x_tran_key', transkey) + + def process(self): + encoded_args = urllib.urlencode(self.parameters) + if self.testmode == True: + url = 'https://test.authorize.net/gateway/transact.dll' + else: + url = 'https://secure.authorize.net/gateway/transact.dll' + + if self.proxy == None: + self.results += str(urllib.urlopen(url, encoded_args).read()).split(self.delimiter) + else: + opener = urllib.FancyURLopener(self.proxy) + opened = opener.open(url, encoded_args) + try: + self.results += str(opened.read()).split(self.delimiter) + finally: + opened.close() + Results = namedtuple('Results', 'ResultResponse ResponseSubcode ResponseCode ResponseText AuthCode \ + AVSResponse TransactionID InvoiceNumber Description Amount PaymentMethod \ + TransactionType CustomerID CHFirstName CHLastName Company BillingAddress \ + BillingCity BillingState BillingZip BillingCountry Phone Fax Email ShippingFirstName \ + ShippingLastName ShippingCompany ShippingAddress ShippingCity ShippingState \ + ShippingZip ShippingCountry TaxAmount DutyAmount FreightAmount TaxExemptFlag \ + PONumber MD5Hash CVVResponse CAVVResponse') + self.response = Results(*tuple(r for r in self.results)[0:40]) + + if self.getResultResponseFull() == 'Approved': + self.error = False + self.success = True + self.declined = False + elif self.getResultResponseFull() == 'Declined': + self.error = False + self.success = False + self.declined = True + else: + raise AIM.AIMError(self.response.ResponseText) + + def setTransaction(self, creditcard, expiration, total, cvv=None, tax=None, invoice=None): + if str(creditcard).strip() == '' or creditcard == None: + raise AIM.AIMError('No credit card number passed to setTransaction(): {0}'.format(creditcard)) + if str(expiration).strip() == '' or expiration == None: + raise AIM.AIMError('No expiration number to setTransaction(): {0}'.format(expiration)) + if str(total).strip() == '' or total == None: + raise AIM.AIMError('No total amount passed to setTransaction(): {0}'.format(total)) + + self.setParameter('x_card_num', creditcard) + self.setParameter('x_exp_date', expiration) + self.setParameter('x_amount', total) + if cvv != None: + self.setParameter('x_card_code', cvv) + if tax != None: + self.setParameter('x_tax', tax) + if invoice != None: + self.setParameter('x_invoice_num', invoice) + + def setTransactionType(self, transtype=None): + types = ['AUTH_CAPTURE', 'AUTH_ONLY', 'PRIOR_AUTH_CAPTURE', 'CREDIT', 'CAPTURE_ONLY', 'VOID'] + if transtype.upper() not in types: + raise AIM.AIMError('Incorrect Transaction Type passed to setTransactionType(): {0}'.format(transtype)) + self.setParameter('x_type', transtype.upper()) + + def setProxy(self, proxy=None): + if str(proxy).strip() == '' or proxy == None: + raise AIM.AIMError('No proxy passed to setProxy()') + self.proxy = {'http': str(proxy).strip()} + + def setParameter(self, key=None, value=None): + if key != None and value != None and str(key).strip() != '' and str(value).strip() != '': + self.parameters[key] = str(value).strip() + else: + raise AIM.AIMError('Incorrect parameters passed to setParameter(): {0}:{1}'.format(key, value)) + + def isApproved(self): + return self.success + + def isDeclined(self): + return self.declined + + def isError(self): + return self.error + + def getResultResponseFull(self): + responses = ['', 'Approved', 'Declined', 'Error'] + return responses[int(self.results[0])] + +def process(creditcard,expiration,total,cvv=None,tax=None,invoice=None, + login='cnpdev4289', transkey='SR2P8g4jdEn7vFLQ',testmode=True): + payment = AIM(login,transkey,testmode) + expiration = expiration.replace('/','') + payment.setTransaction(creditcard, expiration, total, cvv, tax, invoice) + try: + payment.process() + return payment.isApproved() + except AIM.AIMError: + return False + +def test(): + import socket + import sys + from time import time + + creditcard = '4427802641004797' + expiration = '122012' + total = '1.00' + cvv = '123' + tax = '0.00' + invoice = str(time())[4:10] # get a random invoice number + + try: + payment = AIM('cnpdev4289', 'SR2P8g4jdEn7vFLQ', True) + payment.setTransaction(creditcard, expiration, total, cvv, tax, invoice) + payment.setParameter('x_duplicate_window', 180) # three minutes duplicate windows + payment.setParameter('x_cust_id', '1324') # customer ID + payment.setParameter('x_first_name', 'John') + payment.setParameter('x_last_name', 'Conde') + payment.setParameter('x_company', 'Test Company') + payment.setParameter('x_address', '1234 Main Street') + payment.setParameter('x_city', 'Townsville') + payment.setParameter('x_state', 'NJ') + payment.setParameter('x_zip', '12345') + payment.setParameter('x_country', 'US') + payment.setParameter('x_phone', '800-555-1234') + payment.setParameter('x_description', 'Test Transaction') + payment.setParameter('x_customer_ip', socket.gethostbyname(socket.gethostname())) + payment.setParameter('x_email', 'john@example.com') + payment.setParameter('x_email_customer', False) + payment.process() + if payment.isApproved(): + print 'Response Code: ', payment.response.ResponseCode + print 'Response Text: ', payment.response.ResponseText + print 'Response: ', payment.getResultResponseFull() + print 'Transaction ID: ', payment.response.TransactionID + print 'CVV Result: ', payment.response.CVVResponse + print 'Approval Code: ', payment.response.AuthCode + print 'AVS Result: ', payment.response.AVSResponse + elif payment.isDeclined(): + print 'Your credit card was declined by your bank' + elif payment.isError(): + raise AIM.AIMError('An uncaught error occurred') + except AIM.AIMError, e: + print "Exception thrown:", e + print 'An error occured' + print 'approved',payment.isApproved() + print 'declined',payment.isDeclined() + print 'error',payment.isError() + +if __name__=='__main__': + test() + ADDED gluon/contrib/__init__.py Index: gluon/contrib/__init__.py ================================================================== --- gluon/contrib/__init__.py +++ gluon/contrib/__init__.py @@ -0,0 +1,2 @@ + + ADDED gluon/contrib/comet_messaging.py Index: gluon/contrib/comet_messaging.py ================================================================== --- gluon/contrib/comet_messaging.py +++ gluon/contrib/comet_messaging.py @@ -0,0 +1,191 @@ +#!/usr/bin/python +""" +This file is part of the web2py Web Framework +Copyrighted by Massimo Di Pierro +License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) + +Attention: Requires Chrome or Safari. For IE of Firefox you need https://github.com/gimite/web-socket-js + +1) install tornado + + easy_install tornado + +2) start this app: + + python gluon/contrib/comet_messaging.py -k mykey -p 8888 + +3) from any web2py app you can post messages with + + from gluon.contrib.comet_messaging import comet_send + comet_send('http://127.0.0.1:8888','Hello World','mykey','mygroup') + +4) from any template you can receive them with + + + +When the server posts a message, all clients connected to the page will popup an alert message +Or if you want to send json messages and store evaluated json in a var called data: + + + +- All communications between web2py and comet_messaging will be digitally signed with hmac. +- All validation is handled on the web2py side and there is no need to modify comet_messaging.py +- Multiple web2py instances can talk with one or more comet_messaging servers. +- "ws://127.0.0.1:8888/realtime/" must be contain the IP of the comet_messaging server. +- Via group='mygroup' name you can support multiple groups of clients (think of many chat-rooms) + +Here is a complete sample web2py action: + + def index(): + form=LOAD('default','ajax_form',ajax=True) + script=SCRIPT(''' + jQuery(document).ready(function(){ + var callback=function(e){alert(e.data)}; + if(!web2py_comet('ws://127.0.0.1:8888/realtime/mygroup',callback)) + alert("html5 websocket not supported by your browser, try Google Chrome"); + }); + ''') + return dict(form=form, script=script) + + def ajax_form(): + form=SQLFORM.factory(Field('message')) + if form.accepts(request,session): + from gluon.contrib.comet_messaging import comet_send + comet_send('http://127.0.0.1:8888',form.vars.message,'mykey','mygroup') + return form + +Acknowledgements: +Tornado code inspired by http://thomas.pelletier.im/2010/08/websocket-tornado-redis/ + +""" + +import tornado.httpserver +import tornado.websocket +import tornado.ioloop +import tornado.web +import hmac +import sys +import optparse +import urllib +import time + +listeners = {} +names = {} +tokens = {} + +def comet_send(url,message,hmac_key=None,group='default'): + sig = hmac_key and hmac.new(hmac_key,message).hexdigest() or '' + params = urllib.urlencode({'message': message, 'signature': sig, 'group':group}) + f = urllib.urlopen(url, params) + data= f.read() + f.close() + return data + +class PostHandler(tornado.web.RequestHandler): + """ + only authorized parties can post messages + """ + def post(self): + if hmac_key and not 'signature' in self.request.arguments: return 'false' + if 'message' in self.request.arguments: + message = self.request.arguments['message'][0] + group = self.request.arguments.get('group',['default'])[0] + print '%s:MESSAGE to %s:%s' % (time.time(), group, message) + if hmac_key: + signature = self.request.arguments['signature'][0] + if not hmac.new(hmac_key,message).hexdigest()==signature: return 'false' + for client in listeners.get(group,[]): client.write_message(message) + return 'true' + return 'false' + +class TokenHandler(tornado.web.RequestHandler): + """ + if running with -t post a token to allow a client to join using the token + the message here is the token (any uuid) + allows only authorized parties to joins, for example, a chat + """ + def post(self): + if hmac_key and not 'message' in self.request.arguments: return 'false' + if 'message' in self.request.arguments: + message = self.request.arguments['message'][0] + if hmac_key: + signature = self.request.arguments['signature'][0] + if not hmac.new(hmac_key,message).hexdigest()==signature: return 'false' + tokens[message] = None + return 'true' + return 'false' + +class DistributeHandler(tornado.websocket.WebSocketHandler): + def open(self,params): + group,token,name = params.split('/')+[None,None] + self.group = group or 'default' + self.token = token or 'none' + self.name = name or 'anonymous' + # only authorized parties can join + if DistributeHandler.tokens: + if not self.token in tokens or not token[self.token]==None: + self.close() + else: + tokens[self.token] = self + if not self.group in listeners: listeners[self.group]=[] + # notify clients that a member has joined the groups + for client in listeners.get(self.group,[]): client.write_message('+'+self.name) + listeners[self.group].append(self) + names[self] = self.name + print '%s:CONNECT to %s' % (time.time(), self.group) + def on_message(self, message): + pass + def on_close(self): + if self.group in listeners: listeners[self.group].remove(self) + del names[self] + # notify clients that a member has left the groups + for client in listeners.get(self.group,[]): client.write_message('-'+self.name) + print '%s:DISCONNECT from %s' % (time.time(), self.group) + +if __name__ == "__main__": + usage = __doc__ + version= "" + parser = optparse.OptionParser(usage, None, optparse.Option, version) + parser.add_option('-p', + '--port', + default='8888', + dest='port', + help='socket') + parser.add_option('-l', + '--listen', + default='0.0.0.0', + dest='address', + help='listener address') + parser.add_option('-k', + '--hmac_key', + default='', + dest='hmac_key', + help='hmac_key') + parser.add_option('-t', + '--tokens', + action='store_true', + default=False, + dest='tokens', + help='require tockens to join') + (options, args) = parser.parse_args() + hmac_key = options.hmac_key + DistributeHandler.tokens = options.tokens + urls=[ + (r'/', PostHandler), + (r'/token', TokenHandler), + (r'/realtime/(.*)', DistributeHandler)] + application = tornado.web.Application(urls, auto_reload=True) + http_server = tornado.httpserver.HTTPServer(application) + http_server.listen(int(options.port), address=options.address) + tornado.ioloop.IOLoop.instance().start() + ADDED gluon/contrib/feedparser.py Index: gluon/contrib/feedparser.py ================================================================== --- gluon/contrib/feedparser.py +++ gluon/contrib/feedparser.py @@ -0,0 +1,4010 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +"""Universal feed parser + +Handles RSS 0.9x, RSS 1.0, RSS 2.0, CDF, Atom 0.3, and Atom 1.0 feeds + +Visit http://feedparser.org/ for the latest version +Visit http://feedparser.org/docs/ for the latest documentation + +Required: Python 2.1 or later +Recommended: Python 2.3 or later +Recommended: CJKCodecs and iconv_codec +""" # + "$Revision: 1.92 $"[11:15] + "-cvs" + +__version__ = '4.1' +__license__ = \ + """Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE.""" +__author__ = 'Mark Pilgrim ' +__contributors__ = ['Jason Diamond ', + 'John Beimler ', + 'Fazal Majid ' + , 'Aaron Swartz ', + 'Kevin Marks '] +_debug = 0 + +# HTTP "User-Agent" header to send to servers when downloading feeds. +# If you are embedding feedparser in a larger application, you should +# change this to your application name and URL. + +USER_AGENT = 'UniversalFeedParser/%s +http://feedparser.org/'\ + % __version__ + +# HTTP "Accept" header to send to servers when downloading feeds. If you don't +# want to send an Accept header, set this to None. + +ACCEPT_HEADER = \ + 'application/atom+xml,application/rdf+xml,application/rss+xml,application/x-netcdf,application/xml;q=0.9,text/xml;q=0.2,*/*;q=0.1' + +# List of preferred XML parsers, by SAX driver name. These will be tried first, +# but if they're not installed, Python will keep searching through its own list +# of pre-installed parsers until it finds one that supports everything we need. + +PREFERRED_XML_PARSERS = ['drv_libxml2'] + +# If you want feedparser to automatically run HTML markup through HTML Tidy, set +# this to 1. Requires mxTidy +# or utidylib . + +TIDY_MARKUP = 0 + +# List of Python interfaces for HTML Tidy, in order of preference. Only useful +# if TIDY_MARKUP = 1 + +PREFERRED_TIDY_INTERFACES = ['uTidy', 'mxTidy'] + +# ---------- required modules (should come with any Python distribution) ---------- + +import sgmllib +import re +import sys +import copy +import urlparse +import time +import rfc822 +import types +import cgi +import urllib +import urllib2 +try: + from cStringIO import StringIO as _StringIO +except: + from StringIO import StringIO as _StringIO + +# ---------- optional modules (feedparser will work without these, but with reduced functionality) ---------- + +# gzip is included with most Python distributions, but may not be available if you compiled your own + +try: + import gzip +except: + gzip = None +try: + import zlib +except: + zlib = None + +# If a real XML parser is available, feedparser will attempt to use it. feedparser has +# been tested with the built-in SAX parser, PyXML, and libxml2. On platforms where the +# Python distribution does not come with an XML parser (such as Mac OS X 10.2 and some +# versions of FreeBSD), feedparser will quietly fall back on regex-based parsing. + +try: + import xml.sax + xml.sax.make_parser(PREFERRED_XML_PARSERS) # test for valid parsers + from xml.sax.saxutils import escape as _xmlescape + _XML_AVAILABLE = 1 +except: + _XML_AVAILABLE = 0 + + + def _xmlescape(data): + data = data.replace('&', '&') + data = data.replace('>', '>') + data = data.replace('<', '<') + return data + + +# base64 support for Atom feeds that contain embedded binary data + +try: + import base64 + import binascii +except: + base64 = binascii = None + +# cjkcodecs and iconv_codec provide support for more character encodings. +# Both are available from http://cjkpython.i18n.org/ + +try: + import cjkcodecs.aliases +except: + pass +try: + import iconv_codec +except: + pass + +# chardet library auto-detects character encodings +# Download from http://chardet.feedparser.org/ + +try: + import chardet + if _debug: + import chardet.constants + chardet.constants._debug = 1 +except: + chardet = None + +# ---------- don't touch these ---------- + + +class ThingsNobodyCaresAboutButMe(Exception): + + pass + + +class CharacterEncodingOverride(ThingsNobodyCaresAboutButMe): + + pass + + +class CharacterEncodingUnknown(ThingsNobodyCaresAboutButMe): + + pass + + +class NonXMLContentType(ThingsNobodyCaresAboutButMe): + + pass + + +class UndeclaredNamespace(Exception): + + pass + + +sgmllib.tagfind = re.compile('[a-zA-Z][-_.:a-zA-Z0-9]*') +sgmllib.special = re.compile('' % (tag, ''.join([' %s="%s"' + % t for t in attrs])), escape=0) + + # match namespaces + + if tag.find(':') != -1: + (prefix, suffix) = tag.split(':', 1) + else: + (prefix, suffix) = ('', tag) + prefix = self.namespacemap.get(prefix, prefix) + if prefix: + prefix = prefix + '_' + + # special hack for better tracking of empty textinput/image elements in illformed feeds + + if not prefix and tag not in ('title', 'link', 'description', + 'name'): + self.intextinput = 0 + if not prefix and tag not in ( + 'title', + 'link', + 'description', + 'url', + 'href', + 'width', + 'height', + ): + self.inimage = 0 + + # call special handler (if defined) or default handler + + methodname = '_start_' + prefix + suffix + try: + method = getattr(self, methodname) + return method(attrsD) + except AttributeError: + return self.push(prefix + suffix, 1) + + def unknown_endtag(self, tag): + if _debug: + sys.stderr.write('end %s\n' % tag) + + # match namespaces + + if tag.find(':') != -1: + (prefix, suffix) = tag.split(':', 1) + else: + (prefix, suffix) = ('', tag) + prefix = self.namespacemap.get(prefix, prefix) + if prefix: + prefix = prefix + '_' + + # call special handler (if defined) or default handler + + methodname = '_end_' + prefix + suffix + try: + method = getattr(self, methodname) + method() + except AttributeError: + self.pop(prefix + suffix) + + # track inline content + + if self.incontent and self.contentparams.has_key('type')\ + and not self.contentparams.get('type', 'xml' + ).endswith('xml'): + + # element declared itself as escaped markup, but it isn't really + + self.contentparams['type'] = 'application/xhtml+xml' + if self.incontent and self.contentparams.get('type')\ + == 'application/xhtml+xml': + tag = tag.split(':')[-1] + self.handle_data('' % tag, escape=0) + + # track xml:base and xml:lang going out of scope + + if self.basestack: + self.basestack.pop() + if self.basestack and self.basestack[-1]: + self.baseuri = self.basestack[-1] + if self.langstack: + self.langstack.pop() + if self.langstack: # and (self.langstack[-1] is not None): + self.lang = self.langstack[-1] + + def handle_charref(self, ref): + + # called for each character reference, e.g. for ' ', ref will be '160' + + if not self.elementstack: + return + ref = ref.lower() + if ref in ( + '34', + '38', + '39', + '60', + '62', + 'x22', + 'x26', + 'x27', + 'x3c', + 'x3e', + ): + text = '&#%s;' % ref + else: + if ref[0] == 'x': + c = int(ref[1:], 16) + else: + c = int(ref) + text = unichr(c).encode('utf-8') + self.elementstack[-1][2].append(text) + + def handle_entityref(self, ref): + + # called for each entity reference, e.g. for '©', ref will be 'copy' + + if not self.elementstack: + return + if _debug: + sys.stderr.write('entering handle_entityref with %s\n' + % ref) + if ref in ('lt', 'gt', 'quot', 'amp', 'apos'): + text = '&%s;' % ref + else: + + # entity resolution graciously donated by Aaron Swartz + + def name2cp(k): + import htmlentitydefs + if hasattr(htmlentitydefs, 'name2codepoint'): # requires Python 2.3 + return htmlentitydefs.name2codepoint[k] + k = htmlentitydefs.entitydefs[k] + if k.startswith('&#') and k.endswith(';'): + return int(k[2:-1]) # not in latin-1 + return ord(k) + + try: + name2cp(ref) + except KeyError: + text = '&%s;' % ref + else: + text = unichr(name2cp(ref)).encode('utf-8') + self.elementstack[-1][2].append(text) + + def handle_data(self, text, escape=1): + + # called for each block of plain text, i.e. outside of any tag and + # not containing any character or entity references + + if not self.elementstack: + return + if escape and self.contentparams.get('type')\ + == 'application/xhtml+xml': + text = _xmlescape(text) + self.elementstack[-1][2].append(text) + + def handle_comment(self, text): + + # called for each comment, e.g. + + pass + + def handle_pi(self, text): + + # called for each processing instruction, e.g. + + pass + + def handle_decl(self, text): + pass + + def parse_declaration(self, i): + + # override internal declaration handler to handle CDATA blocks + + if _debug: + sys.stderr.write('entering parse_declaration\n') + if self.rawdata[i:i + 9] == '', i) + if k == -1: + k = len(self.rawdata) + self.handle_data(_xmlescape(self.rawdata[i + 9:k]), 0) + return k + 3 + else: + k = self.rawdata.find('>', i) + return k + 1 + + def mapContentType(self, contentType): + contentType = contentType.lower() + if contentType == 'text': + contentType = 'text/plain' + elif contentType == 'html': + contentType = 'text/html' + elif contentType == 'xhtml': + contentType = 'application/xhtml+xml' + return contentType + + def trackNamespace(self, prefix, uri): + loweruri = uri.lower() + if (prefix, loweruri) == (None, + 'http://my.netscape.com/rdf/simple/0.9/' + ) and not self.version: + self.version = 'rss090' + if loweruri == 'http://purl.org/rss/1.0/' and not self.version: + self.version = 'rss10' + if loweruri == 'http://www.w3.org/2005/atom'\ + and not self.version: + self.version = 'atom10' + if loweruri.find('backend.userland.com/rss') != -1: + + # match any backend.userland.com namespace + + uri = 'http://backend.userland.com/rss' + loweruri = uri + if self._matchnamespaces.has_key(loweruri): + self.namespacemap[prefix] = self._matchnamespaces[loweruri] + self.namespacesInUse[self._matchnamespaces[loweruri]] = uri + else: + self.namespacesInUse[prefix or ''] = uri + + def resolveURI(self, uri): + return _urljoin(self.baseuri or '', uri) + + def decodeEntities(self, element, data): + return data + + def push(self, element, expectingText): + self.elementstack.append([element, expectingText, []]) + + def pop(self, element, stripWhitespace=1): + if not self.elementstack: + return + if self.elementstack[-1][0] != element: + return + + (element, expectingText, pieces) = self.elementstack.pop() + output = ''.join(pieces) + if stripWhitespace: + output = output.strip() + if not expectingText: + return output + + # decode base64 content + + if base64 and self.contentparams.get('base64', 0): + try: + output = base64.decodestring(output) + except binascii.Error: + pass + except binascii.Incomplete: + pass + + # resolve relative URIs + + if element in self.can_be_relative_uri and output: + output = self.resolveURI(output) + + # decode entities within embedded markup + + if not self.contentparams.get('base64', 0): + output = self.decodeEntities(element, output) + + # remove temporary cruft from contentparams + + try: + del self.contentparams['mode'] + except KeyError: + pass + try: + del self.contentparams['base64'] + except KeyError: + pass + + # resolve relative URIs within embedded markup + + if self.mapContentType(self.contentparams.get('type', + 'text/html')) in self.html_types: + if element in self.can_contain_relative_uris: + output = _resolveRelativeURIs(output, self.baseuri, + self.encoding) + + # sanitize embedded markup + + if self.mapContentType(self.contentparams.get('type', + 'text/html')) in self.html_types: + if element in self.can_contain_dangerous_markup: + output = _sanitizeHTML(output, self.encoding) + + if self.encoding and type(output) != type(u''): + try: + output = unicode(output, self.encoding) + except: + pass + + # categories/tags/keywords/whatever are handled in _end_category + + if element == 'category': + return output + + # store output in appropriate place(s) + + if self.inentry and not self.insource: + if element == 'content': + self.entries[-1].setdefault(element, []) + contentparams = copy.deepcopy(self.contentparams) + contentparams['value'] = output + self.entries[-1][element].append(contentparams) + elif element == 'link': + self.entries[-1][element] = output + if output: + self.entries[-1]['links'][-1]['href'] = output + else: + if element == 'description': + element = 'summary' + self.entries[-1][element] = output + if self.incontent: + contentparams = copy.deepcopy(self.contentparams) + contentparams['value'] = output + self.entries[-1][element + '_detail'] = \ + contentparams + elif (self.infeed or self.insource) and not self.intextinput\ + and not self.inimage: + context = self._getContext() + if element == 'description': + element = 'subtitle' + context[element] = output + if element == 'link': + context['links'][-1]['href'] = output + elif self.incontent: + contentparams = copy.deepcopy(self.contentparams) + contentparams['value'] = output + context[element + '_detail'] = contentparams + return output + + def pushContent( + self, + tag, + attrsD, + defaultContentType, + expectingText, + ): + self.incontent += 1 + self.contentparams = FeedParserDict({'type' + : self.mapContentType(attrsD.get('type', + defaultContentType)), 'language': self.lang, 'base' + : self.baseuri}) + self.contentparams['base64'] = self._isBase64(attrsD, + self.contentparams) + self.push(tag, expectingText) + + def popContent(self, tag): + value = self.pop(tag) + self.incontent -= 1 + self.contentparams.clear() + return value + + def _mapToStandardPrefix(self, name): + colonpos = name.find(':') + if colonpos != -1: + prefix = name[:colonpos] + suffix = name[colonpos + 1:] + prefix = self.namespacemap.get(prefix, prefix) + name = prefix + ':' + suffix + return name + + def _getAttribute(self, attrsD, name): + return attrsD.get(self._mapToStandardPrefix(name)) + + def _isBase64(self, attrsD, contentparams): + if attrsD.get('mode', '') == 'base64': + return 1 + if self.contentparams['type'].startswith('text/'): + return 0 + if self.contentparams['type'].endswith('+xml'): + return 0 + if self.contentparams['type'].endswith('/xml'): + return 0 + return 1 + + def _itsAnHrefDamnIt(self, attrsD): + href = attrsD.get('url', attrsD.get('uri', attrsD.get('href', + None))) + if href: + try: + del attrsD['url'] + except KeyError: + pass + try: + del attrsD['uri'] + except KeyError: + pass + attrsD['href'] = href + return attrsD + + def _save(self, key, value): + context = self._getContext() + context.setdefault(key, value) + + def _start_rss(self, attrsD): + versionmap = { + '0.91': 'rss091u', + '0.92': 'rss092', + '0.93': 'rss093', + '0.94': 'rss094', + } + if not self.version: + attr_version = attrsD.get('version', '') + version = versionmap.get(attr_version) + if version: + self.version = version + elif attr_version.startswith('2.'): + self.version = 'rss20' + else: + self.version = 'rss' + + def _start_dlhottitles(self, attrsD): + self.version = 'hotrss' + + def _start_channel(self, attrsD): + self.infeed = 1 + self._cdf_common(attrsD) + + _start_feedinfo = _start_channel + + def _cdf_common(self, attrsD): + if attrsD.has_key('lastmod'): + self._start_modified({}) + self.elementstack[-1][-1] = attrsD['lastmod'] + self._end_modified() + if attrsD.has_key('href'): + self._start_link({}) + self.elementstack[-1][-1] = attrsD['href'] + self._end_link() + + def _start_feed(self, attrsD): + self.infeed = 1 + versionmap = {'0.1': 'atom01', '0.2': 'atom02', '0.3': 'atom03'} + if not self.version: + attr_version = attrsD.get('version') + version = versionmap.get(attr_version) + if version: + self.version = version + else: + self.version = 'atom' + + def _end_channel(self): + self.infeed = 0 + + _end_feed = _end_channel + + def _start_image(self, attrsD): + self.inimage = 1 + self.push('image', 0) + context = self._getContext() + context.setdefault('image', FeedParserDict()) + + def _end_image(self): + self.pop('image') + self.inimage = 0 + + def _start_textinput(self, attrsD): + self.intextinput = 1 + self.push('textinput', 0) + context = self._getContext() + context.setdefault('textinput', FeedParserDict()) + + _start_textInput = _start_textinput + + def _end_textinput(self): + self.pop('textinput') + self.intextinput = 0 + + _end_textInput = _end_textinput + + def _start_author(self, attrsD): + self.inauthor = 1 + self.push('author', 1) + + _start_managingeditor = _start_author + _start_dc_author = _start_author + _start_dc_creator = _start_author + _start_itunes_author = _start_author + + def _end_author(self): + self.pop('author') + self.inauthor = 0 + self._sync_author_detail() + + _end_managingeditor = _end_author + _end_dc_author = _end_author + _end_dc_creator = _end_author + _end_itunes_author = _end_author + + def _start_itunes_owner(self, attrsD): + self.inpublisher = 1 + self.push('publisher', 0) + + def _end_itunes_owner(self): + self.pop('publisher') + self.inpublisher = 0 + self._sync_author_detail('publisher') + + def _start_contributor(self, attrsD): + self.incontributor = 1 + context = self._getContext() + context.setdefault('contributors', []) + context['contributors'].append(FeedParserDict()) + self.push('contributor', 0) + + def _end_contributor(self): + self.pop('contributor') + self.incontributor = 0 + + def _start_dc_contributor(self, attrsD): + self.incontributor = 1 + context = self._getContext() + context.setdefault('contributors', []) + context['contributors'].append(FeedParserDict()) + self.push('name', 0) + + def _end_dc_contributor(self): + self._end_name() + self.incontributor = 0 + + def _start_name(self, attrsD): + self.push('name', 0) + + _start_itunes_name = _start_name + + def _end_name(self): + value = self.pop('name') + if self.inpublisher: + self._save_author('name', value, 'publisher') + elif self.inauthor: + self._save_author('name', value) + elif self.incontributor: + self._save_contributor('name', value) + elif self.intextinput: + context = self._getContext() + context['textinput']['name'] = value + + _end_itunes_name = _end_name + + def _start_width(self, attrsD): + self.push('width', 0) + + def _end_width(self): + value = self.pop('width') + try: + value = int(value) + except: + value = 0 + if self.inimage: + context = self._getContext() + context['image']['width'] = value + + def _start_height(self, attrsD): + self.push('height', 0) + + def _end_height(self): + value = self.pop('height') + try: + value = int(value) + except: + value = 0 + if self.inimage: + context = self._getContext() + context['image']['height'] = value + + def _start_url(self, attrsD): + self.push('href', 1) + + _start_homepage = _start_url + _start_uri = _start_url + + def _end_url(self): + value = self.pop('href') + if self.inauthor: + self._save_author('href', value) + elif self.incontributor: + self._save_contributor('href', value) + elif self.inimage: + context = self._getContext() + context['image']['href'] = value + elif self.intextinput: + context = self._getContext() + context['textinput']['link'] = value + + _end_homepage = _end_url + _end_uri = _end_url + + def _start_email(self, attrsD): + self.push('email', 0) + + _start_itunes_email = _start_email + + def _end_email(self): + value = self.pop('email') + if self.inpublisher: + self._save_author('email', value, 'publisher') + elif self.inauthor: + self._save_author('email', value) + elif self.incontributor: + self._save_contributor('email', value) + + _end_itunes_email = _end_email + + def _getContext(self): + if self.insource: + context = self.sourcedata + elif self.inentry: + context = self.entries[-1] + else: + context = self.feeddata + return context + + def _save_author( + self, + key, + value, + prefix='author', + ): + context = self._getContext() + context.setdefault(prefix + '_detail', FeedParserDict()) + context[prefix + '_detail'][key] = value + self._sync_author_detail() + + def _save_contributor(self, key, value): + context = self._getContext() + context.setdefault('contributors', [FeedParserDict()]) + context['contributors'][-1][key] = value + + def _sync_author_detail(self, key='author'): + context = self._getContext() + detail = context.get('%s_detail' % key) + if detail: + name = detail.get('name') + email = detail.get('email') + if name and email: + context[key] = '%s (%s)' % (name, email) + elif name: + context[key] = name + elif email: + context[key] = email + else: + author = context.get(key) + if not author: + return + emailmatch = \ + re.search(r'''(([a-zA-Z0-9\_\-\.\+]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?))''' + , author) + if not emailmatch: + return + email = emailmatch.group(0) + + # probably a better way to do the following, but it passes all the tests + + author = author.replace(email, '') + author = author.replace('()', '') + author = author.strip() + if author and author[0] == '(': + author = author[1:] + if author and author[-1] == ')': + author = author[:-1] + author = author.strip() + context.setdefault('%s_detail' % key, FeedParserDict()) + context['%s_detail' % key]['name'] = author + context['%s_detail' % key]['email'] = email + + def _start_subtitle(self, attrsD): + self.pushContent('subtitle', attrsD, 'text/plain', 1) + + _start_tagline = _start_subtitle + _start_itunes_subtitle = _start_subtitle + + def _end_subtitle(self): + self.popContent('subtitle') + + _end_tagline = _end_subtitle + _end_itunes_subtitle = _end_subtitle + + def _start_rights(self, attrsD): + self.pushContent('rights', attrsD, 'text/plain', 1) + + _start_dc_rights = _start_rights + _start_copyright = _start_rights + + def _end_rights(self): + self.popContent('rights') + + _end_dc_rights = _end_rights + _end_copyright = _end_rights + + def _start_item(self, attrsD): + self.entries.append(FeedParserDict()) + self.push('item', 0) + self.inentry = 1 + self.guidislink = 0 + id = self._getAttribute(attrsD, 'rdf:about') + if id: + context = self._getContext() + context['id'] = id + self._cdf_common(attrsD) + + _start_entry = _start_item + _start_product = _start_item + + def _end_item(self): + self.pop('item') + self.inentry = 0 + + _end_entry = _end_item + + def _start_dc_language(self, attrsD): + self.push('language', 1) + + _start_language = _start_dc_language + + def _end_dc_language(self): + self.lang = self.pop('language') + + _end_language = _end_dc_language + + def _start_dc_publisher(self, attrsD): + self.push('publisher', 1) + + _start_webmaster = _start_dc_publisher + + def _end_dc_publisher(self): + self.pop('publisher') + self._sync_author_detail('publisher') + + _end_webmaster = _end_dc_publisher + + def _start_published(self, attrsD): + self.push('published', 1) + + _start_dcterms_issued = _start_published + _start_issued = _start_published + + def _end_published(self): + value = self.pop('published') + self._save('published_parsed', _parse_date(value)) + + _end_dcterms_issued = _end_published + _end_issued = _end_published + + def _start_updated(self, attrsD): + self.push('updated', 1) + + _start_modified = _start_updated + _start_dcterms_modified = _start_updated + _start_pubdate = _start_updated + _start_dc_date = _start_updated + + def _end_updated(self): + value = self.pop('updated') + parsed_value = _parse_date(value) + self._save('updated_parsed', parsed_value) + + _end_modified = _end_updated + _end_dcterms_modified = _end_updated + _end_pubdate = _end_updated + _end_dc_date = _end_updated + + def _start_created(self, attrsD): + self.push('created', 1) + + _start_dcterms_created = _start_created + + def _end_created(self): + value = self.pop('created') + self._save('created_parsed', _parse_date(value)) + + _end_dcterms_created = _end_created + + def _start_expirationdate(self, attrsD): + self.push('expired', 1) + + def _end_expirationdate(self): + self._save('expired_parsed', _parse_date(self.pop('expired'))) + + def _start_cc_license(self, attrsD): + self.push('license', 1) + value = self._getAttribute(attrsD, 'rdf:resource') + if value: + self.elementstack[-1][2].append(value) + self.pop('license') + + def _start_creativecommons_license(self, attrsD): + self.push('license', 1) + + def _end_creativecommons_license(self): + self.pop('license') + + def _addTag( + self, + term, + scheme, + label, + ): + context = self._getContext() + tags = context.setdefault('tags', []) + if not term and not scheme and not label: + return + value = FeedParserDict({'term': term, 'scheme': scheme, 'label' + : label}) + if value not in tags: + tags.append(FeedParserDict({'term': term, 'scheme': scheme, + 'label': label})) + + def _start_category(self, attrsD): + if _debug: + sys.stderr.write('entering _start_category with %s\n' + % repr(attrsD)) + term = attrsD.get('term') + scheme = attrsD.get('scheme', attrsD.get('domain')) + label = attrsD.get('label') + self._addTag(term, scheme, label) + self.push('category', 1) + + _start_dc_subject = _start_category + _start_keywords = _start_category + + def _end_itunes_keywords(self): + for term in self.pop('itunes_keywords').split(): + self._addTag(term, 'http://www.itunes.com/', None) + + def _start_itunes_category(self, attrsD): + self._addTag(attrsD.get('text'), 'http://www.itunes.com/', None) + self.push('category', 1) + + def _end_category(self): + value = self.pop('category') + if not value: + return + context = self._getContext() + tags = context['tags'] + if value and len(tags) and not tags[-1]['term']: + tags[-1]['term'] = value + else: + self._addTag(value, None, None) + + _end_dc_subject = _end_category + _end_keywords = _end_category + _end_itunes_category = _end_category + + def _start_cloud(self, attrsD): + self._getContext()['cloud'] = FeedParserDict(attrsD) + + def _start_link(self, attrsD): + attrsD.setdefault('rel', 'alternate') + attrsD.setdefault('type', 'text/html') + attrsD = self._itsAnHrefDamnIt(attrsD) + if attrsD.has_key('href'): + attrsD['href'] = self.resolveURI(attrsD['href']) + expectingText = self.infeed or self.inentry or self.insource + context = self._getContext() + context.setdefault('links', []) + context['links'].append(FeedParserDict(attrsD)) + if attrsD['rel'] == 'enclosure': + self._start_enclosure(attrsD) + if attrsD.has_key('href'): + expectingText = 0 + if attrsD.get('rel') == 'alternate'\ + and self.mapContentType(attrsD.get('type'))\ + in self.html_types: + context['link'] = attrsD['href'] + else: + self.push('link', expectingText) + + _start_producturl = _start_link + + def _end_link(self): + value = self.pop('link') + context = self._getContext() + if self.intextinput: + context['textinput']['link'] = value + if self.inimage: + context['image']['link'] = value + + _end_producturl = _end_link + + def _start_guid(self, attrsD): + self.guidislink = attrsD.get('ispermalink', 'true') == 'true' + self.push('id', 1) + + def _end_guid(self): + value = self.pop('id') + self._save('guidislink', self.guidislink + and not self._getContext().has_key('link')) + if self.guidislink: + + # guid acts as link, but only if 'ispermalink' is not present or is 'true', + # and only if the item doesn't already have a link element + + self._save('link', value) + + def _start_title(self, attrsD): + self.pushContent('title', attrsD, 'text/plain', self.infeed + or self.inentry or self.insource) + + _start_dc_title = _start_title + _start_media_title = _start_title + + def _end_title(self): + value = self.popContent('title') + context = self._getContext() + if self.intextinput: + context['textinput']['title'] = value + elif self.inimage: + context['image']['title'] = value + + _end_dc_title = _end_title + _end_media_title = _end_title + + def _start_description(self, attrsD): + context = self._getContext() + if context.has_key('summary'): + self._summaryKey = 'content' + self._start_content(attrsD) + else: + self.pushContent('description', attrsD, 'text/html', + self.infeed or self.inentry + or self.insource) + + def _start_abstract(self, attrsD): + self.pushContent('description', attrsD, 'text/plain', + self.infeed or self.inentry or self.insource) + + def _end_description(self): + if self._summaryKey == 'content': + self._end_content() + else: + value = self.popContent('description') + context = self._getContext() + if self.intextinput: + context['textinput']['description'] = value + elif self.inimage: + context['image']['description'] = value + self._summaryKey = None + + _end_abstract = _end_description + + def _start_info(self, attrsD): + self.pushContent('info', attrsD, 'text/plain', 1) + + _start_feedburner_browserfriendly = _start_info + + def _end_info(self): + self.popContent('info') + + _end_feedburner_browserfriendly = _end_info + + def _start_generator(self, attrsD): + if attrsD: + attrsD = self._itsAnHrefDamnIt(attrsD) + if attrsD.has_key('href'): + attrsD['href'] = self.resolveURI(attrsD['href']) + self._getContext()['generator_detail'] = FeedParserDict(attrsD) + self.push('generator', 1) + + def _end_generator(self): + value = self.pop('generator') + context = self._getContext() + if context.has_key('generator_detail'): + context['generator_detail']['name'] = value + + def _start_admin_generatoragent(self, attrsD): + self.push('generator', 1) + value = self._getAttribute(attrsD, 'rdf:resource') + if value: + self.elementstack[-1][2].append(value) + self.pop('generator') + self._getContext()['generator_detail'] = FeedParserDict({'href' + : value}) + + def _start_admin_errorreportsto(self, attrsD): + self.push('errorreportsto', 1) + value = self._getAttribute(attrsD, 'rdf:resource') + if value: + self.elementstack[-1][2].append(value) + self.pop('errorreportsto') + + def _start_summary(self, attrsD): + context = self._getContext() + if context.has_key('summary'): + self._summaryKey = 'content' + self._start_content(attrsD) + else: + self._summaryKey = 'summary' + self.pushContent(self._summaryKey, attrsD, 'text/plain', 1) + + _start_itunes_summary = _start_summary + + def _end_summary(self): + if self._summaryKey == 'content': + self._end_content() + else: + self.popContent(self._summaryKey or 'summary') + self._summaryKey = None + + _end_itunes_summary = _end_summary + + def _start_enclosure(self, attrsD): + attrsD = self._itsAnHrefDamnIt(attrsD) + self._getContext().setdefault('enclosures', + []).append(FeedParserDict(attrsD)) + href = attrsD.get('href') + if href: + context = self._getContext() + if not context.get('id'): + context['id'] = href + + def _start_source(self, attrsD): + self.insource = 1 + + def _end_source(self): + self.insource = 0 + self._getContext()['source'] = copy.deepcopy(self.sourcedata) + self.sourcedata.clear() + + def _start_content(self, attrsD): + self.pushContent('content', attrsD, 'text/plain', 1) + src = attrsD.get('src') + if src: + self.contentparams['src'] = src + self.push('content', 1) + + def _start_prodlink(self, attrsD): + self.pushContent('content', attrsD, 'text/html', 1) + + def _start_body(self, attrsD): + self.pushContent('content', attrsD, 'application/xhtml+xml', 1) + + _start_xhtml_body = _start_body + + def _start_content_encoded(self, attrsD): + self.pushContent('content', attrsD, 'text/html', 1) + + _start_fullitem = _start_content_encoded + + def _end_content(self): + copyToDescription = \ + self.mapContentType(self.contentparams.get('type'))\ + in ['text/plain'] + self.html_types + value = self.popContent('content') + if copyToDescription: + self._save('description', value) + + _end_body = _end_content + _end_xhtml_body = _end_content + _end_content_encoded = _end_content + _end_fullitem = _end_content + _end_prodlink = _end_content + + def _start_itunes_image(self, attrsD): + self.push('itunes_image', 0) + self._getContext()['image'] = FeedParserDict({'href' + : attrsD.get('href')}) + + _start_itunes_link = _start_itunes_image + + def _end_itunes_block(self): + value = self.pop('itunes_block', 0) + self._getContext()['itunes_block'] = value == 'yes' and 1 or 0 + + def _end_itunes_explicit(self): + value = self.pop('itunes_explicit', 0) + self._getContext()['itunes_explicit'] = value == 'yes' and 1\ + or 0 + + +if _XML_AVAILABLE: + + + class _StrictFeedParser(_FeedParserMixin, + xml.sax.handler.ContentHandler): + + def __init__( + self, + baseuri, + baselang, + encoding, + ): + if _debug: + sys.stderr.write('trying StrictFeedParser\n') + xml.sax.handler.ContentHandler.__init__(self) + _FeedParserMixin.__init__(self, baseuri, baselang, encoding) + self.bozo = 0 + self.exc = None + + def startPrefixMapping(self, prefix, uri): + self.trackNamespace(prefix, uri) + + def startElementNS( + self, + name, + qname, + attrs, + ): + (namespace, localname) = name + lowernamespace = str(namespace or '').lower() + if lowernamespace.find('backend.userland.com/rss') != -1: + + # match any backend.userland.com namespace + + namespace = 'http://backend.userland.com/rss' + lowernamespace = namespace + if qname and qname.find(':') > 0: + givenprefix = qname.split(':')[0] + else: + givenprefix = None + prefix = self._matchnamespaces.get(lowernamespace, + givenprefix) + if givenprefix and (prefix == None or prefix == '' + and lowernamespace == '')\ + and not self.namespacesInUse.has_key(givenprefix): + raise UndeclaredNamespace, \ + "'%s' is not associated with a namespace"\ + % givenprefix + if prefix: + localname = prefix + ':' + localname + localname = str(localname).lower() + if _debug: + sys.stderr.write('startElementNS: qname = %s, namespace = %s, givenprefix = %s, prefix = %s, attrs = %s, localname = %s\n' + % ( + qname, + namespace, + givenprefix, + prefix, + attrs.items(), + localname, + )) + + # qname implementation is horribly broken in Python 2.1 (it + # doesn't report any), and slightly broken in Python 2.2 (it + # doesn't report the xml: namespace). So we match up namespaces + # with a known list first, and then possibly override them with + # the qnames the SAX parser gives us (if indeed it gives us any + # at all). Thanks to MatejC for helping me test this and + # tirelessly telling me that it didn't work yet. + + attrsD = {} + for ((namespace, attrlocalname), attrvalue) in \ + attrs._attrs.items(): + lowernamespace = (namespace or '').lower() + prefix = self._matchnamespaces.get(lowernamespace, '') + if prefix: + attrlocalname = prefix + ':' + attrlocalname + attrsD[str(attrlocalname).lower()] = attrvalue + for qname in attrs.getQNames(): + attrsD[str(qname).lower()] = \ + attrs.getValueByQName(qname) + self.unknown_starttag(localname, attrsD.items()) + + def characters(self, text): + self.handle_data(text) + + def endElementNS(self, name, qname): + (namespace, localname) = name + lowernamespace = str(namespace or '').lower() + if qname and qname.find(':') > 0: + givenprefix = qname.split(':')[0] + else: + givenprefix = '' + prefix = self._matchnamespaces.get(lowernamespace, + givenprefix) + if prefix: + localname = prefix + ':' + localname + localname = str(localname).lower() + self.unknown_endtag(localname) + + def error(self, exc): + self.bozo = 1 + self.exc = exc + + def fatalError(self, exc): + self.error(exc) + raise exc + + +class _BaseHTMLProcessor(sgmllib.SGMLParser): + + elements_no_end_tag = [ + 'area', + 'base', + 'basefont', + 'br', + 'col', + 'frame', + 'hr', + 'img', + 'input', + 'isindex', + 'link', + 'meta', + 'param', + ] + + def __init__(self, encoding): + self.encoding = encoding + if _debug: + sys.stderr.write('entering BaseHTMLProcessor, encoding=%s\n' + % self.encoding) + sgmllib.SGMLParser.__init__(self) + + def reset(self): + self.pieces = [] + sgmllib.SGMLParser.reset(self) + + def _shorttag_replace(self, match): + tag = match.group(1) + if tag in self.elements_no_end_tag: + return '<' + tag + ' />' + else: + return '<' + tag + '>' + + def feed(self, data): + data = re.compile(r'', self._shorttag_replace, data) # bug [ 1399464 ] Bad regexp for _shorttag_replace + + data = re.sub(r'<([^<\s]+?)\s*/>', self._shorttag_replace, data) + data = data.replace(''', "'") + data = data.replace('"', '"') + if self.encoding and type(data) == type(u''): + data = data.encode(self.encoding) + sgmllib.SGMLParser.feed(self, data) + + def normalize_attrs(self, attrs): + + # utility method to be called by descendants + + attrs = [(k.lower(), v) for (k, v) in attrs] + attrs = [(k, k in ('rel', 'type') and v.lower() or v) for (k, + v) in attrs] + return attrs + + def unknown_starttag(self, tag, attrs): + + # called for each start tag + # attrs is a list of (attr, value) tuples + # e.g. for
    , tag='pre', attrs=[('class', 'screen')]
    +
    +        if _debug:
    +            sys.stderr.write('_BaseHTMLProcessor, unknown_starttag, tag=%s\n'
    +                              % tag)
    +        uattrs = []
    +
    +        # thanks to Kevin Marks for this breathtaking hack to deal with (valid) high-bit attribute values in UTF-8 feeds
    +
    +        for (key, value) in attrs:
    +            if type(value) != type(u''):
    +                value = unicode(value, self.encoding)
    +            uattrs.append((unicode(key, self.encoding), value))
    +        strattrs = u''.join([u' %s="%s"' % (key, value) for (key,
    +                            value) in uattrs]).encode(self.encoding)
    +        if tag in self.elements_no_end_tag:
    +            self.pieces.append('<%(tag)s%(strattrs)s />' % locals())
    +        else:
    +            self.pieces.append('<%(tag)s%(strattrs)s>' % locals())
    +
    +    def unknown_endtag(self, tag):
    +
    +        # called for each end tag, e.g. for 
    , tag will be 'pre' + # Reconstruct the original end tag. + + if tag not in self.elements_no_end_tag: + self.pieces.append('' % locals()) + + def handle_charref(self, ref): + + # called for each character reference, e.g. for ' ', ref will be '160' + # Reconstruct the original character reference. + + self.pieces.append('&#%(ref)s;' % locals()) + + def handle_entityref(self, ref): + + # called for each entity reference, e.g. for '©', ref will be 'copy' + # Reconstruct the original entity reference. + + self.pieces.append('&%(ref)s;' % locals()) + + def handle_data(self, text): + + # called for each block of plain text, i.e. outside of any tag and + # not containing any character or entity references + # Store the original text verbatim. + + if _debug: + sys.stderr.write('_BaseHTMLProcessor, handle_text, text=%s\n' + % text) + self.pieces.append(text) + + def handle_comment(self, text): + + # called for each HTML comment, e.g. + # Reconstruct the original comment. + + self.pieces.append('' % locals()) + + def handle_pi(self, text): + + # called for each processing instruction, e.g. + # Reconstruct original processing instruction. + + self.pieces.append('' % locals()) + + def handle_decl(self, text): + + # called for the DOCTYPE, if present, e.g. + # + # Reconstruct original DOCTYPE + + self.pieces.append('' % locals()) + + _new_declname_match = re.compile(r'[a-zA-Z][-_.a-zA-Z0-9:]*\s*' + ).match + + def _scan_name(self, i, declstartpos): + rawdata = self.rawdata + n = len(rawdata) + if i == n: + return (None, -1) + m = self._new_declname_match(rawdata, i) + if m: + s = m.group() + name = s.strip() + if i + len(s) == n: + return (None, -1) # end of buffer + return (name.lower(), m.end()) + else: + self.handle_data(rawdata) + +# self.updatepos(declstartpos, i) + + return (None, -1) + + def output(self): + """Return processed HTML as a single string""" + + return ''.join([str(p) for p in self.pieces]) + + +class _LooseFeedParser(_FeedParserMixin, _BaseHTMLProcessor): + + def __init__( + self, + baseuri, + baselang, + encoding, + ): + sgmllib.SGMLParser.__init__(self) + _FeedParserMixin.__init__(self, baseuri, baselang, encoding) + + def decodeEntities(self, element, data): + data = data.replace('<', '<') + data = data.replace('<', '<') + data = data.replace('>', '>') + data = data.replace('>', '>') + data = data.replace('&', '&') + data = data.replace('&', '&') + data = data.replace('"', '"') + data = data.replace('"', '"') + data = data.replace(''', ''') + data = data.replace(''', ''') + if self.contentparams.has_key('type')\ + and not self.contentparams.get('type', 'xml' + ).endswith('xml'): + data = data.replace('<', '<') + data = data.replace('>', '>') + data = data.replace('&', '&') + data = data.replace('"', '"') + data = data.replace(''', "'") + return data + + +class _RelativeURIResolver(_BaseHTMLProcessor): + + relative_uris = [ + ('a', 'href'), + ('applet', 'codebase'), + ('area', 'href'), + ('blockquote', 'cite'), + ('body', 'background'), + ('del', 'cite'), + ('form', 'action'), + ('frame', 'longdesc'), + ('frame', 'src'), + ('iframe', 'longdesc'), + ('iframe', 'src'), + ('head', 'profile'), + ('img', 'longdesc'), + ('img', 'src'), + ('img', 'usemap'), + ('input', 'src'), + ('input', 'usemap'), + ('ins', 'cite'), + ('link', 'href'), + ('object', 'classid'), + ('object', 'codebase'), + ('object', 'data'), + ('object', 'usemap'), + ('q', 'cite'), + ('script', 'src'), + ] + + def __init__(self, baseuri, encoding): + _BaseHTMLProcessor.__init__(self, encoding) + self.baseuri = baseuri + + def resolveURI(self, uri): + return _urljoin(self.baseuri, uri) + + def unknown_starttag(self, tag, attrs): + attrs = self.normalize_attrs(attrs) + attrs = [(key, (tag, key) in self.relative_uris + and self.resolveURI(value) or value) for (key, + value) in attrs] + _BaseHTMLProcessor.unknown_starttag(self, tag, attrs) + + +def _resolveRelativeURIs(htmlSource, baseURI, encoding): + if _debug: + sys.stderr.write('entering _resolveRelativeURIs\n') + p = _RelativeURIResolver(baseURI, encoding) + p.feed(htmlSource) + return p.output() + + +class _HTMLSanitizer(_BaseHTMLProcessor): + + acceptable_elements = [ + 'a', + 'abbr', + 'acronym', + 'address', + 'area', + 'b', + 'big', + 'blockquote', + 'br', + 'button', + 'caption', + 'center', + 'cite', + 'code', + 'col', + 'colgroup', + 'dd', + 'del', + 'dfn', + 'dir', + 'div', + 'dl', + 'dt', + 'em', + 'fieldset', + 'font', + 'form', + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'hr', + 'i', + 'img', + 'input', + 'ins', + 'kbd', + 'label', + 'legend', + 'li', + 'map', + 'menu', + 'ol', + 'optgroup', + 'option', + 'p', + 'pre', + 'q', + 's', + 'samp', + 'select', + 'small', + 'span', + 'strike', + 'strong', + 'sub', + 'sup', + 'table', + 'tbody', + 'td', + 'textarea', + 'tfoot', + 'th', + 'thead', + 'tr', + 'tt', + 'u', + 'ul', + 'var', + ] + + acceptable_attributes = [ + 'abbr', + 'accept', + 'accept-charset', + 'accesskey', + 'action', + 'align', + 'alt', + 'axis', + 'border', + 'cellpadding', + 'cellspacing', + 'char', + 'charoff', + 'charset', + 'checked', + 'cite', + 'class', + 'clear', + 'cols', + 'colspan', + 'color', + 'compact', + 'coords', + 'datetime', + 'dir', + 'disabled', + 'enctype', + 'for', + 'frame', + 'headers', + 'height', + 'href', + 'hreflang', + 'hspace', + 'id', + 'ismap', + 'label', + 'lang', + 'longdesc', + 'maxlength', + 'media', + 'method', + 'multiple', + 'name', + 'nohref', + 'noshade', + 'nowrap', + 'prompt', + 'readonly', + 'rel', + 'rev', + 'rows', + 'rowspan', + 'rules', + 'scope', + 'selected', + 'shape', + 'size', + 'span', + 'src', + 'start', + 'summary', + 'tabindex', + 'target', + 'title', + 'type', + 'usemap', + 'valign', + 'value', + 'vspace', + 'width', + ] + + unacceptable_elements_with_end_tag = ['script', 'applet'] + + def reset(self): + _BaseHTMLProcessor.reset(self) + self.unacceptablestack = 0 + + def unknown_starttag(self, tag, attrs): + if not tag in self.acceptable_elements: + if tag in self.unacceptable_elements_with_end_tag: + self.unacceptablestack += 1 + return + attrs = self.normalize_attrs(attrs) + attrs = [(key, value) for (key, value) in attrs if key + in self.acceptable_attributes] + _BaseHTMLProcessor.unknown_starttag(self, tag, attrs) + + def unknown_endtag(self, tag): + if not tag in self.acceptable_elements: + if tag in self.unacceptable_elements_with_end_tag: + self.unacceptablestack -= 1 + return + _BaseHTMLProcessor.unknown_endtag(self, tag) + + def handle_pi(self, text): + pass + + def handle_decl(self, text): + pass + + def handle_data(self, text): + if not self.unacceptablestack: + _BaseHTMLProcessor.handle_data(self, text) + + +def _sanitizeHTML(htmlSource, encoding): + p = _HTMLSanitizer(encoding) + p.feed(htmlSource) + data = p.output() + if TIDY_MARKUP: + + # loop through list of preferred Tidy interfaces looking for one that's installed, + # then set up a common _tidy function to wrap the interface-specific API. + + _tidy = None + for tidy_interface in PREFERRED_TIDY_INTERFACES: + try: + if tidy_interface == 'uTidy': + from tidy import parseString as _utidy + + def _tidy(data, **kwargs): + return str(_utidy(data, **kwargs)) + + break + elif tidy_interface == 'mxTidy': + from mx.Tidy import Tidy as _mxtidy + + def _tidy(data, **kwargs): + (nerrors, nwarnings, data, errordata) = \ + _mxtidy.tidy(data, **kwargs) + return data + + break + except: + pass + if _tidy: + utf8 = type(data) == type(u'') + if utf8: + data = data.encode('utf-8') + data = _tidy(data, output_xhtml=1, numeric_entities=1, + wrap=0, char_encoding='utf8') + if utf8: + data = unicode(data, 'utf-8') + if data.count(''): + data = data.split('>', 1)[1] + if data.count('= '2.3.3' + assert base64 != None + (user, passw) = \ + base64.decodestring(req.headers['Authorization' + ].split(' ')[1]).split(':') + realm = re.findall('realm="([^"]*)"', + headers['WWW-Authenticate'])[0] + self.add_password(realm, host, user, passw) + retry = self.http_error_auth_reqed('www-authenticate', + host, req, headers) + self.reset_retry_count() + return retry + except: + return self.http_error_default(req, fp, code, msg, headers) + + +def _open_resource( + url_file_stream_or_string, + etag, + modified, + agent, + referrer, + handlers, + ): + """URL, filename, or string --> stream + + This function lets you define parsers that take any input source + (URL, pathname to local or network file, or actual data as a string) + and deal with it in a uniform manner. Returned object is guaranteed + to have all the basic stdio read methods (read, readline, readlines). + Just .close() the object when you're done with it. + + If the etag argument is supplied, it will be used as the value of an + If-None-Match request header. + + If the modified argument is supplied, it must be a tuple of 9 integers + as returned by gmtime() in the standard Python time module. This MUST + be in GMT (Greenwich Mean Time). The formatted date/time will be used + as the value of an If-Modified-Since request header. + + If the agent argument is supplied, it will be used as the value of a + User-Agent request header. + + If the referrer argument is supplied, it will be used as the value of a + Referer[sic] request header. + + If handlers is supplied, it is a list of handlers used to build a + urllib2 opener. + """ + + if hasattr(url_file_stream_or_string, 'read'): + return url_file_stream_or_string + + if url_file_stream_or_string == '-': + return sys.stdin + + if urlparse.urlparse(url_file_stream_or_string)[0] in ('http', + 'https', 'ftp'): + if not agent: + agent = USER_AGENT + + # test for inline user:password for basic auth + + auth = None + if base64: + (urltype, rest) = \ + urllib.splittype(url_file_stream_or_string) + (realhost, rest) = urllib.splithost(rest) + if realhost: + (user_passwd, realhost) = urllib.splituser(realhost) + if user_passwd: + url_file_stream_or_string = '%s://%s%s' % (urltype, + realhost, rest) + auth = base64.encodestring(user_passwd).strip() + + # try to open with urllib2 (to use optional headers) + + request = urllib2.Request(url_file_stream_or_string) + request.add_header('User-Agent', agent) + if etag: + request.add_header('If-None-Match', etag) + if modified: + + # format into an RFC 1123-compliant timestamp. We can't use + # time.strftime() since the %a and %b directives can be affected + # by the current locale, but RFC 2616 states that dates must be + # in English. + + short_weekdays = [ + 'Mon', + 'Tue', + 'Wed', + 'Thu', + 'Fri', + 'Sat', + 'Sun', + ] + months = [ + 'Jan', + 'Feb', + 'Mar', + 'Apr', + 'May', + 'Jun', + 'Jul', + 'Aug', + 'Sep', + 'Oct', + 'Nov', + 'Dec', + ] + request.add_header('If-Modified-Since', + '%s, %02d %s %04d %02d:%02d:%02d GMT' % ( + short_weekdays[modified[6]], + modified[2], + months[modified[1] - 1], + modified[0], + modified[3], + modified[4], + modified[5], + )) + if referrer: + request.add_header('Referer', referrer) + if gzip and zlib: + request.add_header('Accept-encoding', 'gzip, deflate') + elif gzip: + request.add_header('Accept-encoding', 'gzip') + elif zlib: + request.add_header('Accept-encoding', 'deflate') + else: + request.add_header('Accept-encoding', '') + if auth: + request.add_header('Authorization', 'Basic %s' % auth) + if ACCEPT_HEADER: + request.add_header('Accept', ACCEPT_HEADER) + request.add_header('A-IM', 'feed') # RFC 3229 support + opener = apply(urllib2.build_opener, tuple([_FeedURLHandler()] + + handlers)) + opener.addheaders = [] # RMK - must clear so we only send our custom User-Agent + try: + return opener.open(request) + finally: + opener.close() # JohnD + + # try to open with native open function (if url_file_stream_or_string is a filename) + + try: + return open(url_file_stream_or_string) + except: + pass + + # treat url_file_stream_or_string as string + + return _StringIO(str(url_file_stream_or_string)) + + +_date_handlers = [] + + +def registerDateHandler(func): + """Register a date handler function (takes string, returns 9-tuple date in GMT)""" + + _date_handlers.insert(0, func) + + +# ISO-8601 date parsing routines written by Fazal Majid. +# The ISO 8601 standard is very convoluted and irregular - a full ISO 8601 +# parser is beyond the scope of feedparser and would be a worthwhile addition +# to the Python library. +# A single regular expression cannot parse ISO 8601 date formats into groups +# as the standard is highly irregular (for instance is 030104 2003-01-04 or +# 0301-04-01), so we use templates instead. +# Please note the order in templates is significant because we need a +# greedy match. + +_iso8601_tmpl = [ + 'YYYY-?MM-?DD', + 'YYYY-MM', + 'YYYY-?OOO', + 'YY-?MM-?DD', + 'YY-?OOO', + 'YYYY', + '-YY-?MM', + '-OOO', + '-YY', + '--MM-?DD', + '--MM', + '---DD', + 'CC', + '', + ] +_iso8601_re = [tmpl.replace('YYYY', r'(?P\d{4})').replace('YY', + r'(?P\d\d)').replace('MM', r'(?P[01]\d)' + ).replace('DD', r'(?P[0123]\d)').replace('OOO', + r'(?P[0123]\d\d)').replace('CC', + r'(?P\d\d$)') + + r'(T?(?P\d{2}):(?P\d{2})' + + r'(:(?P\d{2}))?' + + r'(?P[+-](?P\d{2})(:(?P\d{2}))?|Z)?)?' + for tmpl in _iso8601_tmpl] +del tmpl +_iso8601_matches = [re.compile(regex).match for regex in _iso8601_re] +del regex + + +def _parse_date_iso8601(dateString): + """Parse a variety of ISO-8601-compatible formats like 20040105""" + + m = None + for _iso8601_match in _iso8601_matches: + m = _iso8601_match(dateString) + if m: + break + if not m: + return + if m.span() == (0, 0): + return + params = m.groupdict() + ordinal = params.get('ordinal', 0) + if ordinal: + ordinal = int(ordinal) + else: + ordinal = 0 + year = params.get('year', '--') + if not year or year == '--': + year = time.gmtime()[0] + elif len(year) == 2: + + # ISO 8601 assumes current century, i.e. 93 -> 2093, NOT 1993 + + year = 100 * int(time.gmtime()[0] / 100) + int(year) + else: + year = int(year) + month = params.get('month', '-') + if not month or month == '-': + + # ordinals are NOT normalized by mktime, we simulate them + # by setting month=1, day=ordinal + + if ordinal: + month = 1 + else: + month = time.gmtime()[1] + month = int(month) + day = params.get('day', 0) + if not day: + + # see above + + if ordinal: + day = ordinal + elif params.get('century', 0) or params.get('year', 0)\ + or params.get('month', 0): + day = 1 + else: + day = time.gmtime()[2] + else: + day = int(day) + + # special case of the century - is the first year of the 21st century + # 2000 or 2001 ? The debate goes on... + + if 'century' in params.keys(): + year = (int(params['century']) - 1) * 100 + 1 + + # in ISO 8601 most fields are optional + + for field in ['hour', 'minute', 'second', 'tzhour', 'tzmin']: + if not params.get(field, None): + params[field] = 0 + hour = int(params.get('hour', 0)) + minute = int(params.get('minute', 0)) + second = int(params.get('second', 0)) + + # weekday is normalized by mktime(), we can ignore it + + weekday = 0 + + # daylight savings is complex, but not needed for feedparser's purposes + # as time zones, if specified, include mention of whether it is active + # (e.g. PST vs. PDT, CET). Using -1 is implementation-dependent and + # and most implementations have DST bugs + + daylight_savings_flag = 0 + tm = [ + year, + month, + day, + hour, + minute, + second, + weekday, + ordinal, + daylight_savings_flag, + ] + + # ISO 8601 time zone adjustments + + tz = params.get('tz') + if tz and tz != 'Z': + if tz[0] == '-': + tm[3] += int(params.get('tzhour', 0)) + tm[4] += int(params.get('tzmin', 0)) + elif tz[0] == '+': + tm[3] -= int(params.get('tzhour', 0)) + tm[4] -= int(params.get('tzmin', 0)) + else: + return None + + # Python's time.mktime() is a wrapper around the ANSI C mktime(3c) + # which is guaranteed to normalize d/m/y/h/m/s. + # Many implementations have bugs, but we'll pretend they don't. + + return time.localtime(time.mktime(tm)) + + +registerDateHandler(_parse_date_iso8601) + +# 8-bit date handling routines written by ytrewq1. + +_korean_year = u'\ub144' # b3e2 in euc-kr +_korean_month = u'\uc6d4' # bff9 in euc-kr +_korean_day = u'\uc77c' # c0cf in euc-kr +_korean_am = u'\uc624\uc804' # bfc0 c0fc in euc-kr +_korean_pm = u'\uc624\ud6c4' # bfc0 c8c4 in euc-kr + +_korean_onblog_date_re = \ + re.compile('(\d{4})%s\s+(\d{2})%s\s+(\d{2})%s\s+(\d{2}):(\d{2}):(\d{2})' + % (_korean_year, _korean_month, _korean_day)) +_korean_nate_date_re = \ + re.compile(u'(\d{4})-(\d{2})-(\d{2})\s+(%s|%s)\s+(\d{,2}):(\d{,2}):(\d{,2})' + % (_korean_am, _korean_pm)) + + +def _parse_date_onblog(dateString): + """Parse a string according to the OnBlog 8-bit date format""" + + m = _korean_onblog_date_re.match(dateString) + if not m: + return + w3dtfdate = \ + '%(year)s-%(month)s-%(day)sT%(hour)s:%(minute)s:%(second)s%(zonediff)s'\ + % { + 'year': m.group(1), + 'month': m.group(2), + 'day': m.group(3), + 'hour': m.group(4), + 'minute': m.group(5), + 'second': m.group(6), + 'zonediff': '+09:00', + } + if _debug: + sys.stderr.write('OnBlog date parsed as: %s\n' % w3dtfdate) + return _parse_date_w3dtf(w3dtfdate) + + +registerDateHandler(_parse_date_onblog) + + +def _parse_date_nate(dateString): + """Parse a string according to the Nate 8-bit date format""" + + m = _korean_nate_date_re.match(dateString) + if not m: + return + hour = int(m.group(5)) + ampm = m.group(4) + if ampm == _korean_pm: + hour += 12 + hour = str(hour) + if len(hour) == 1: + hour = '0' + hour + w3dtfdate = \ + '%(year)s-%(month)s-%(day)sT%(hour)s:%(minute)s:%(second)s%(zonediff)s'\ + % { + 'year': m.group(1), + 'month': m.group(2), + 'day': m.group(3), + 'hour': hour, + 'minute': m.group(6), + 'second': m.group(7), + 'zonediff': '+09:00', + } + if _debug: + sys.stderr.write('Nate date parsed as: %s\n' % w3dtfdate) + return _parse_date_w3dtf(w3dtfdate) + + +registerDateHandler(_parse_date_nate) + +_mssql_date_re = \ + re.compile('(\d{4})-(\d{2})-(\d{2})\s+(\d{2}):(\d{2}):(\d{2})(\.\d+)?' + ) + + +def _parse_date_mssql(dateString): + """Parse a string according to the MS SQL date format""" + + m = _mssql_date_re.match(dateString) + if not m: + return + w3dtfdate = \ + '%(year)s-%(month)s-%(day)sT%(hour)s:%(minute)s:%(second)s%(zonediff)s'\ + % { + 'year': m.group(1), + 'month': m.group(2), + 'day': m.group(3), + 'hour': m.group(4), + 'minute': m.group(5), + 'second': m.group(6), + 'zonediff': '+09:00', + } + if _debug: + sys.stderr.write('MS SQL date parsed as: %s\n' % w3dtfdate) + return _parse_date_w3dtf(w3dtfdate) + + +registerDateHandler(_parse_date_mssql) + +# Unicode strings for Greek date strings + +_greek_months = { + u'\u0399\u03b1\u03bd': u'Jan', + u'\u03a6\u03b5\u03b2': u'Feb', + u'\u039c\u03ac\u03ce': u'Mar', + u'\u039c\u03b1\u03ce': u'Mar', + u'\u0391\u03c0\u03c1': u'Apr', + u'\u039c\u03ac\u03b9': u'May', + u'\u039c\u03b1\u03ca': u'May', + u'\u039c\u03b1\u03b9': u'May', + u'\u0399\u03bf\u03cd\u03bd': u'Jun', + u'\u0399\u03bf\u03bd': u'Jun', + u'\u0399\u03bf\u03cd\u03bb': u'Jul', + u'\u0399\u03bf\u03bb': u'Jul', + u'\u0391\u03cd\u03b3': u'Aug', + u'\u0391\u03c5\u03b3': u'Aug', + u'\u03a3\u03b5\u03c0': u'Sep', + u'\u039f\u03ba\u03c4': u'Oct', + u'\u039d\u03bf\u03ad': u'Nov', + u'\u039d\u03bf\u03b5': u'Nov', + u'\u0394\u03b5\u03ba': u'Dec', + } + +_greek_wdays = { + u'\u039a\u03c5\u03c1': u'Sun', + u'\u0394\u03b5\u03c5': u'Mon', + u'\u03a4\u03c1\u03b9': u'Tue', + u'\u03a4\u03b5\u03c4': u'Wed', + u'\u03a0\u03b5\u03bc': u'Thu', + u'\u03a0\u03b1\u03c1': u'Fri', + u'\u03a3\u03b1\u03b2': u'Sat', + } + +_greek_date_format_re = \ + re.compile(u'([^,]+),\s+(\d{2})\s+([^\s]+)\s+(\d{4})\s+(\d{2}):(\d{2}):(\d{2})\s+([^\s]+)' + ) + + +def _parse_date_greek(dateString): + """Parse a string according to a Greek 8-bit date format.""" + + m = _greek_date_format_re.match(dateString) + if not m: + return + try: + wday = _greek_wdays[m.group(1)] + month = _greek_months[m.group(3)] + except: + return + rfc822date = \ + '%(wday)s, %(day)s %(month)s %(year)s %(hour)s:%(minute)s:%(second)s %(zonediff)s'\ + % { + 'wday': wday, + 'day': m.group(2), + 'month': month, + 'year': m.group(4), + 'hour': m.group(5), + 'minute': m.group(6), + 'second': m.group(7), + 'zonediff': m.group(8), + } + if _debug: + sys.stderr.write('Greek date parsed as: %s\n' % rfc822date) + return _parse_date_rfc822(rfc822date) + + +registerDateHandler(_parse_date_greek) + +# Unicode strings for Hungarian date strings + +_hungarian_months = { + u'janu\u00e1r': u'01', + u'febru\u00e1ri': u'02', + u'm\u00e1rcius': u'03', + u'\u00e1prilis': u'04', + u'm\u00e1ujus': u'05', + u'j\u00fanius': u'06', + u'j\u00falius': u'07', + u'augusztus': u'08', + u'szeptember': u'09', + u'okt\u00f3ber': u'10', + u'november': u'11', + u'december': u'12', + } + +_hungarian_date_format_re = \ + re.compile(u'(\d{4})-([^-]+)-(\d{,2})T(\d{,2}):(\d{2})((\+|-)(\d{,2}:\d{2}))' + ) + + +def _parse_date_hungarian(dateString): + """Parse a string according to a Hungarian 8-bit date format.""" + + m = _hungarian_date_format_re.match(dateString) + if not m: + return + try: + month = _hungarian_months[m.group(2)] + day = m.group(3) + if len(day) == 1: + day = '0' + day + hour = m.group(4) + if len(hour) == 1: + hour = '0' + hour + except: + return + w3dtfdate = \ + '%(year)s-%(month)s-%(day)sT%(hour)s:%(minute)s%(zonediff)s' % { + 'year': m.group(1), + 'month': month, + 'day': day, + 'hour': hour, + 'minute': m.group(5), + 'zonediff': m.group(6), + } + if _debug: + sys.stderr.write('Hungarian date parsed as: %s\n' % w3dtfdate) + return _parse_date_w3dtf(w3dtfdate) + + +registerDateHandler(_parse_date_hungarian) + +# W3DTF-style date parsing adapted from PyXML xml.utils.iso8601, written by +# Drake and licensed under the Python license. Removed all range checking +# for month, day, hour, minute, and second, since mktime will normalize +# these later + + +def _parse_date_w3dtf(dateString): + + def __extract_date(m): + year = int(m.group('year')) + if year < 100: + year = 100 * int(time.gmtime()[0] / 100) + int(year) + if year < 1000: + return (0, 0, 0) + julian = m.group('julian') + if julian: + julian = int(julian) + month = julian / 30 + 1 + day = julian % 30 + 1 + jday = None + while jday != julian: + t = time.mktime(( + year, + month, + day, + 0, + 0, + 0, + 0, + 0, + 0, + )) + jday = time.gmtime(t)[-2] + diff = abs(jday - julian) + if jday > julian: + if diff < day: + day = day - diff + else: + month = month - 1 + day = 31 + elif jday < julian: + if day + diff < 28: + day = day + diff + else: + month = month + 1 + return (year, month, day) + month = m.group('month') + day = 1 + if month is None: + month = 1 + else: + month = int(month) + day = m.group('day') + if day: + day = int(day) + else: + day = 1 + return (year, month, day) + + def __extract_time(m): + if not m: + return (0, 0, 0) + hours = m.group('hours') + if not hours: + return (0, 0, 0) + hours = int(hours) + minutes = int(m.group('minutes')) + seconds = m.group('seconds') + if seconds: + seconds = int(seconds) + else: + seconds = 0 + return (hours, minutes, seconds) + + def __extract_tzd(m): + """Return the Time Zone Designator as an offset in seconds from UTC.""" + + if not m: + return 0 + tzd = m.group('tzd') + if not tzd: + return 0 + if tzd == 'Z': + return 0 + hours = int(m.group('tzdhours')) + minutes = m.group('tzdminutes') + if minutes: + minutes = int(minutes) + else: + minutes = 0 + offset = (hours * 60 + minutes) * 60 + if tzd[0] == '+': + return -offset + return offset + + __date_re = \ + '(?P\\d\\d\\d\\d)(?:(?P-|)(?:(?P\\d\\d\\d)|(?P\\d\\d)(?:(?P=dsep)(?P\\d\\d))?))?' + + __tzd_re = \ + '(?P[-+](?P\d\d)(?::?(?P\d\d))|Z)' + __tzd_rx = re.compile(__tzd_re) + __time_re = \ + '(?P\\d\\d)(?P:|)(?P\\d\\d)(?:(?P=tsep)(?P\\d\\d(?:[.,]\\d+)?))?'\ + + __tzd_re + __datetime_re = '%s(?:T%s)?' % (__date_re, __time_re) + __datetime_rx = re.compile(__datetime_re) + m = __datetime_rx.match(dateString) + if m is None or m.group() != dateString: + return + gmt = __extract_date(m) + __extract_time(m) + (0, 0, 0) + if gmt[0] == 0: + return + return time.gmtime((time.mktime(gmt) + __extract_tzd(m)) + - time.timezone) + + +registerDateHandler(_parse_date_w3dtf) + + +def _parse_date_rfc822(dateString): + """Parse an RFC822, RFC1123, RFC2822, or asctime-style date""" + + data = dateString.split() + if data[0][-1] in (',', '.') or data[0].lower() in rfc822._daynames: + del data[0] + if len(data) == 4: + s = data[3] + i = s.find('+') + if i > 0: + data[3:] = [s[:i], s[i + 1:]] + else: + data.append('') + dateString = ' '.join(data) + if len(data) < 5: + dateString += ' 00:00:00 GMT' + tm = rfc822.parsedate_tz(dateString) + if tm: + return time.gmtime(rfc822.mktime_tz(tm)) + + +# rfc822.py defines several time zones, but we define some extra ones. +# 'ET' is equivalent to 'EST', etc. + +_additional_timezones = { + 'AT': -400, + 'ET': -500, + 'CT': -600, + 'MT': -700, + 'PT': -800, + } +rfc822._timezones.update(_additional_timezones) +registerDateHandler(_parse_date_rfc822) + + +def _parse_date(dateString): + """Parses a variety of date formats into a 9-tuple in GMT""" + + for handler in _date_handlers: + try: + date9tuple = handler(dateString) + if not date9tuple: + continue + if len(date9tuple) != 9: + if _debug: + sys.stderr.write('date handler function must return 9-tuple\n' + ) + raise ValueError, 'date handler function must return 9-tuple' + map(int, date9tuple) + return date9tuple + except Exception, e: + if _debug: + sys.stderr.write('%s raised %s\n' % (handler.__name__, + repr(e))) + pass + return None + + +def _getCharacterEncoding(http_headers, xml_data): + """Get the character encoding of the XML document + + http_headers is a dictionary + xml_data is a raw string (not Unicode) + + This is so much trickier than it sounds, it's not even funny. + According to RFC 3023 ('XML Media Types'), if the HTTP Content-Type + is application/xml, application/*+xml, + application/xml-external-parsed-entity, or application/xml-dtd, + the encoding given in the charset parameter of the HTTP Content-Type + takes precedence over the encoding given in the XML prefix within the + document, and defaults to 'utf-8' if neither are specified. But, if + the HTTP Content-Type is text/xml, text/*+xml, or + text/xml-external-parsed-entity, the encoding given in the XML prefix + within the document is ALWAYS IGNORED and only the encoding given in + the charset parameter of the HTTP Content-Type header should be + respected, and it defaults to 'us-ascii' if not specified. + + Furthermore, discussion on the atom-syntax mailing list with the + author of RFC 3023 leads me to the conclusion that any document + served with a Content-Type of text/* and no charset parameter + must be treated as us-ascii. (We now do this.) And also that it + must always be flagged as non-well-formed. (We now do this too.) + + If Content-Type is unspecified (input was local file or non-HTTP source) + or unrecognized (server just got it totally wrong), then go by the + encoding given in the XML prefix of the document and default to + 'iso-8859-1' as per the HTTP specification (RFC 2616). + + Then, assuming we didn't find a character encoding in the HTTP headers + (and the HTTP Content-type allowed us to look in the body), we need + to sniff the first few bytes of the XML data and try to determine + whether the encoding is ASCII-compatible. Section F of the XML + specification shows the way here: + http://www.w3.org/TR/REC-xml/#sec-guessing-no-ext-info + + If the sniffed encoding is not ASCII-compatible, we need to make it + ASCII compatible so that we can sniff further into the XML declaration + to find the encoding attribute, which will tell us the true encoding. + + Of course, none of this guarantees that we will be able to parse the + feed in the declared character encoding (assuming it was declared + correctly, which many are not). CJKCodecs and iconv_codec help a lot; + you should definitely install them if you can. + http://cjkpython.i18n.org/ + """ + + def _parseHTTPContentType(content_type): + """takes HTTP Content-Type header and returns (content type, charset) + + If no charset is specified, returns (content type, '') + If no content type is specified, returns ('', '') + Both return parameters are guaranteed to be lowercase strings + """ + + content_type = content_type or '' + (content_type, params) = cgi.parse_header(content_type) + return (content_type, params.get('charset', '').replace("'", '' + )) + + sniffed_xml_encoding = '' + xml_encoding = '' + true_encoding = '' + (http_content_type, http_encoding) = \ + _parseHTTPContentType(http_headers.get('content-type')) + + # Must sniff for non-ASCII-compatible character encodings before + # searching for XML declaration. This heuristic is defined in + # section F of the XML specification: + # http://www.w3.org/TR/REC-xml/#sec-guessing-no-ext-info + + try: + if xml_data[:4] == '\x4c\x6f\xa7\x94': + + # EBCDIC + + xml_data = _ebcdic_to_ascii(xml_data) + elif xml_data[:4] == '\x00\x3c\x00\x3f': + + # UTF-16BE + + sniffed_xml_encoding = 'utf-16be' + xml_data = unicode(xml_data, 'utf-16be').encode('utf-8') + elif len(xml_data) >= 4 and xml_data[:2] == '\xfe\xff'\ + and xml_data[2:4] != '\x00\x00': + + # UTF-16BE with BOM + + sniffed_xml_encoding = 'utf-16be' + xml_data = unicode(xml_data[2:], 'utf-16be').encode('utf-8') + elif xml_data[:4] == '\x3c\x00\x3f\x00': + + # UTF-16LE + + sniffed_xml_encoding = 'utf-16le' + xml_data = unicode(xml_data, 'utf-16le').encode('utf-8') + elif len(xml_data) >= 4 and xml_data[:2] == '\xff\xfe'\ + and xml_data[2:4] != '\x00\x00': + + # UTF-16LE with BOM + + sniffed_xml_encoding = 'utf-16le' + xml_data = unicode(xml_data[2:], 'utf-16le').encode('utf-8') + elif xml_data[:4] == '\x00\x00\x00\x3c': + + # UTF-32BE + + sniffed_xml_encoding = 'utf-32be' + xml_data = unicode(xml_data, 'utf-32be').encode('utf-8') + elif xml_data[:4] == '\x3c\x00\x00\x00': + + # UTF-32LE + + sniffed_xml_encoding = 'utf-32le' + xml_data = unicode(xml_data, 'utf-32le').encode('utf-8') + elif xml_data[:4] == '\x00\x00\xfe\xff': + + # UTF-32BE with BOM + + sniffed_xml_encoding = 'utf-32be' + xml_data = unicode(xml_data[4:], 'utf-32be').encode('utf-8') + elif xml_data[:4] == '\xff\xfe\x00\x00': + + # UTF-32LE with BOM + + sniffed_xml_encoding = 'utf-32le' + xml_data = unicode(xml_data[4:], 'utf-32le').encode('utf-8') + elif xml_data[:3] == '\xef\xbb\xbf': + + # UTF-8 with BOM + + sniffed_xml_encoding = 'utf-8' + xml_data = unicode(xml_data[3:], 'utf-8').encode('utf-8') + else: + + # ASCII-compatible + + pass + xml_encoding_match = \ + re.compile('^<\?.*encoding=[\'"](.*?)[\'"].*\?>' + ).match(xml_data) + except: + xml_encoding_match = None + if xml_encoding_match: + xml_encoding = xml_encoding_match.groups()[0].lower() + if sniffed_xml_encoding and xml_encoding in ( + 'iso-10646-ucs-2', + 'ucs-2', + 'csunicode', + 'iso-10646-ucs-4', + 'ucs-4', + 'csucs4', + 'utf-16', + 'utf-32', + 'utf_16', + 'utf_32', + 'utf16', + 'u16', + ): + xml_encoding = sniffed_xml_encoding + acceptable_content_type = 0 + application_content_types = ('application/xml', + 'application/xml-dtd', + 'application/xml-external-parsed-entity' + ) + text_content_types = ('text/xml', 'text/xml-external-parsed-entity') + if http_content_type in application_content_types\ + or http_content_type.startswith('application/')\ + and http_content_type.endswith('+xml'): + acceptable_content_type = 1 + true_encoding = http_encoding or xml_encoding or 'utf-8' + elif http_content_type in text_content_types\ + or http_content_type.startswith('text/')\ + and http_content_type.endswith('+xml'): + acceptable_content_type = 1 + true_encoding = http_encoding or 'us-ascii' + elif http_content_type.startswith('text/'): + true_encoding = http_encoding or 'us-ascii' + elif http_headers and not http_headers.has_key('content-type'): + true_encoding = xml_encoding or 'iso-8859-1' + else: + true_encoding = xml_encoding or 'utf-8' + return (true_encoding, http_encoding, xml_encoding, + sniffed_xml_encoding, acceptable_content_type) + + +def _toUTF8(data, encoding): + """Changes an XML data stream on the fly to specify a new encoding + + data is a raw sequence of bytes (not Unicode) that is presumed to be in %encoding already + encoding is a string recognized by encodings.aliases + """ + + if _debug: + sys.stderr.write('entering _toUTF8, trying encoding %s\n' + % encoding) + + # strip Byte Order Mark (if present) + + if len(data) >= 4 and data[:2] == '\xfe\xff' and data[2:4]\ + != '\x00\x00': + if _debug: + sys.stderr.write('stripping BOM\n') + if encoding != 'utf-16be': + sys.stderr.write('trying utf-16be instead\n') + encoding = 'utf-16be' + data = data[2:] + elif len(data) >= 4 and data[:2] == '\xff\xfe' and data[2:4]\ + != '\x00\x00': + if _debug: + sys.stderr.write('stripping BOM\n') + if encoding != 'utf-16le': + sys.stderr.write('trying utf-16le instead\n') + encoding = 'utf-16le' + data = data[2:] + elif data[:3] == '\xef\xbb\xbf': + if _debug: + sys.stderr.write('stripping BOM\n') + if encoding != 'utf-8': + sys.stderr.write('trying utf-8 instead\n') + encoding = 'utf-8' + data = data[3:] + elif data[:4] == '\x00\x00\xfe\xff': + if _debug: + sys.stderr.write('stripping BOM\n') + if encoding != 'utf-32be': + sys.stderr.write('trying utf-32be instead\n') + encoding = 'utf-32be' + data = data[4:] + elif data[:4] == '\xff\xfe\x00\x00': + if _debug: + sys.stderr.write('stripping BOM\n') + if encoding != 'utf-32le': + sys.stderr.write('trying utf-32le instead\n') + encoding = 'utf-32le' + data = data[4:] + newdata = unicode(data, encoding) + if _debug: + sys.stderr.write('successfully converted %s data to unicode\n' + % encoding) + declmatch = re.compile('^<\?xml[^>]*?>') + newdecl = '''''' + if declmatch.search(newdata): + newdata = declmatch.sub(newdecl, newdata) + else: + newdata = newdecl + u'\n' + newdata + return newdata.encode('utf-8') + + +def _stripDoctype(data): + """Strips DOCTYPE from XML document, returns (rss_version, stripped_data) + + rss_version may be 'rss091n' or None + stripped_data is the same XML document, minus the DOCTYPE + """ + + entity_pattern = re.compile(r']*?)>', re.MULTILINE) + data = entity_pattern.sub('', data) + doctype_pattern = re.compile(r']*?)>', re.MULTILINE) + doctype_results = doctype_pattern.findall(data) + doctype = doctype_results and doctype_results[0] or '' + if doctype.lower().count('netscape'): + version = 'rss091n' + else: + version = None + data = doctype_pattern.sub('', data) + return (version, data) + + +def parse( + url_file_stream_or_string, + etag=None, + modified=None, + agent=None, + referrer=None, + handlers=[], + ): + """Parse a feed from a URL, file, stream, or string""" + + result = FeedParserDict() + result['feed'] = FeedParserDict() + result['entries'] = [] + if _XML_AVAILABLE: + result['bozo'] = 0 + if type(handlers) == types.InstanceType: + handlers = [handlers] + try: + f = _open_resource( + url_file_stream_or_string, + etag, + modified, + agent, + referrer, + handlers, + ) + data = f.read() + except Exception, e: + result['bozo'] = 1 + result['bozo_exception'] = e + data = '' + f = None + + # if feed is gzip-compressed, decompress it + + if f and data and hasattr(f, 'headers'): + if gzip and f.headers.get('content-encoding', '') == 'gzip': + try: + data = gzip.GzipFile(fileobj=_StringIO(data)).read() + except Exception, e: + + # Some feeds claim to be gzipped but they're not, so + # we get garbage. Ideally, we should re-request the + # feed without the 'Accept-encoding: gzip' header, + # but we don't. + + result['bozo'] = 1 + result['bozo_exception'] = e + data = '' + elif zlib and f.headers.get('content-encoding', '')\ + == 'deflate': + try: + data = zlib.decompress(data, -zlib.MAX_WBITS) + except Exception, e: + result['bozo'] = 1 + result['bozo_exception'] = e + data = '' + + # save HTTP headers + + if hasattr(f, 'info'): + info = f.info() + result['etag'] = info.getheader('ETag') + last_modified = info.getheader('Last-Modified') + if last_modified: + result['modified'] = _parse_date(last_modified) + if hasattr(f, 'url'): + result['href'] = f.url + result['status'] = 200 + if hasattr(f, 'status'): + result['status'] = f.status + if hasattr(f, 'headers'): + result['headers'] = f.headers.dict + if hasattr(f, 'close'): + f.close() + + # there are four encodings to keep track of: + # - http_encoding is the encoding declared in the Content-Type HTTP header + # - xml_encoding is the encoding declared in the ; changed +# project name +# 2.5 - 7/25/2003 - MAP - changed to Python license (all contributors agree); +# removed unnecessary urllib code -- urllib2 should always be available anyway; +# return actual url, status, and full HTTP headers (as result['url'], +# result['status'], and result['headers']) if parsing a remote feed over HTTP -- +# this should pass all the HTTP tests at ; +# added the latest namespace-of-the-week for RSS 2.0 +# 2.5.1 - 7/26/2003 - RMK - clear opener.addheaders so we only send our custom +# User-Agent (otherwise urllib2 sends two, which confuses some servers) +# 2.5.2 - 7/28/2003 - MAP - entity-decode inline xml properly; added support for +# inline and as used in some RSS 2.0 feeds +# 2.5.3 - 8/6/2003 - TvdV - patch to track whether we're inside an image or +# textInput, and also to return the character encoding (if specified) +# 2.6 - 1/1/2004 - MAP - dc:author support (MarekK); fixed bug tracking +# nested divs within content (JohnD); fixed missing sys import (JohanS); +# fixed regular expression to capture XML character encoding (Andrei); +# added support for Atom 0.3-style links; fixed bug with textInput tracking; +# added support for cloud (MartijnP); added support for multiple +# category/dc:subject (MartijnP); normalize content model: 'description' gets +# description (which can come from description, summary, or full content if no +# description), 'content' gets dict of base/language/type/value (which can come +# from content:encoded, xhtml:body, content, or fullitem); +# fixed bug matching arbitrary Userland namespaces; added xml:base and xml:lang +# tracking; fixed bug tracking unknown tags; fixed bug tracking content when +# element is not in default namespace (like Pocketsoap feed); +# resolve relative URLs in link, guid, docs, url, comments, wfw:comment, +# wfw:commentRSS; resolve relative URLs within embedded HTML markup in +# description, xhtml:body, content, content:encoded, title, subtitle, +# summary, info, tagline, and copyright; added support for pingback and +# trackback namespaces +# 2.7 - 1/5/2004 - MAP - really added support for trackback and pingback +# namespaces, as opposed to 2.6 when I said I did but didn't really; +# sanitize HTML markup within some elements; added mxTidy support (if +# installed) to tidy HTML markup within some elements; fixed indentation +# bug in _parse_date (FazalM); use socket.setdefaulttimeout if available +# (FazalM); universal date parsing and normalization (FazalM): 'created', modified', +# 'issued' are parsed into 9-tuple date format and stored in 'created_parsed', +# 'modified_parsed', and 'issued_parsed'; 'date' is duplicated in 'modified' +# and vice-versa; 'date_parsed' is duplicated in 'modified_parsed' and vice-versa +# 2.7.1 - 1/9/2004 - MAP - fixed bug handling " and '. fixed memory +# leak not closing url opener (JohnD); added dc:publisher support (MarekK); +# added admin:errorReportsTo support (MarekK); Python 2.1 dict support (MarekK) +# 2.7.4 - 1/14/2004 - MAP - added workaround for improperly formed
    tags in +# encoded HTML (skadz); fixed unicode handling in normalize_attrs (ChrisL); +# fixed relative URI processing for guid (skadz); added ICBM support; added +# base64 support +# 2.7.5 - 1/15/2004 - MAP - added workaround for malformed DOCTYPE (seen on many +# blogspot.com sites); added _debug variable +# 2.7.6 - 1/16/2004 - MAP - fixed bug with StringIO importing +# 3.0b3 - 1/23/2004 - MAP - parse entire feed with real XML parser (if available); +# added several new supported namespaces; fixed bug tracking naked markup in +# description; added support for enclosure; added support for source; re-added +# support for cloud which got dropped somehow; added support for expirationDate +# 3.0b4 - 1/26/2004 - MAP - fixed xml:lang inheritance; fixed multiple bugs tracking +# xml:base URI, one for documents that don't define one explicitly and one for +# documents that define an outer and an inner xml:base that goes out of scope +# before the end of the document +# 3.0b5 - 1/26/2004 - MAP - fixed bug parsing multiple links at feed level +# 3.0b6 - 1/27/2004 - MAP - added feed type and version detection, result['version'] +# will be one of SUPPORTED_VERSIONS.keys() or empty string if unrecognized; +# added support for creativeCommons:license and cc:license; added support for +# full Atom content model in title, tagline, info, copyright, summary; fixed bug +# with gzip encoding (not always telling server we support it when we do) +# 3.0b7 - 1/28/2004 - MAP - support Atom-style author element in author_detail +# (dictionary of 'name', 'url', 'email'); map author to author_detail if author +# contains name + email address +# 3.0b8 - 1/28/2004 - MAP - added support for contributor +# 3.0b9 - 1/29/2004 - MAP - fixed check for presence of dict function; added +# support for summary +# 3.0b10 - 1/31/2004 - MAP - incorporated ISO-8601 date parsing routines from +# xml.util.iso8601 +# 3.0b11 - 2/2/2004 - MAP - added 'rights' to list of elements that can contain +# dangerous markup; fiddled with decodeEntities (not right); liberalized +# date parsing even further +# 3.0b12 - 2/6/2004 - MAP - fiddled with decodeEntities (still not right); +# added support to Atom 0.2 subtitle; added support for Atom content model +# in copyright; better sanitizing of dangerous HTML elements with end tags +# (script, frameset) +# 3.0b13 - 2/8/2004 - MAP - better handling of empty HTML tags (br, hr, img, +# etc.) in embedded markup, in either HTML or XHTML form (
    ,
    ,
    ) +# 3.0b14 - 2/8/2004 - MAP - fixed CDATA handling in non-wellformed feeds under +# Python 2.1 +# 3.0b15 - 2/11/2004 - MAP - fixed bug resolving relative links in wfw:commentRSS; +# fixed bug capturing author and contributor URL; fixed bug resolving relative +# links in author and contributor URL; fixed bug resolvin relative links in +# generator URL; added support for recognizing RSS 1.0; passed Simon Fell's +# namespace tests, and included them permanently in the test suite with his +# permission; fixed namespace handling under Python 2.1 +# 3.0b16 - 2/12/2004 - MAP - fixed support for RSS 0.90 (broken in b15) +# 3.0b17 - 2/13/2004 - MAP - determine character encoding as per RFC 3023 +# 3.0b18 - 2/17/2004 - MAP - always map description to summary_detail (Andrei); +# use libxml2 (if available) +# 3.0b19 - 3/15/2004 - MAP - fixed bug exploding author information when author +# name was in parentheses; removed ultra-problematic mxTidy support; patch to +# workaround crash in PyXML/expat when encountering invalid entities +# (MarkMoraes); support for textinput/textInput +# 3.0b20 - 4/7/2004 - MAP - added CDF support +# 3.0b21 - 4/14/2004 - MAP - added Hot RSS support +# 3.0b22 - 4/19/2004 - MAP - changed 'channel' to 'feed', 'item' to 'entries' in +# results dict; changed results dict to allow getting values with results.key +# as well as results[key]; work around embedded illformed HTML with half +# a DOCTYPE; work around malformed Content-Type header; if character encoding +# is wrong, try several common ones before falling back to regexes (if this +# works, bozo_exception is set to CharacterEncodingOverride); fixed character +# encoding issues in BaseHTMLProcessor by tracking encoding and converting +# from Unicode to raw strings before feeding data to sgmllib.SGMLParser; +# convert each value in results to Unicode (if possible), even if using +# regex-based parsing +# 3.0b23 - 4/21/2004 - MAP - fixed UnicodeDecodeError for feeds that contain +# high-bit characters in attributes in embedded HTML in description (thanks +# Thijs van de Vossen); moved guid, date, and date_parsed to mapped keys in +# FeedParserDict; tweaked FeedParserDict.has_key to return True if asking +# about a mapped key +# 3.0fc1 - 4/23/2004 - MAP - made results.entries[0].links[0] and +# results.entries[0].enclosures[0] into FeedParserDict; fixed typo that could +# cause the same encoding to be tried twice (even if it failed the first time); +# fixed DOCTYPE stripping when DOCTYPE contained entity declarations; +# better textinput and image tracking in illformed RSS 1.0 feeds +# 3.0fc2 - 5/10/2004 - MAP - added and passed Sam's amp tests; added and passed +# my blink tag tests +# 3.0fc3 - 6/18/2004 - MAP - fixed bug in _changeEncodingDeclaration that +# failed to parse utf-16 encoded feeds; made source into a FeedParserDict; +# duplicate admin:generatorAgent/@rdf:resource in generator_detail.url; +# added support for image; refactored parse() fallback logic to try other +# encodings if SAX parsing fails (previously it would only try other encodings +# if re-encoding failed); remove unichr madness in normalize_attrs now that +# we're properly tracking encoding in and out of BaseHTMLProcessor; set +# feed.language from root-level xml:lang; set entry.id from rdf:about; +# send Accept header +# 3.0 - 6/21/2004 - MAP - don't try iso-8859-1 (can't distinguish between +# iso-8859-1 and windows-1252 anyway, and most incorrectly marked feeds are +# windows-1252); fixed regression that could cause the same encoding to be +# tried twice (even if it failed the first time) +# 3.0.1 - 6/22/2004 - MAP - default to us-ascii for all text/* content types; +# recover from malformed content-type header parameter with no equals sign +# ('text/xml; charset:iso-8859-1') +# 3.1 - 6/28/2004 - MAP - added and passed tests for converting HTML entities +# to Unicode equivalents in illformed feeds (aaronsw); added and +# passed tests for converting character entities to Unicode equivalents +# in illformed feeds (aaronsw); test for valid parsers when setting +# XML_AVAILABLE; make version and encoding available when server returns +# a 304; add handlers parameter to pass arbitrary urllib2 handlers (like +# digest auth or proxy support); add code to parse username/password +# out of url and send as basic authentication; expose downloading-related +# exceptions in bozo_exception (aaronsw); added __contains__ method to +# FeedParserDict (aaronsw); added publisher_detail (aaronsw) +# 3.2 - 7/3/2004 - MAP - use cjkcodecs and iconv_codec if available; always +# convert feed to UTF-8 before passing to XML parser; completely revamped +# logic for determining character encoding and attempting XML parsing +# (much faster); increased default timeout to 20 seconds; test for presence +# of Location header on redirects; added tests for many alternate character +# encodings; support various EBCDIC encodings; support UTF-16BE and +# UTF16-LE with or without a BOM; support UTF-8 with a BOM; support +# UTF-32BE and UTF-32LE with or without a BOM; fixed crashing bug if no +# XML parsers are available; added support for 'Content-encoding: deflate'; +# send blank 'Accept-encoding: ' header if neither gzip nor zlib modules +# are available +# 3.3 - 7/15/2004 - MAP - optimize EBCDIC to ASCII conversion; fix obscure +# problem tracking xml:base and xml:lang if element declares it, child +# doesn't, first grandchild redeclares it, and second grandchild doesn't; +# refactored date parsing; defined public registerDateHandler so callers +# can add support for additional date formats at runtime; added support +# for OnBlog, Nate, MSSQL, Greek, and Hungarian dates (ytrewq1); added +# zopeCompatibilityHack() which turns FeedParserDict into a regular +# dictionary, required for Zope compatibility, and also makes command- +# line debugging easier because pprint module formats real dictionaries +# better than dictionary-like objects; added NonXMLContentType exception, +# which is stored in bozo_exception when a feed is served with a non-XML +# media type such as 'text/plain'; respect Content-Language as default +# language if not xml:lang is present; cloud dict is now FeedParserDict; +# generator dict is now FeedParserDict; better tracking of xml:lang, +# including support for xml:lang='' to unset the current language; +# recognize RSS 1.0 feeds even when RSS 1.0 namespace is not the default +# namespace; don't overwrite final status on redirects (scenarios: +# redirecting to a URL that returns 304, redirecting to a URL that +# redirects to another URL with a different type of redirect); add +# support for HTTP 303 redirects +# 4.0 - MAP - support for relative URIs in xml:base attribute; fixed +# encoding issue with mxTidy (phopkins); preliminary support for RFC 3229; +# support for Atom 1.0; support for iTunes extensions; new 'tags' for +# categories/keywords/etc. as array of dict +# {'term': term, 'scheme': scheme, 'label': label} to match Atom 1.0 +# terminology; parse RFC 822-style dates with no time; lots of other +# bug fixes +# 4.1 - MAP - removed socket timeout; added support for chardet library + ADDED gluon/contrib/gae_retry.py Index: gluon/contrib/gae_retry.py ================================================================== --- gluon/contrib/gae_retry.py +++ gluon/contrib/gae_retry.py @@ -0,0 +1,87 @@ +def autoretry_datastore_timeouts(attempts=5.0, interval=0.1, exponent=2.0): + """ + Copyright (C) 2009 twitter.com/rcb + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + ====================================================================== + + This function wraps the AppEngine Datastore API to autoretry + datastore timeouts at the lowest accessible level. + + The benefits of this approach are: + + 1. Small Footprint: Does not monkey with Model internals + which may break in future releases. + 2. Max Performance: Retrying at this lowest level means + serialization and key formatting is not + needlessly repeated on each retry. + At initialization time, execute this: + + >>> autoretry_datastore_timeouts() + + Should only be called once, subsequent calls have no effect. + + >>> autoretry_datastore_timeouts() # no effect + + Default (5) attempts: .1, .2, .4, .8, 1.6 seconds + + Parameters can each be specified as floats. + + :param attempts: maximum number of times to retry. + :param interval: base seconds to sleep between retries. + :param exponent: rate of exponential back-off. + """ + + import time, logging + from google.appengine.api import apiproxy_stub_map + from google.appengine.runtime import apiproxy_errors + from google.appengine.datastore import datastore_pb + + attempts = float(attempts) + interval = float(interval) + exponent = float(exponent) + wrapped = apiproxy_stub_map.MakeSyncCall + errors = {datastore_pb.Error.TIMEOUT:'Timeout', + datastore_pb.Error.CONCURRENT_TRANSACTION:'TransactionFailedError'} + + def wrapper(*args, **kwargs): + count = 0.0 + while True: + try: + return wrapped(*args, **kwargs) + except apiproxy_errors.ApplicationError, err: + errno = err.application_error + if errno not in errors: raise + sleep = (exponent ** count) * interval + count += 1.0 + if count > attempts: raise + msg = "Datastore %s: retry #%d in %s seconds.\n%s" + vals = '' + if count == 1.0: + vals = '\n'.join([str(a) for a in args]) + logging.warning(msg % (errors[errno], count, sleep, vals)) + time.sleep(sleep) + + setattr(wrapper, '_autoretry_datastore_timeouts', False) + if getattr(wrapped, '_autoretry_datastore_timeouts', True): + apiproxy_stub_map.MakeSyncCall = wrapper + ADDED gluon/contrib/gateways/__init__.py Index: gluon/contrib/gateways/__init__.py ================================================================== --- gluon/contrib/gateways/__init__.py +++ gluon/contrib/gateways/__init__.py @@ -0,0 +1,1 @@ + ADDED gluon/contrib/gateways/fcgi.py Index: gluon/contrib/gateways/fcgi.py ================================================================== --- gluon/contrib/gateways/fcgi.py +++ gluon/contrib/gateways/fcgi.py @@ -0,0 +1,1331 @@ +# Copyright (c) 2002, 2003, 2005, 2006 Allan Saddi +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# $Id$ + +""" +fcgi - a FastCGI/WSGI gateway. + +For more information about FastCGI, see . + +For more information about the Web Server Gateway Interface, see +. + +Example usage: + + #!/usr/bin/env python + from myapplication import app # Assume app is your WSGI application object + from fcgi import WSGIServer + WSGIServer(app).run() + +See the documentation for WSGIServer/Server for more information. + +On most platforms, fcgi will fallback to regular CGI behavior if run in a +non-FastCGI context. If you want to force CGI behavior, set the environment +variable FCGI_FORCE_CGI to "Y" or "y". +""" + +__author__ = 'Allan Saddi ' +__version__ = '$Revision$' + +import sys +import os +import signal +import struct +import cStringIO as StringIO +import select +import socket +import errno +import traceback + +try: + import thread + import threading + thread_available = True +except ImportError: + import dummy_thread as thread + import dummy_threading as threading + thread_available = False + +# Apparently 2.3 doesn't define SHUT_WR? Assume it is 1 in this case. +if not hasattr(socket, 'SHUT_WR'): + socket.SHUT_WR = 1 + +__all__ = ['WSGIServer'] + +# Constants from the spec. +FCGI_LISTENSOCK_FILENO = 0 + +FCGI_HEADER_LEN = 8 + +FCGI_VERSION_1 = 1 + +FCGI_BEGIN_REQUEST = 1 +FCGI_ABORT_REQUEST = 2 +FCGI_END_REQUEST = 3 +FCGI_PARAMS = 4 +FCGI_STDIN = 5 +FCGI_STDOUT = 6 +FCGI_STDERR = 7 +FCGI_DATA = 8 +FCGI_GET_VALUES = 9 +FCGI_GET_VALUES_RESULT = 10 +FCGI_UNKNOWN_TYPE = 11 +FCGI_MAXTYPE = FCGI_UNKNOWN_TYPE + +FCGI_NULL_REQUEST_ID = 0 + +FCGI_KEEP_CONN = 1 + +FCGI_RESPONDER = 1 +FCGI_AUTHORIZER = 2 +FCGI_FILTER = 3 + +FCGI_REQUEST_COMPLETE = 0 +FCGI_CANT_MPX_CONN = 1 +FCGI_OVERLOADED = 2 +FCGI_UNKNOWN_ROLE = 3 + +FCGI_MAX_CONNS = 'FCGI_MAX_CONNS' +FCGI_MAX_REQS = 'FCGI_MAX_REQS' +FCGI_MPXS_CONNS = 'FCGI_MPXS_CONNS' + +FCGI_Header = '!BBHHBx' +FCGI_BeginRequestBody = '!HB5x' +FCGI_EndRequestBody = '!LB3x' +FCGI_UnknownTypeBody = '!B7x' + +FCGI_EndRequestBody_LEN = struct.calcsize(FCGI_EndRequestBody) +FCGI_UnknownTypeBody_LEN = struct.calcsize(FCGI_UnknownTypeBody) + +if __debug__: + import time + + # Set non-zero to write debug output to a file. + DEBUG = 0 + DEBUGLOG = '/tmp/fcgi.log' + + def _debug(level, msg): + if DEBUG < level: + return + + try: + f = open(DEBUGLOG, 'a') + f.write('%sfcgi: %s\n' % (time.ctime()[4:-4], msg)) + f.close() + except: + pass + +class InputStream(object): + """ + File-like object representing FastCGI input streams (FCGI_STDIN and + FCGI_DATA). Supports the minimum methods required by WSGI spec. + """ + def __init__(self, conn): + self._conn = conn + + # See Server. + self._shrinkThreshold = conn.server.inputStreamShrinkThreshold + + self._buf = '' + self._bufList = [] + self._pos = 0 # Current read position. + self._avail = 0 # Number of bytes currently available. + + self._eof = False # True when server has sent EOF notification. + + def _shrinkBuffer(self): + """Gets rid of already read data (since we can't rewind).""" + if self._pos >= self._shrinkThreshold: + self._buf = self._buf[self._pos:] + self._avail -= self._pos + self._pos = 0 + + assert self._avail >= 0 + + def _waitForData(self): + """Waits for more data to become available.""" + self._conn.process_input() + + def read(self, n=-1): + if self._pos == self._avail and self._eof: + return '' + while True: + if n < 0 or (self._avail - self._pos) < n: + # Not enough data available. + if self._eof: + # And there's no more coming. + newPos = self._avail + break + else: + # Wait for more data. + self._waitForData() + continue + else: + newPos = self._pos + n + break + # Merge buffer list, if necessary. + if self._bufList: + self._buf += ''.join(self._bufList) + self._bufList = [] + r = self._buf[self._pos:newPos] + self._pos = newPos + self._shrinkBuffer() + return r + + def readline(self, length=None): + if self._pos == self._avail and self._eof: + return '' + while True: + # Unfortunately, we need to merge the buffer list early. + if self._bufList: + self._buf += ''.join(self._bufList) + self._bufList = [] + # Find newline. + i = self._buf.find('\n', self._pos) + if i < 0: + # Not found? + if self._eof: + # No more data coming. + newPos = self._avail + break + else: + # Wait for more to come. + self._waitForData() + continue + else: + newPos = i + 1 + break + if length is not None: + if self._pos + length < newPos: + newPos = self._pos + length + r = self._buf[self._pos:newPos] + self._pos = newPos + self._shrinkBuffer() + return r + + def readlines(self, sizehint=0): + total = 0 + lines = [] + line = self.readline() + while line: + lines.append(line) + total += len(line) + if 0 < sizehint <= total: + break + line = self.readline() + return lines + + def __iter__(self): + return self + + def next(self): + r = self.readline() + if not r: + raise StopIteration + return r + + def add_data(self, data): + if not data: + self._eof = True + else: + self._bufList.append(data) + self._avail += len(data) + +class MultiplexedInputStream(InputStream): + """ + A version of InputStream meant to be used with MultiplexedConnections. + Assumes the MultiplexedConnection (the producer) and the Request + (the consumer) are running in different threads. + """ + def __init__(self, conn): + super(MultiplexedInputStream, self).__init__(conn) + + # Arbitrates access to this InputStream (it's used simultaneously + # by a Request and its owning Connection object). + lock = threading.RLock() + + # Notifies Request thread that there is new data available. + self._lock = threading.Condition(lock) + + def _waitForData(self): + # Wait for notification from add_data(). + self._lock.wait() + + def read(self, n=-1): + self._lock.acquire() + try: + return super(MultiplexedInputStream, self).read(n) + finally: + self._lock.release() + + def readline(self, length=None): + self._lock.acquire() + try: + return super(MultiplexedInputStream, self).readline(length) + finally: + self._lock.release() + + def add_data(self, data): + self._lock.acquire() + try: + super(MultiplexedInputStream, self).add_data(data) + self._lock.notify() + finally: + self._lock.release() + +class OutputStream(object): + """ + FastCGI output stream (FCGI_STDOUT/FCGI_STDERR). By default, calls to + write() or writelines() immediately result in Records being sent back + to the server. Buffering should be done in a higher level! + """ + def __init__(self, conn, req, type, buffered=False): + self._conn = conn + self._req = req + self._type = type + self._buffered = buffered + self._bufList = [] # Used if buffered is True + self.dataWritten = False + self.closed = False + + def _write(self, data): + length = len(data) + while length: + toWrite = min(length, self._req.server.maxwrite - FCGI_HEADER_LEN) + + rec = Record(self._type, self._req.requestId) + rec.contentLength = toWrite + rec.contentData = data[:toWrite] + self._conn.writeRecord(rec) + + data = data[toWrite:] + length -= toWrite + + def write(self, data): + assert not self.closed + + if not data: + return + + self.dataWritten = True + + if self._buffered: + self._bufList.append(data) + else: + self._write(data) + + def writelines(self, lines): + assert not self.closed + + for line in lines: + self.write(line) + + def flush(self): + # Only need to flush if this OutputStream is actually buffered. + if self._buffered: + data = ''.join(self._bufList) + self._bufList = [] + self._write(data) + + # Though available, the following should NOT be called by WSGI apps. + def close(self): + """Sends end-of-stream notification, if necessary.""" + if not self.closed and self.dataWritten: + self.flush() + rec = Record(self._type, self._req.requestId) + self._conn.writeRecord(rec) + self.closed = True + +class TeeOutputStream(object): + """ + Simple wrapper around two or more output file-like objects that copies + written data to all streams. + """ + def __init__(self, streamList): + self._streamList = streamList + + def write(self, data): + for f in self._streamList: + f.write(data) + + def writelines(self, lines): + for line in lines: + self.write(line) + + def flush(self): + for f in self._streamList: + f.flush() + +class StdoutWrapper(object): + """ + Wrapper for sys.stdout so we know if data has actually been written. + """ + def __init__(self, stdout): + self._file = stdout + self.dataWritten = False + + def write(self, data): + if data: + self.dataWritten = True + self._file.write(data) + + def writelines(self, lines): + for line in lines: + self.write(line) + + def __getattr__(self, name): + return getattr(self._file, name) + +def decode_pair(s, pos=0): + """ + Decodes a name/value pair. + + The number of bytes decoded as well as the name/value pair + are returned. + """ + nameLength = ord(s[pos]) + if nameLength & 128: + nameLength = struct.unpack('!L', s[pos:pos+4])[0] & 0x7fffffff + pos += 4 + else: + pos += 1 + + valueLength = ord(s[pos]) + if valueLength & 128: + valueLength = struct.unpack('!L', s[pos:pos+4])[0] & 0x7fffffff + pos += 4 + else: + pos += 1 + + name = s[pos:pos+nameLength] + pos += nameLength + value = s[pos:pos+valueLength] + pos += valueLength + + return (pos, (name, value)) + +def encode_pair(name, value): + """ + Encodes a name/value pair. + + The encoded string is returned. + """ + nameLength = len(name) + if nameLength < 128: + s = chr(nameLength) + else: + s = struct.pack('!L', nameLength | 0x80000000L) + + valueLength = len(value) + if valueLength < 128: + s += chr(valueLength) + else: + s += struct.pack('!L', valueLength | 0x80000000L) + + return s + name + value + +class Record(object): + """ + A FastCGI Record. + + Used for encoding/decoding records. + """ + def __init__(self, type=FCGI_UNKNOWN_TYPE, requestId=FCGI_NULL_REQUEST_ID): + self.version = FCGI_VERSION_1 + self.type = type + self.requestId = requestId + self.contentLength = 0 + self.paddingLength = 0 + self.contentData = '' + + def _recvall(sock, length): + """ + Attempts to receive length bytes from a socket, blocking if necessary. + (Socket may be blocking or non-blocking.) + """ + dataList = [] + recvLen = 0 + while length: + try: + data = sock.recv(length) + except socket.error, e: + if e[0] == errno.EAGAIN: + select.select([sock], [], []) + continue + else: + raise + if not data: # EOF + break + dataList.append(data) + dataLen = len(data) + recvLen += dataLen + length -= dataLen + return ''.join(dataList), recvLen + _recvall = staticmethod(_recvall) + + def read(self, sock): + """Read and decode a Record from a socket.""" + try: + header, length = self._recvall(sock, FCGI_HEADER_LEN) + except: + raise EOFError + + if length < FCGI_HEADER_LEN: + raise EOFError + + self.version, self.type, self.requestId, self.contentLength, \ + self.paddingLength = struct.unpack(FCGI_Header, header) + + if __debug__: _debug(9, 'read: fd = %d, type = %d, requestId = %d, ' + 'contentLength = %d' % + (sock.fileno(), self.type, self.requestId, + self.contentLength)) + + if self.contentLength: + try: + self.contentData, length = self._recvall(sock, + self.contentLength) + except: + raise EOFError + + if length < self.contentLength: + raise EOFError + + if self.paddingLength: + try: + self._recvall(sock, self.paddingLength) + except: + raise EOFError + + def _sendall(sock, data): + """ + Writes data to a socket and does not return until all the data is sent. + """ + length = len(data) + while length: + try: + sent = sock.send(data) + except socket.error, e: + if e[0] == errno.EAGAIN: + select.select([], [sock], []) + continue + else: + raise + data = data[sent:] + length -= sent + _sendall = staticmethod(_sendall) + + def write(self, sock): + """Encode and write a Record to a socket.""" + self.paddingLength = -self.contentLength & 7 + + if __debug__: _debug(9, 'write: fd = %d, type = %d, requestId = %d, ' + 'contentLength = %d' % + (sock.fileno(), self.type, self.requestId, + self.contentLength)) + + header = struct.pack(FCGI_Header, self.version, self.type, + self.requestId, self.contentLength, + self.paddingLength) + self._sendall(sock, header) + if self.contentLength: + self._sendall(sock, self.contentData) + if self.paddingLength: + self._sendall(sock, '\x00'*self.paddingLength) + +class Request(object): + """ + Represents a single FastCGI request. + + These objects are passed to your handler and is the main interface + between your handler and the fcgi module. The methods should not + be called by your handler. However, server, params, stdin, stdout, + stderr, and data are free for your handler's use. + """ + def __init__(self, conn, inputStreamClass): + self._conn = conn + + self.server = conn.server + self.params = {} + self.stdin = inputStreamClass(conn) + self.stdout = OutputStream(conn, self, FCGI_STDOUT) + self.stderr = OutputStream(conn, self, FCGI_STDERR, buffered=True) + self.data = inputStreamClass(conn) + + def run(self): + """Runs the handler, flushes the streams, and ends the request.""" + try: + protocolStatus, appStatus = self.server.handler(self) + except: + traceback.print_exc(file=self.stderr) + self.stderr.flush() + if not self.stdout.dataWritten: + self.server.error(self) + + protocolStatus, appStatus = FCGI_REQUEST_COMPLETE, 0 + + if __debug__: _debug(1, 'protocolStatus = %d, appStatus = %d' % + (protocolStatus, appStatus)) + + self._flush() + self._end(appStatus, protocolStatus) + + def _end(self, appStatus=0L, protocolStatus=FCGI_REQUEST_COMPLETE): + self._conn.end_request(self, appStatus, protocolStatus) + + def _flush(self): + self.stdout.close() + self.stderr.close() + +class CGIRequest(Request): + """A normal CGI request disguised as a FastCGI request.""" + def __init__(self, server): + # These are normally filled in by Connection. + self.requestId = 1 + self.role = FCGI_RESPONDER + self.flags = 0 + self.aborted = False + + self.server = server + self.params = dict(os.environ) + self.stdin = sys.stdin + self.stdout = StdoutWrapper(sys.stdout) # Oh, the humanity! + self.stderr = sys.stderr + self.data = StringIO.StringIO() + + def _end(self, appStatus=0L, protocolStatus=FCGI_REQUEST_COMPLETE): + sys.exit(appStatus) + + def _flush(self): + # Not buffered, do nothing. + pass + +class Connection(object): + """ + A Connection with the web server. + + Each Connection is associated with a single socket (which is + connected to the web server) and is responsible for handling all + the FastCGI message processing for that socket. + """ + _multiplexed = False + _inputStreamClass = InputStream + + def __init__(self, sock, addr, server): + self._sock = sock + self._addr = addr + self.server = server + + # Active Requests for this Connection, mapped by request ID. + self._requests = {} + + def _cleanupSocket(self): + """Close the Connection's socket.""" + try: + self._sock.shutdown(socket.SHUT_WR) + except: + return + try: + while True: + r, w, e = select.select([self._sock], [], []) + if not r or not self._sock.recv(1024): + break + except: + pass + self._sock.close() + + def run(self): + """Begin processing data from the socket.""" + self._keepGoing = True + while self._keepGoing: + try: + self.process_input() + except EOFError: + break + except (select.error, socket.error), e: + if e[0] == errno.EBADF: # Socket was closed by Request. + break + raise + + self._cleanupSocket() + + def process_input(self): + """Attempt to read a single Record from the socket and process it.""" + # Currently, any children Request threads notify this Connection + # that it is no longer needed by closing the Connection's socket. + # We need to put a timeout on select, otherwise we might get + # stuck in it indefinitely... (I don't like this solution.) + while self._keepGoing: + try: + r, w, e = select.select([self._sock], [], [], 1.0) + except ValueError: + # Sigh. ValueError gets thrown sometimes when passing select + # a closed socket. + raise EOFError + if r: break + if not self._keepGoing: + return + rec = Record() + rec.read(self._sock) + + if rec.type == FCGI_GET_VALUES: + self._do_get_values(rec) + elif rec.type == FCGI_BEGIN_REQUEST: + self._do_begin_request(rec) + elif rec.type == FCGI_ABORT_REQUEST: + self._do_abort_request(rec) + elif rec.type == FCGI_PARAMS: + self._do_params(rec) + elif rec.type == FCGI_STDIN: + self._do_stdin(rec) + elif rec.type == FCGI_DATA: + self._do_data(rec) + elif rec.requestId == FCGI_NULL_REQUEST_ID: + self._do_unknown_type(rec) + else: + # Need to complain about this. + pass + + def writeRecord(self, rec): + """ + Write a Record to the socket. + """ + rec.write(self._sock) + + def end_request(self, req, appStatus=0L, + protocolStatus=FCGI_REQUEST_COMPLETE, remove=True): + """ + End a Request. + + Called by Request objects. An FCGI_END_REQUEST Record is + sent to the web server. If the web server no longer requires + the connection, the socket is closed, thereby ending this + Connection (run() returns). + """ + rec = Record(FCGI_END_REQUEST, req.requestId) + rec.contentData = struct.pack(FCGI_EndRequestBody, appStatus, + protocolStatus) + rec.contentLength = FCGI_EndRequestBody_LEN + self.writeRecord(rec) + + if remove: + del self._requests[req.requestId] + + if __debug__: _debug(2, 'end_request: flags = %d' % req.flags) + + if not (req.flags & FCGI_KEEP_CONN) and not self._requests: + self._cleanupSocket() + self._keepGoing = False + + def _do_get_values(self, inrec): + """Handle an FCGI_GET_VALUES request from the web server.""" + outrec = Record(FCGI_GET_VALUES_RESULT) + + pos = 0 + while pos < inrec.contentLength: + pos, (name, value) = decode_pair(inrec.contentData, pos) + cap = self.server.capability.get(name) + if cap is not None: + outrec.contentData += encode_pair(name, str(cap)) + + outrec.contentLength = len(outrec.contentData) + self.writeRecord(outrec) + + def _do_begin_request(self, inrec): + """Handle an FCGI_BEGIN_REQUEST from the web server.""" + role, flags = struct.unpack(FCGI_BeginRequestBody, inrec.contentData) + + req = self.server.request_class(self, self._inputStreamClass) + req.requestId, req.role, req.flags = inrec.requestId, role, flags + req.aborted = False + + if not self._multiplexed and self._requests: + # Can't multiplex requests. + self.end_request(req, 0L, FCGI_CANT_MPX_CONN, remove=False) + else: + self._requests[inrec.requestId] = req + + def _do_abort_request(self, inrec): + """ + Handle an FCGI_ABORT_REQUEST from the web server. + + We just mark a flag in the associated Request. + """ + req = self._requests.get(inrec.requestId) + if req is not None: + req.aborted = True + + def _start_request(self, req): + """Run the request.""" + # Not multiplexed, so run it inline. + req.run() + + def _do_params(self, inrec): + """ + Handle an FCGI_PARAMS Record. + + If the last FCGI_PARAMS Record is received, start the request. + """ + req = self._requests.get(inrec.requestId) + if req is not None: + if inrec.contentLength: + pos = 0 + while pos < inrec.contentLength: + pos, (name, value) = decode_pair(inrec.contentData, pos) + req.params[name] = value + else: + self._start_request(req) + + def _do_stdin(self, inrec): + """Handle the FCGI_STDIN stream.""" + req = self._requests.get(inrec.requestId) + if req is not None: + req.stdin.add_data(inrec.contentData) + + def _do_data(self, inrec): + """Handle the FCGI_DATA stream.""" + req = self._requests.get(inrec.requestId) + if req is not None: + req.data.add_data(inrec.contentData) + + def _do_unknown_type(self, inrec): + """Handle an unknown request type. Respond accordingly.""" + outrec = Record(FCGI_UNKNOWN_TYPE) + outrec.contentData = struct.pack(FCGI_UnknownTypeBody, inrec.type) + outrec.contentLength = FCGI_UnknownTypeBody_LEN + self.writeRecord(rec) + +class MultiplexedConnection(Connection): + """ + A version of Connection capable of handling multiple requests + simultaneously. + """ + _multiplexed = True + _inputStreamClass = MultiplexedInputStream + + def __init__(self, sock, addr, server): + super(MultiplexedConnection, self).__init__(sock, addr, server) + + # Used to arbitrate access to self._requests. + lock = threading.RLock() + + # Notification is posted everytime a request completes, allowing us + # to quit cleanly. + self._lock = threading.Condition(lock) + + def _cleanupSocket(self): + # Wait for any outstanding requests before closing the socket. + self._lock.acquire() + while self._requests: + self._lock.wait() + self._lock.release() + + super(MultiplexedConnection, self)._cleanupSocket() + + def writeRecord(self, rec): + # Must use locking to prevent intermingling of Records from different + # threads. + self._lock.acquire() + try: + # Probably faster than calling super. ;) + rec.write(self._sock) + finally: + self._lock.release() + + def end_request(self, req, appStatus=0L, + protocolStatus=FCGI_REQUEST_COMPLETE, remove=True): + self._lock.acquire() + try: + super(MultiplexedConnection, self).end_request(req, appStatus, + protocolStatus, + remove) + self._lock.notify() + finally: + self._lock.release() + + def _do_begin_request(self, inrec): + self._lock.acquire() + try: + super(MultiplexedConnection, self)._do_begin_request(inrec) + finally: + self._lock.release() + + def _do_abort_request(self, inrec): + self._lock.acquire() + try: + super(MultiplexedConnection, self)._do_abort_request(inrec) + finally: + self._lock.release() + + def _start_request(self, req): + thread.start_new_thread(req.run, ()) + + def _do_params(self, inrec): + self._lock.acquire() + try: + super(MultiplexedConnection, self)._do_params(inrec) + finally: + self._lock.release() + + def _do_stdin(self, inrec): + self._lock.acquire() + try: + super(MultiplexedConnection, self)._do_stdin(inrec) + finally: + self._lock.release() + + def _do_data(self, inrec): + self._lock.acquire() + try: + super(MultiplexedConnection, self)._do_data(inrec) + finally: + self._lock.release() + +class Server(object): + """ + The FastCGI server. + + Waits for connections from the web server, processing each + request. + + If run in a normal CGI context, it will instead instantiate a + CGIRequest and run the handler through there. + """ + request_class = Request + cgirequest_class = CGIRequest + + # Limits the size of the InputStream's string buffer to this size + the + # server's maximum Record size. Since the InputStream is not seekable, + # we throw away already-read data once this certain amount has been read. + inputStreamShrinkThreshold = 102400 - 8192 + + def __init__(self, handler=None, maxwrite=8192, bindAddress=None, + umask=None, multiplexed=False): + """ + handler, if present, must reference a function or method that + takes one argument: a Request object. If handler is not + specified at creation time, Server *must* be subclassed. + (The handler method below is abstract.) + + maxwrite is the maximum number of bytes (per Record) to write + to the server. I've noticed mod_fastcgi has a relatively small + receive buffer (8K or so). + + bindAddress, if present, must either be a string or a 2-tuple. If + present, run() will open its own listening socket. You would use + this if you wanted to run your application as an 'external' FastCGI + app. (i.e. the webserver would no longer be responsible for starting + your app) If a string, it will be interpreted as a filename and a UNIX + socket will be opened. If a tuple, the first element, a string, + is the interface name/IP to bind to, and the second element (an int) + is the port number. + + Set multiplexed to True if you want to handle multiple requests + per connection. Some FastCGI backends (namely mod_fastcgi) don't + multiplex requests at all, so by default this is off (which saves + on thread creation/locking overhead). If threads aren't available, + this keyword is ignored; it's not possible to multiplex requests + at all. + """ + if handler is not None: + self.handler = handler + self.maxwrite = maxwrite + if thread_available: + try: + import resource + # Attempt to glean the maximum number of connections + # from the OS. + maxConns = resource.getrlimit(resource.RLIMIT_NOFILE)[0] + except ImportError: + maxConns = 100 # Just some made up number. + maxReqs = maxConns + if multiplexed: + self._connectionClass = MultiplexedConnection + maxReqs *= 5 # Another made up number. + else: + self._connectionClass = Connection + self.capability = { + FCGI_MAX_CONNS: maxConns, + FCGI_MAX_REQS: maxReqs, + FCGI_MPXS_CONNS: multiplexed and 1 or 0 + } + else: + self._connectionClass = Connection + self.capability = { + # If threads aren't available, these are pretty much correct. + FCGI_MAX_CONNS: 1, + FCGI_MAX_REQS: 1, + FCGI_MPXS_CONNS: 0 + } + self._bindAddress = bindAddress + self._umask = umask + + def _setupSocket(self): + if self._bindAddress is None: # Run as a normal FastCGI? + isFCGI = True + + sock = socket.fromfd(FCGI_LISTENSOCK_FILENO, socket.AF_INET, + socket.SOCK_STREAM) + try: + sock.getpeername() + except socket.error, e: + if e[0] == errno.ENOTSOCK: + # Not a socket, assume CGI context. + isFCGI = False + elif e[0] != errno.ENOTCONN: + raise + + # FastCGI/CGI discrimination is broken on Mac OS X. + # Set the environment variable FCGI_FORCE_CGI to "Y" or "y" + # if you want to run your app as a simple CGI. (You can do + # this with Apache's mod_env [not loaded by default in OS X + # client, ha ha] and the SetEnv directive.) + if not isFCGI or \ + os.environ.get('FCGI_FORCE_CGI', 'N').upper().startswith('Y'): + req = self.cgirequest_class(self) + req.run() + sys.exit(0) + else: + # Run as a server + oldUmask = None + if type(self._bindAddress) is str: + # Unix socket + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + try: + os.unlink(self._bindAddress) + except OSError: + pass + if self._umask is not None: + oldUmask = os.umask(self._umask) + else: + # INET socket + assert type(self._bindAddress) is tuple + assert len(self._bindAddress) == 2 + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + + sock.bind(self._bindAddress) + sock.listen(socket.SOMAXCONN) + + if oldUmask is not None: + os.umask(oldUmask) + + return sock + + def _cleanupSocket(self, sock): + """Closes the main socket.""" + sock.close() + + def _installSignalHandlers(self): + self._oldSIGs = [(x,signal.getsignal(x)) for x in + (signal.SIGHUP, signal.SIGINT, signal.SIGTERM)] + signal.signal(signal.SIGHUP, self._hupHandler) + signal.signal(signal.SIGINT, self._intHandler) + signal.signal(signal.SIGTERM, self._intHandler) + + def _restoreSignalHandlers(self): + for signum,handler in self._oldSIGs: + signal.signal(signum, handler) + + def _hupHandler(self, signum, frame): + self._hupReceived = True + self._keepGoing = False + + def _intHandler(self, signum, frame): + self._keepGoing = False + + def run(self, timeout=1.0): + """ + The main loop. Exits on SIGHUP, SIGINT, SIGTERM. Returns True if + SIGHUP was received, False otherwise. + """ + web_server_addrs = os.environ.get('FCGI_WEB_SERVER_ADDRS') + if web_server_addrs is not None: + web_server_addrs = map(lambda x: x.strip(), + web_server_addrs.split(',')) + + sock = self._setupSocket() + + self._keepGoing = True + self._hupReceived = False + + # Install signal handlers. + self._installSignalHandlers() + + while self._keepGoing: + try: + r, w, e = select.select([sock], [], [], timeout) + except select.error, e: + if e[0] == errno.EINTR: + continue + raise + + if r: + try: + clientSock, addr = sock.accept() + except socket.error, e: + if e[0] in (errno.EINTR, errno.EAGAIN): + continue + raise + + if web_server_addrs and \ + (len(addr) != 2 or addr[0] not in web_server_addrs): + clientSock.close() + continue + + # Instantiate a new Connection and begin processing FastCGI + # messages (either in a new thread or this thread). + conn = self._connectionClass(clientSock, addr, self) + thread.start_new_thread(conn.run, ()) + + self._mainloopPeriodic() + + # Restore signal handlers. + self._restoreSignalHandlers() + + self._cleanupSocket(sock) + + return self._hupReceived + + def _mainloopPeriodic(self): + """ + Called with just about each iteration of the main loop. Meant to + be overridden. + """ + pass + + def _exit(self, reload=False): + """ + Protected convenience method for subclasses to force an exit. Not + really thread-safe, which is why it isn't public. + """ + if self._keepGoing: + self._keepGoing = False + self._hupReceived = reload + + def handler(self, req): + """ + Default handler, which just raises an exception. Unless a handler + is passed at initialization time, this must be implemented by + a subclass. + """ + raise NotImplementedError, self.__class__.__name__ + '.handler' + + def error(self, req): + """ + Called by Request if an exception occurs within the handler. May and + should be overridden. + """ + import cgitb + req.stdout.write('Content-Type: text/html\r\n\r\n' + + cgitb.html(sys.exc_info())) + +class WSGIServer(Server): + """ + FastCGI server that supports the Web Server Gateway Interface. See + . + """ + def __init__(self, application, environ=None, + multithreaded=True, **kw): + """ + environ, if present, must be a dictionary-like object. Its + contents will be copied into application's environ. Useful + for passing application-specific variables. + + Set multithreaded to False if your application is not MT-safe. + """ + if kw.has_key('handler'): + del kw['handler'] # Doesn't make sense to let this through + super(WSGIServer, self).__init__(**kw) + + if environ is None: + environ = {} + + self.application = application + self.environ = environ + self.multithreaded = multithreaded + + # Used to force single-threadedness + self._app_lock = thread.allocate_lock() + + def handler(self, req): + """Special handler for WSGI.""" + if req.role != FCGI_RESPONDER: + return FCGI_UNKNOWN_ROLE, 0 + + # Mostly taken from example CGI gateway. + environ = req.params + environ.update(self.environ) + + environ['wsgi.version'] = (1,0) + environ['wsgi.input'] = req.stdin + if self._bindAddress is None: + stderr = req.stderr + else: + stderr = TeeOutputStream((sys.stderr, req.stderr)) + environ['wsgi.errors'] = stderr + environ['wsgi.multithread'] = not isinstance(req, CGIRequest) and \ + thread_available and self.multithreaded + # Rationale for the following: If started by the web server + # (self._bindAddress is None) in either FastCGI or CGI mode, the + # possibility of being spawned multiple times simultaneously is quite + # real. And, if started as an external server, multiple copies may be + # spawned for load-balancing/redundancy. (Though I don't think + # mod_fastcgi supports this?) + environ['wsgi.multiprocess'] = True + environ['wsgi.run_once'] = isinstance(req, CGIRequest) + + if environ.get('HTTPS', 'off') in ('on', '1'): + environ['wsgi.url_scheme'] = 'https' + else: + environ['wsgi.url_scheme'] = 'http' + + self._sanitizeEnv(environ) + + headers_set = [] + headers_sent = [] + result = None + + def write(data): + assert type(data) is str, 'write() argument must be string' + assert headers_set, 'write() before start_response()' + + if not headers_sent: + status, responseHeaders = headers_sent[:] = headers_set + found = False + for header,value in responseHeaders: + if header.lower() == 'content-length': + found = True + break + if not found and result is not None: + try: + if len(result) == 1: + responseHeaders.append(('Content-Length', + str(len(data)))) + except: + pass + s = 'Status: %s\r\n' % status + for header in responseHeaders: + s += '%s: %s\r\n' % header + s += '\r\n' + req.stdout.write(s) + + req.stdout.write(data) + req.stdout.flush() + + def start_response(status, response_headers, exc_info=None): + if exc_info: + try: + if headers_sent: + # Re-raise if too late + raise exc_info[0], exc_info[1], exc_info[2] + finally: + exc_info = None # avoid dangling circular ref + else: + assert not headers_set, 'Headers already set!' + + assert type(status) is str, 'Status must be a string' + assert len(status) >= 4, 'Status must be at least 4 characters' + assert int(status[:3]), 'Status must begin with 3-digit code' + assert status[3] == ' ', 'Status must have a space after code' + assert type(response_headers) is list, 'Headers must be a list' + if __debug__: + for name,val in response_headers: + assert type(name) is str, 'Header names must be strings' + assert type(val) is str, 'Header values must be strings' + + headers_set[:] = [status, response_headers] + return write + + if not self.multithreaded: + self._app_lock.acquire() + try: + try: + result = self.application(environ, start_response) + try: + for data in result: + if data: + write(data) + if not headers_sent: + write('') # in case body was empty + finally: + if hasattr(result, 'close'): + result.close() + except socket.error, e: + if e[0] != errno.EPIPE: + raise # Don't let EPIPE propagate beyond server + finally: + if not self.multithreaded: + self._app_lock.release() + + return FCGI_REQUEST_COMPLETE, 0 + + def _sanitizeEnv(self, environ): + """Ensure certain values are present, if required by WSGI.""" + if not environ.has_key('SCRIPT_NAME'): + environ['SCRIPT_NAME'] = '' + if not environ.has_key('PATH_INFO'): + environ['PATH_INFO'] = '' + + # If any of these are missing, it probably signifies a broken + # server... + for name,default in [('REQUEST_METHOD', 'GET'), + ('SERVER_NAME', 'localhost'), + ('SERVER_PORT', '80'), + ('SERVER_PROTOCOL', 'HTTP/1.0')]: + if not environ.has_key(name): + environ['wsgi.errors'].write('%s: missing FastCGI param %s ' + 'required by WSGI!\n' % + (self.__class__.__name__, name)) + environ[name] = default + +if __name__ == '__main__': + def test_app(environ, start_response): + """Probably not the most efficient example.""" + import cgi + start_response('200 OK', [('Content-Type', 'text/html')]) + yield 'Hello World!\n' \ + '\n' \ + '

    Hello World!

    \n' \ + '

    ' + names = environ.keys() + names.sort() + for name in names: + yield '\n' % ( + name, cgi.escape(`environ[name]`)) + + form = cgi.FieldStorage(fp=environ['wsgi.input'], environ=environ, + keep_blank_values=1) + if form.list: + yield '' + + for field in form.list: + yield '\n' % ( + field.name, field.value) + + yield '
    %s%s
    Form data
    %s%s
    \n' \ + '\n' + + WSGIServer(test_app).run() ADDED gluon/contrib/generics.py Index: gluon/contrib/generics.py ================================================================== --- gluon/contrib/generics.py +++ gluon/contrib/generics.py @@ -0,0 +1,64 @@ +# fix response + +import re +import os +import cPickle +import gluon.serializers +from gluon import current +from gluon.html import markmin_serializer, TAG, HTML, BODY, UL, XML +from gluon.contenttype import contenttype +from gluon.contrib.pyfpdf import FPDF, HTMLMixin +from gluon.sanitizer import sanitize +from gluon.contrib.markmin.markmin2latex import markmin2latex +from gluon.contrib.markmin.markmin2pdf import markmin2pdf + +def wrapper(f): + def g(data): + try: + output = f(data) + except (TypeError, ValueError): + raise HTTP(405, '%s serialization error' % extension.upper()) + except ImportError: + raise HTTP(405, '%s not available' % extension.upper()) + except: + raise HTTP(405, '%s error' % extension.upper()) + return XML(ouput) + return g + +def latex_from_html(html): + markmin=TAG(html).element('body').flatten(markmin_serializer) + return XML(markmin2latex(markmin)) + +def pdflatex_from_html(html): + if os.system('which pdflatex > /dev/null')==0: + markmin=TAG(html).element('body').flatten(markmin_serializer) + out,warning,errors=markmin2pdf(markmin) + if errors: + current.response.headers['Content-Type']='text/html' + raise HTTP(405,HTML(BODY(H1('errors'), + LU(*errors), + H1('warnings'), + LU(*warnings))).xml()) + else: + return XML(out) + +def pyfpdf_from_html(html): + request = current.request + def image_map(path): + if path.startswith('/%s/static/' % request.application): + return os.path.join(request.folder,path.split('/',2)[2]) + return 'http%s://%s%s' % (request.is_https and 's' or '',request.env.http_host, path) + class MyFPDF(FPDF, HTMLMixin): pass + pdf=MyFPDF() + pdf.add_page() + html = sanitize(html, escape=False) #### should have better list of allowed tags + pdf.write_html(html,image_map=image_map) + return XML(pdf.output(dest='S')) + +def pdf_from_html(html): + # try use latex and pdflatex + if os.system('which pdflatex > /dev/null')==0: + return pdflatex_from_html(html) + else: + return pyfpdf_from_html(html) + ADDED gluon/contrib/gql.py Index: gluon/contrib/gql.py ================================================================== --- gluon/contrib/gql.py +++ gluon/contrib/gql.py @@ -0,0 +1,6 @@ +# this file exists for backward compatibility + +__all__ = ['DAL','Field','drivers','gae'] + +from gluon.dal import DAL, Field, Table, Query, Set, Expression, Row, Rows, drivers, BaseAdapter, SQLField, SQLTable, SQLXorable, SQLQuery, SQLSet, SQLRows, SQLStorage, SQLDB, GQLDB, SQLALL, SQLCustomType, gae + ADDED gluon/contrib/login_methods/__init__.py Index: gluon/contrib/login_methods/__init__.py ================================================================== --- gluon/contrib/login_methods/__init__.py +++ gluon/contrib/login_methods/__init__.py @@ -0,0 +1,1 @@ + ADDED gluon/contrib/login_methods/basic_auth.py Index: gluon/contrib/login_methods/basic_auth.py ================================================================== --- gluon/contrib/login_methods/basic_auth.py +++ gluon/contrib/login_methods/basic_auth.py @@ -0,0 +1,24 @@ +import urllib +import urllib2 +import base64 + + +def basic_auth(server="http://127.0.0.1"): + """ + to use basic login with a different server + from gluon.contrib.login_methods.basic_auth import basic_auth + auth.settings.login_methods.append(basic_auth('http://server')) + """ + + def basic_login_aux(username, + password, + server=server): + key = base64.b64encode(username+':'+password) + headers = {'Authorization': 'Basic ' + key} + request = urllib2.Request(server, None, headers) + try: + urllib2.urlopen(request) + return True + except (urllib2.URLError, urllib2.HTTPError): + return False + return basic_login_aux ADDED gluon/contrib/login_methods/cas_auth.py Index: gluon/contrib/login_methods/cas_auth.py ================================================================== --- gluon/contrib/login_methods/cas_auth.py +++ gluon/contrib/login_methods/cas_auth.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +This file is part of web2py Web Framework (Copyrighted, 2007-2009). +Developed by Massimo Di Pierro . +License: GPL v2 + +Tinkered by Szabolcs Gyuris < szimszo n @ o regpreshaz dot eu> +""" + +from gluon import current, redirect + +class CasAuth( object ): + """ + Login will be done via Web2py's CAS application, instead of web2py's + login form. + + Include in your model (eg db.py):: + + from gluon.contrib.login_methods.cas_auth import CasAuth + auth.define_tables(username=True) + auth.settings.login_form=CasAuth( + urlbase = "https://[your CAS provider]/app/default/user/cas", + actions=['login','validate','logout']) + + where urlbase is the actual CAS server url without the login,logout... + Enjoy. + + ###UPDATE### + if you want to connect to a CAS version 2 JASIG Server use this: + auth.settings.login_form=CasAuth( + urlbase = "https://[Your CAS server]/cas", + actions = ['login','serviceValidate','logout'], + casversion = 2, + casusername = "cas:user") + + where casusername is the xml node returned by CAS server which contains + user's username. + + """ + def __init__(self, g=None, ### g for backward compatibility ### + urlbase = "https://web2py.com/cas/cas", + actions=['login','check','logout'], + maps=dict(username=lambda v:v.get('username',v['user']), + email=lambda v:v.get('email',None), + user_id=lambda v:v['user']), + casversion = 1, + casusername = 'cas:user' + ): + self.urlbase=urlbase + self.cas_login_url="%s/%s"%(self.urlbase,actions[0]) + self.cas_check_url="%s/%s"%(self.urlbase,actions[1]) + self.cas_logout_url="%s/%s"%(self.urlbase,actions[2]) + self.maps=maps + self.casversion = casversion + self.casusername = casusername + http_host=current.request.env.http_x_forwarded_host + if not http_host: http_host=current.request.env.http_host + if current.request.env.wsgi_url_scheme in [ 'https', 'HTTPS' ]: + scheme = 'https' + else: + scheme = 'http' + self.cas_my_url='%s://%s%s'%( scheme, http_host, current.request.env.path_info ) + + def login_url( self, next = "/" ): + current.session.token=self._CAS_login() + return next + def logout_url( self, next = "/" ): + current.session.token=None + current.session.auth=None + self._CAS_logout() + return next + def get_user( self ): + user=current.session.token + if user: + d = {'source':'web2py cas'} + for key in self.maps: + d[key]=self.maps[key](user) + return d + return None + def _CAS_login( self ): + """ + exposed as CAS.login(request) + returns a token on success, None on failed authentication + """ + import urllib + self.ticket=current.request.vars.ticket + if not current.request.vars.ticket: + redirect( "%s?service=%s"% (self.cas_login_url, + self.cas_my_url)) + else: + url="%s?service=%s&ticket=%s" % (self.cas_check_url, + self.cas_my_url, + self.ticket ) + data=urllib.urlopen( url ).read() + if data.startswith('yes') or data.startswith('no'): + data = data.split('\n') + if data[0]=='yes': + a,b,c = data[1].split( ':' )+[None,None] + return dict(user=a,email=b,username=c) + return None + import xml.dom.minidom as dom + import xml.parsers.expat as expat + try: + dxml=dom.parseString(data) + envelop = dxml.getElementsByTagName("cas:authenticationSuccess") + if len(envelop)>0: + res = dict() + for x in envelop[0].childNodes: + if x.nodeName.startswith('cas:') and len(x.childNodes): + key = x.nodeName[4:].encode('utf8') + value = x.childNodes[0].nodeValue.encode('utf8') + if not key in res: + res[key]=value + else: + if not isinstance(res[key],list): + res[key]=[res[key]] + res[key].append(value) + return res + except expat.ExpatError: pass + return None # fallback + + + def _CAS_logout( self ): + """ + exposed CAS.logout() + redirects to the CAS logout page + """ + import urllib + redirect("%s?service=%s" % (self.cas_logout_url,self.cas_my_url)) ADDED gluon/contrib/login_methods/email_auth.py Index: gluon/contrib/login_methods/email_auth.py ================================================================== --- gluon/contrib/login_methods/email_auth.py +++ gluon/contrib/login_methods/email_auth.py @@ -0,0 +1,36 @@ +import smtplib + + +def email_auth(server="smtp.gmail.com:587", + domain="@gmail.com"): + """ + to use email_login: + from gluon.contrib.login_methods.email_auth import email_auth + auth.settings.login_methods.append(email_auth("smtp.gmail.com:587", + "@gmail.com")) + """ + + def email_auth_aux(email, + password, + server=server, + domain=domain): + if domain: + if not isinstance(domain,(list,tuple)): + domain=[str(domain)] + if not [d for d in domain if email[-len(d):]==d]: + return False + (host, port) = server.split(':') + try: + server = None + server = smtplib.SMTP(host, port) + server.ehlo() + server.starttls() + server.ehlo() + server.login(email, password) + server.quit() + return True + except: + if server: + server.quit() + return False + return email_auth_aux ADDED gluon/contrib/login_methods/extended_login_form.py Index: gluon/contrib/login_methods/extended_login_form.py ================================================================== --- gluon/contrib/login_methods/extended_login_form.py +++ gluon/contrib/login_methods/extended_login_form.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python +# coding: utf8 + +""" +ExtendedLoginForm is used to extend normal login form in web2py with one more login method. +So user can choose the built-in login or extended login methods. +""" + +from gluon.html import DIV + +class ExtendedLoginForm(object): + """ + Put extended_login_form under web2py/gluon/contrib/login_methods folder. + Then inside your model where defines the auth: + + auth = Auth(globals(),db) # authentication/authorization + ... + auth.define_tables() # You might like to put the code after auth.define_tables + ... # if the alt_login_form deals with tables of auth. + + alt_login_form = RPXAccount(request, + api_key="...", + domain="...", + url = "http://localhost:8000/%s/default/user/login" % request.application) + extended_login_form = ExtendedLoginForm(auth, alt_login_form, signals=['token']) + + auth.settings.login_form = extended_login_form + + Note: + Since rpx_account doesn't create the password for the user, you + might need to provide a way for user to create password to do + normal login. + + """ + + def __init__(self, + auth, + alt_login_form, + signals=[], + login_arg = 'login' + ): + self.auth = auth + self.alt_login_form = alt_login_form + self.signals = signals + self.login_arg = login_arg + + def get_user(self): + """ + Delegate the get_user to alt_login_form.get_user. + """ + if hasattr(self.alt_login_form, 'get_user'): + return self.alt_login_form.get_user() + return None # let gluon.tools.Auth.get_or_create_user do the rest + + def login_url(self, next): + """ + Optional implement for alt_login_form. + + In normal case, this should be replaced by get_user, and never get called. + """ + if hasattr(self.alt_login_form, 'login_url'): + return self.alt_login_form.login_url(next) + return self.auth.settings.login_url + + def logout_url(self, next): + """ + Optional implement for alt_login_form. + + Called if bool(alt_login_form.get_user) is True. + + If alt_login_form implemented logout_url function, it will return that function call. + """ + if hasattr(self.alt_login_form, 'logout_url'): + return self.alt_login_form.logout_url(next) + return next + + def login_form(self): + """ + Combine the auth() form with alt_login_form. + + If signals are set and a parameter in request matches any signals, + it will return the call of alt_login_form.login_form instead. + So alt_login_form can handle some particular situations, for example, + multiple steps of OpenID login inside alt_login_form.login_form. + + Otherwise it will render the normal login form combined with + alt_login_form.login_form. + """ + request = self.auth.environment.request + args = request.args + + if (self.signals and + any([True for signal in self.signals if request.vars.has_key(signal)]) + ): + return self.alt_login_form.login_form() + + self.auth.settings.login_form = self.auth + form = DIV(self.auth()) + self.auth.settings.login_form = self + + form.components.append(self.alt_login_form.login_form()) + return form ADDED gluon/contrib/login_methods/gae_google_account.py Index: gluon/contrib/login_methods/gae_google_account.py ================================================================== --- gluon/contrib/login_methods/gae_google_account.py +++ gluon/contrib/login_methods/gae_google_account.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +This file is part of web2py Web Framework (Copyrighted, 2007-2009). +Developed by Massimo Di Pierro . +License: GPL v2 + +Thanks to Hans Donner for GaeGoogleAccount. +""" + +from google.appengine.api import users + +class GaeGoogleAccount(object): + """ + Login will be done via Google's Appengine login object, instead of web2py's + login form. + + Include in your model (eg db.py):: + + from gluon.contrib.login_methods.gae_google_account import \ + GaeGoogleAccount + auth.settings.login_form=GaeGoogleAccount() + + """ + + def login_url(self, next="/"): + return users.create_login_url(next) + + def logout_url(self, next="/"): + return users.create_logout_url(next) + + def get_user(self): + user = users.get_current_user() + if user: + return dict(nickname=user.nickname(), email=user.email(), + user_id=user.user_id(), source="google account") ADDED gluon/contrib/login_methods/ldap_auth.py Index: gluon/contrib/login_methods/ldap_auth.py ================================================================== --- gluon/contrib/login_methods/ldap_auth.py +++ gluon/contrib/login_methods/ldap_auth.py @@ -0,0 +1,170 @@ +import sys +import logging +try: + import ldap + ldap.set_option(ldap.OPT_REFERRALS, 0) +except Exception, e: + logging.error('missing ldap, try "easy_install python-ldap"') + raise e + + +def ldap_auth(server='ldap', port=None, + base_dn='ou=users,dc=domain,dc=com', + mode='uid', secure=False, cert_path=None, bind_dn=None, bind_pw=None, filterstr='objectClass=*'): + """ + to use ldap login with MS Active Directory:: + + from gluon.contrib.login_methods.ldap_auth import ldap_auth + auth.settings.login_methods.append(ldap_auth( + mode='ad', server='my.domain.controller', + base_dn='ou=Users,dc=domain,dc=com')) + + to use ldap login with Notes Domino:: + + auth.settings.login_methods.append(ldap_auth( + mode='domino',server='my.domino.server')) + + to use ldap login with OpenLDAP:: + + auth.settings.login_methods.append(ldap_auth( + server='my.ldap.server', base_dn='ou=Users,dc=domain,dc=com')) + + to use ldap login with OpenLDAP and subtree search and (optionally) multiple DNs: + + auth.settings.login_methods.append(ldap_auth( + mode='uid_r', server='my.ldap.server', + base_dn=['ou=Users,dc=domain,dc=com','ou=Staff,dc=domain,dc=com'])) + + or (if using CN):: + + auth.settings.login_methods.append(ldap_auth( + mode='cn', server='my.ldap.server', + base_dn='ou=Users,dc=domain,dc=com')) + + If using secure ldaps:// pass secure=True and cert_path="..." + + If you need to bind to the directory with an admin account in order to search it then specify bind_dn & bind_pw to use for this. + - currently only implemented for Active Directory + + If you need to restrict the set of allowed users (e.g. to members of a department) then specify + a rfc4515 search filter string. + - currently only implemented for mode in ['ad', 'company', 'uid_r'] + """ + + def ldap_auth_aux(username, + password, + ldap_server=server, + ldap_port=port, + ldap_basedn=base_dn, + ldap_mode=mode, + ldap_binddn=bind_dn, + ldap_bindpw=bind_pw, + secure=secure, + cert_path=cert_path, + filterstr=filterstr): + try: + if secure: + if not ldap_port: + ldap_port = 636 + con = ldap.initialize( + "ldaps://" + ldap_server + ":" + str(ldap_port)) + if cert_path: + con.set_option(ldap.OPT_X_TLS_CACERTDIR, cert_path) + else: + if not ldap_port: + ldap_port = 389 + con = ldap.initialize( + "ldap://" + ldap_server + ":" + str(ldap_port)) + + if ldap_mode == 'ad': + # Microsoft Active Directory + if '@' not in username: + domain = [] + for x in ldap_basedn.split(','): + if "DC=" in x.upper(): + domain.append(x.split('=')[-1]) + username = "%s@%s" % (username, '.'.join(domain)) + username_bare = username.split("@")[0] + con.set_option(ldap.OPT_PROTOCOL_VERSION, 3) + if ldap_binddn: + # need to search directory with an admin account 1st + con.simple_bind_s(ldap_binddn, ldap_bindpw) + else: + # credentials should be in the form of username@domain.tld + con.simple_bind_s(username, password) + # this will throw an index error if the account is not found + # in the ldap_basedn + result = con.search_ext_s( + ldap_basedn, ldap.SCOPE_SUBTREE, + "(&(sAMAccountName=%s)(%s))" % (username_bare, filterstr), ["sAMAccountName"])[0][1] + if ldap_binddn: + # We know the user exists & is in the correct OU + # so now we just check the password + con.simple_bind_s(username, password) + + if ldap_mode == 'domino': + # Notes Domino + if "@" in username: + username = username.split("@")[0] + con.simple_bind_s(username, password) + + if ldap_mode == 'cn': + # OpenLDAP (CN) + dn = "cn=" + username + "," + ldap_basedn + con.simple_bind_s(dn, password) + + if ldap_mode == 'uid': + # OpenLDAP (UID) + dn = "uid=" + username + "," + ldap_basedn + con.simple_bind_s(dn, password) + + if ldap_mode == 'company': + # no DNs or password needed to search directory + dn = "" + pw = "" + # bind anonymously + con.simple_bind_s(dn, pw) + # search by e-mail address + filter = '(&(mail=' + username + ')(' + filterstr + '))' + # find the uid + attrs = ['uid'] + # perform the actual search + company_search_result=con.search_s(ldap_basedn, + ldap.SCOPE_SUBTREE, + filter, attrs) + dn = company_search_result[0][0] + # perform the real authentication test + con.simple_bind_s(dn, password) + + if ldap_mode == 'uid_r': + # OpenLDAP (UID) with subtree search and multiple DNs + if type(ldap_basedn) == type([]): + basedns = ldap_basedn + else: + basedns = [ldap_basedn] + filter = '(&(uid=%s)(%s))' % (username, filterstr) + for basedn in basedns: + try: + result = con.search_s(basedn, ldap.SCOPE_SUBTREE, filter) + if result: + user_dn = result[0][0] + # Check the password + con.simple_bind_s(user_dn, password) + con.unbind() + return True + except ldap.LDAPError, detail: + (exc_type, exc_value) = sys.exc_info()[:2] + sys.stderr.write("ldap_auth: searching %s for %s resulted in %s: %s\n" % + (basedn, filter, exc_type, exc_value)) + return False + + con.unbind() + return True + except ldap.LDAPError, e: + return False + except IndexError, ex: # for AD membership test + return False + + if filterstr[0] == '(' and filterstr[-1] == ')': # rfc4515 syntax + filterstr = filterstr[1:-1] # parens added again where used + return ldap_auth_aux ADDED gluon/contrib/login_methods/linkedin_account.py Index: gluon/contrib/login_methods/linkedin_account.py ================================================================== --- gluon/contrib/login_methods/linkedin_account.py +++ gluon/contrib/login_methods/linkedin_account.py @@ -0,0 +1,50 @@ + +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +This file is part of web2py Web Framework (Copyrighted, 2007-2009). +Developed by Massimo Di Pierro . +License: GPL v2 + +Thanks to Hans Donner for GaeGoogleAccount. +""" + +from gluon.http import HTTP +try: + import linkedin +except ImportError: + raise HTTP(400,"linkedin module not found") + +class LinkedInAccount(object): + """ + Login will be done via Google's Appengine login object, instead of web2py's + login form. + + Include in your model (eg db.py):: + + from gluon.contrib.login_methods.linkedin_account import LinkedInAccount + auth.settings.login_form=LinkedInAccount(request,KEY,SECRET,RETURN_URL) + + """ + + def __init__(self,request,key,secret,return_url): + self.request = request + self.api = linkedin.LinkedIn(key,secret,return_url) + self.token = result = self.api.requestToken() + + def login_url(self, next="/"): + return self.api.getAuthorizeURL(self.token) + + def logout_url(self, next="/"): + return '' + + def get_user(self): + result = self.request.vars.verifier and self.api.accessToken(verifier = self.request.vars.verifier ) + if result: + profile = self.api.GetProfile() + profile = self.api.GetProfile(profile).public_url = "http://www.linkedin.com/in/ozgurv" + return dict(first_name = profile.first_name, + last_name = profile.last_name, + username = profile.id) + ADDED gluon/contrib/login_methods/loginza.py Index: gluon/contrib/login_methods/loginza.py ================================================================== --- gluon/contrib/login_methods/loginza.py +++ gluon/contrib/login_methods/loginza.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" + Loginza.ru authentication for web2py + Developed by Vladimir Dronnikov (Copyright © 2011) + Email +""" + +import urllib +from gluon.html import * +from gluon.tools import fetch +from gluon.storage import Storage +import gluon.contrib.simplejson as json + +class Loginza(object): + + """ + from gluon.contrib.login_methods.loginza import Loginza + auth.settings.login_form = Loginza(request, + url = "http://localhost:8000/%s/default/user/login" % request.application) + """ + + def __init__(self, + request, + url = "", + embed = True, + auth_url = "http://loginza.ru/api/authinfo", + language = "en", + prompt = "loginza", + on_login_failure = None, + ): + + self.request = request + self.token_url = url + self.embed = embed + self.auth_url = auth_url + self.language = language + self.prompt = prompt + self.profile = None + self.on_login_failure = on_login_failure + self.mappings = Storage() + + # TODO: profile.photo is the URL to the picture + # Howto download and store it locally? + # FIXME: what if email is unique=True + + self.mappings["http://twitter.com/"] = lambda profile:\ + dict(registration_id = profile.get("identity",""), + username = profile.get("nickname",""), + email = profile.get("email",""), + last_name = profile.get("name","").get("full_name",""), + #avatar = profile.get("photo",""), + ) + self.mappings["https://www.google.com/accounts/o8/ud"] = lambda profile:\ + dict(registration_id = profile.get("identity",""), + username = profile.get("name","").get("full_name",""), + email = profile.get("email",""), + first_name = profile.get("name","").get("first_name",""), + last_name = profile.get("name","").get("last_name",""), + #avatar = profile.get("photo",""), + ) + self.mappings["http://vkontakte.ru/"] = lambda profile:\ + dict(registration_id=profile.get("identity",""), + username = profile.get("name","").get("full_name",""), + email = profile.get("email",""), + first_name = profile.get("name","").get("first_name",""), + last_name = profile.get("name","").get("last_name",""), + #avatar = profile.get("photo",""), + ) + self.mappings.default = lambda profile:\ + dict(registration_id = profile.get("identity",""), + username = profile.get("name","").get("full_name"), + email = profile.get("email",""), + first_name = profile.get("name","").get("first_name",""), + last_name = profile.get("name","").get("last_name",""), + #avatar = profile.get("photo",""), + ) + + def get_user(self): + request = self.request + if request.vars.token: + user = Storage() + data = urllib.urlencode(dict(token = request.vars.token)) + auth_info_json = fetch(self.auth_url+'?'+data) + #print auth_info_json + auth_info = json.loads(auth_info_json) + if auth_info["identity"] != None: + self.profile = auth_info + provider = self.profile["provider"] + user = self.mappings.get(provider, self.mappings.default)(self.profile) + #user["password"] = ??? + #user["avatar"] = ??? + return user + elif self.on_login_failure: + redirect(self.on_login_failure) + return None + + def login_form(self): + request = self.request + args = request.args + LOGINZA_URL = "https://loginza.ru/api/widget?lang=%s&token_url=%s&overlay=loginza" + if self.embed: + form = IFRAME(_src=LOGINZA_URL % (self.language, self.token_url), + _scrolling="no", + _frameborder="no", + _style="width:359px;height:300px;") + else: + form = DIV(A(self.prompt, _href=LOGINZA_URL % (self.language, self.token_url), _class="loginza"), + SCRIPT(_src="https://s3-eu-west-1.amazonaws.com/s1.loginza.ru/js/widget.js", _type="text/javascript")) + return form ADDED gluon/contrib/login_methods/oauth10a_account.py Index: gluon/contrib/login_methods/oauth10a_account.py ================================================================== --- gluon/contrib/login_methods/oauth10a_account.py +++ gluon/contrib/login_methods/oauth10a_account.py @@ -0,0 +1,189 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Written by Michele Comitini +License: GPL v3 + +Adds support for OAuth1.0a authentication to web2py. + +Dependencies: + - python-oauth2 (http://github.com/simplegeo/python-oauth2) + +""" + +import oauth2 as oauth +import cgi + +from urllib2 import urlopen +import urllib2 +from urllib import urlencode + +class OAuthAccount(object): + """ + Login will be done via OAuth Framework, instead of web2py's + login form. + + Include in your model (eg db.py):: + # define the auth_table before call to auth.define_tables() + auth_table = db.define_table( + auth.settings.table_user_name, + Field('first_name', length=128, default=""), + Field('last_name', length=128, default=""), + Field('username', length=128, default="", unique=True), + Field('password', 'password', length=256, + readable=False, label='Password'), + Field('registration_key', length=128, default= "", + writable=False, readable=False)) + + auth_table.username.requires = IS_NOT_IN_DB(db, auth_table.username) + . + . + . + auth.define_tables() + . + . + . + + CLIENT_ID=\"\" + CLIENT_SECRET=\"\" + AUTH_URL="..." + TOKEN_URL="..." + ACCESS_TOKEN_URL="..." + from gluon.contrib.login_methods.oauth10a_account import OAuthAccount + auth.settings.login_form=OAuthAccount(globals(),CLIENT_ID,CLIENT_SECRET, AUTH_URL, TOKEN_URL, ACCESS_TOKEN_URL) + + """ + + def __redirect_uri(self, next=None): + """Build the uri used by the authenticating server to redirect + the client back to the page originating the auth request. + Appends the _next action to the generated url so the flows continues. + """ + r = self.request + http_host=r.env.http_x_forwarded_for + if not http_host: http_host=r.env.http_host + + url_scheme = r.env.wsgi_url_scheme + if next: + path_info = next + else: + path_info = r.env.path_info + uri = '%s://%s%s' %(url_scheme, http_host, path_info) + if r.get_vars and not next: + uri += '?' + urlencode(r.get_vars) + return uri + + + def accessToken(self): + """Return the access token generated by the authenticating server. + + If token is already in the session that one will be used. + Otherwise the token is fetched from the auth server. + + """ + + if self.session.access_token: + # return the token (TODO: does it expire?) + + return self.session.access_token + if self.session.request_token: + # Exchange the request token with an authorization token. + token = self.session.request_token + self.session.request_token = None + + # Build an authorized client + # OAuth1.0a put the verifier! + token.set_verifier(self.request.vars.oauth_verifier) + client = oauth.Client(self.consumer, token) + + + resp, content = client.request(self.access_token_url, "POST") + if str(resp['status']) != '200': + self.session.request_token = None + self.globals['redirect'](self.globals['URL'](f='user',args='logout')) + + + self.session.access_token = oauth.Token.from_string(content) + + return self.session.access_token + + self.session.access_token = None + return None + + def __init__(self, g, client_id, client_secret, auth_url, token_url, access_token_url): + self.globals = g + self.client_id = client_id + self.client_secret = client_secret + self.code = None + self.request = g['request'] + self.session = g['session'] + self.auth_url = auth_url + self.token_url = token_url + self.access_token_url = access_token_url + + # consumer init + self.consumer = oauth.Consumer(self.client_id, self.client_secret) + + + def login_url(self, next="/"): + self.__oauth_login(next) + return next + + def logout_url(self, next="/"): + self.session.request_token = None + self.session.access_token = None + return next + + def get_user(self): + '''Get user data. + + Since OAuth does not specify what a user + is, this function must be implemented for the specific + provider. + ''' + raise NotImplementedError, "Must override get_user()" + + def __oauth_login(self, next): + '''This method redirects the user to the authenticating form + on authentication server if the authentication code + and the authentication token are not available to the + application yet. + + Once the authentication code has been received this method is + called to set the access token into the session by calling + accessToken() + ''' + + if not self.accessToken(): + # setup the client + client = oauth.Client(self.consumer, None) + # Get a request token. + # oauth_callback *is REQUIRED* for OAuth1.0a + # putting it in the body seems to work. + callback_url = self.__redirect_uri(next) + data = urlencode(dict(oauth_callback=callback_url)) + resp, content = client.request(self.token_url, "POST", body=data) + if resp['status'] != '200': + self.session.request_token = None + self.globals['redirect'](self.globals['URL'](f='user',args='logout')) + + # Store the request token in session. + request_token = self.session.request_token = oauth.Token.from_string(content) + + # Redirect the user to the authentication URL and pass the callback url. + data = urlencode(dict(oauth_token=request_token.key, + oauth_callback=callback_url)) + auth_request_url = self.auth_url + '?' +data + + + HTTP = self.globals['HTTP'] + + + raise HTTP(307, + "You are not authenticated: you are being redirected to the authentication server", + Location=auth_request_url) + + return None + + ADDED gluon/contrib/login_methods/oauth20_account.py Index: gluon/contrib/login_methods/oauth20_account.py ================================================================== --- gluon/contrib/login_methods/oauth20_account.py +++ gluon/contrib/login_methods/oauth20_account.py @@ -0,0 +1,206 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Written by Michele Comitini +License: GPL v3 + +Adds support for OAuth 2.0 authentication to web2py. + +OAuth 2.0 Draft: http://tools.ietf.org/html/draft-ietf-oauth-v2-10 +""" + +import time +import cgi + +from urllib2 import urlopen +import urllib2 +from urllib import urlencode + +class OAuthAccount(object): + """ + Login will be done via OAuth Framework, instead of web2py's + login form. + + Include in your model (eg db.py):: + # define the auth_table before call to auth.define_tables() + auth_table = db.define_table( + auth.settings.table_user_name, + Field('first_name', length=128, default=""), + Field('last_name', length=128, default=""), + Field('username', length=128, default="", unique=True), + Field('password', 'password', length=256, + readable=False, label='Password'), + Field('registration_key', length=128, default= "", + writable=False, readable=False)) + + auth_table.username.requires = IS_NOT_IN_DB(db, auth_table.username) + . + . + . + auth.define_tables() + . + . + . + + CLIENT_ID=\"\" + CLIENT_SECRET=\"\" + AUTH_URL="http://..." + TOKEN_URL="http://..." + from gluon.contrib.login_methods.oauth20_account import OAuthAccount + auth.settings.login_form=OAuthAccount(globals(),CLIENT_ID,CLIENT_SECRET,AUTH_URL, TOKEN_URL, **args ) + Any optional arg will be passed as is to remote server for requests. + It can be used for the optional "scope" parameters for Facebook. + """ + def __redirect_uri(self, next=None): + """Build the uri used by the authenticating server to redirect + the client back to the page originating the auth request. + Appends the _next action to the generated url so the flows continues. + """ + + r = self.request + http_host=r.env.http_x_forwarded_for + if not http_host: http_host=r.env.http_host + + url_scheme = r.env.wsgi_url_scheme + if next: + path_info = next + else: + path_info = r.env.path_info + uri = '%s://%s%s' %(url_scheme, http_host, path_info) + if r.get_vars and not next: + uri += '?' + urlencode(r.get_vars) + return uri + + + def __build_url_opener(self, uri): + """Build the url opener for managing HTTP Basic Athentication""" + # Create an OpenerDirector with support for Basic HTTP Authentication... + auth_handler = urllib2.HTTPBasicAuthHandler() + auth_handler.add_password(None, + uri, + self.client_id, + self.client_secret) + opener = urllib2.build_opener(auth_handler) + return opener + + + def accessToken(self): + """Return the access token generated by the authenticating server. + + If token is already in the session that one will be used. + Otherwise the token is fetched from the auth server. + + """ + if self.session.token and self.session.token.has_key('expires'): + expires = self.session.token['expires'] + # reuse token until expiration + if expires == 0 or expires > time.time(): + return self.session.token['access_token'] + if self.session.code: + data = dict(client_id=self.client_id, + client_secret=self.client_secret, + redirect_uri=self.session.redirect_uri, + response_type='token', code=self.session.code) + + + if self.args: + data.update(self.args) + open_url = None + opener = self.__build_url_opener(self.token_url) + try: + open_url = opener.open(self.token_url, urlencode(data)) + except urllib2.HTTPError, e: + raise Exception(e.read()) + finally: + del self.session.code # throw it away + + if open_url: + try: + tokendata = cgi.parse_qs(open_url.read()) + self.session.token = dict([(k,v[-1]) for k,v in tokendata.items()]) + # set expiration absolute time try to avoid broken + # implementations where "expires_in" becomes "expires" + if self.session.token.has_key('expires_in'): + exps = 'expires_in' + else: + exps = 'expires' + self.session.token['expires'] = int(self.session.token[exps]) + \ + time.time() + finally: + opener.close() + return self.session.token['access_token'] + + self.session.token = None + return None + + def __init__(self, g, client_id, client_secret, auth_url, token_url, **args): + self.globals = g + self.client_id = client_id + self.client_secret = client_secret + self.request = g['request'] + self.session = g['session'] + self.auth_url = auth_url + self.token_url = token_url + self.args = args + + def login_url(self, next="/"): + self.__oauth_login(next) + return next + + def logout_url(self, next="/"): + del self.session.token + return next + + def get_user(self): + '''Returns the user using the Graph API. + ''' + raise NotImplementedError, "Must override get_user()" + if not self.accessToken(): + return None + + if not self.graph: + self.graph = GraphAPI((self.accessToken())) + + user = None + try: + user = self.graph.get_object("me") + except GraphAPIError: + self.session.token = None + self.graph = None + + if user: + return dict(first_name = user['first_name'], + last_name = user['last_name'], + username = user['id']) + + + + def __oauth_login(self, next): + '''This method redirects the user to the authenticating form + on authentication server if the authentication code + and the authentication token are not available to the + application yet. + + Once the authentication code has been received this method is + called to set the access token into the session by calling + accessToken() + ''' + if not self.accessToken(): + if not self.request.vars.code: + self.session.redirect_uri=self.__redirect_uri(next) + data = dict(redirect_uri=self.session.redirect_uri, + response_type='code', + client_id=self.client_id) + if self.args: + data.update(self.args) + auth_request_url = self.auth_url + "?" +urlencode(data) + HTTP = self.globals['HTTP'] + raise HTTP(307, + "You are not authenticated: you are being redirected to the authentication server", + Location=auth_request_url) + else: + self.session.code = self.request.vars.code + self.accessToken() + return self.session.code + return None ADDED gluon/contrib/login_methods/openid_auth.py Index: gluon/contrib/login_methods/openid_auth.py ================================================================== --- gluon/contrib/login_methods/openid_auth.py +++ gluon/contrib/login_methods/openid_auth.py @@ -0,0 +1,631 @@ +#!/usr/bin/env python +# coding: utf8 + +""" + OpenID authentication for web2py + + Allowed using OpenID login together with web2py built-in login. + + By default, to support OpenID login, put this in your db.py + + >>> from gluon.contrib.login_methods.openid_auth import OpenIDAuth + >>> auth.settings.login_form = OpenIDAuth(auth) + + To show OpenID list in user profile, you can add the following code + before the end of function user() of your_app/controllers/default.py + + + if (request.args and request.args(0) == "profile"): + + form = DIV(form, openid_login_form.list_user_openids()) + return dict(form=form, login_form=login_form, register_form=register_form, self_registration=self_registration) + + More detail in the description of the class OpenIDAuth. + + Requirements: + python-openid version 2.2.5 or later + + Reference: + * w2p openID + http://w2popenid.appspot.com/init/default/wiki/w2popenid + * RPX and web2py auth module + http://www.web2pyslices.com/main/slices/take_slice/28 + * built-in file: gluon/contrib/login_methods/rpx_account.py + * built-in file: gluon/tools.py (Auth class) +""" +import time +from datetime import datetime, timedelta + +from gluon import * +from gluon.storage import Storage, Messages + +try: + import openid.consumer.consumer + from openid.association import Association + from openid.store.interface import OpenIDStore + from openid.extensions.sreg import SRegRequest, SRegResponse + from openid.store import nonce + from openid.consumer.discover import DiscoveryFailure +except ImportError, err: + raise ImportError("OpenIDAuth requires python-openid package") + +DEFAULT = lambda: None + +class OpenIDAuth(object): + """ + OpenIDAuth + + It supports the logout_url, implementing the get_user and login_form + for cas usage of gluon.tools.Auth. + + It also uses the ExtendedLoginForm to allow the OpenIDAuth login_methods + combined with the standard logon/register procedure. + + It uses OpenID Consumer when render the form and begins the OpenID + authentication. + + Example: (put these code after auth.define_tables() in your models.) + + auth = Auth(globals(), db) # authentication/authorization + ... + auth.define_tables() # creates all needed tables + ... + + #include in your model after auth has been defined + from gluon.contrib.login_methods.openid_auth import OpenIDAuth + openid_login_form = OpenIDAuth(request, auth, db) + + from gluon.contrib.login_methods.extended_login_form import ExtendedLoginForm + extended_login_form = ExtendedLoginForm(request, auth, openid_login_form, + signals=['oid','janrain_nonce']) + + auth.settings.login_form = extended_login_form + """ + + def __init__(self, auth): + self.auth = auth + self.db = auth.db + + request = current.request + self.nextvar = '_next' + self.realm = 'http://%s' % request.env.http_host + self.login_url = URL(r=request, f='user', args=['login']) + self.return_to_url = self.realm + self.login_url + + self.table_alt_logins_name = "alt_logins" + if not auth.settings.table_user: + raise + self.table_user = self.auth.settings.table_user + self.openid_expiration = 15 #minutes + + self.messages = self._define_messages() + + if not self.table_alt_logins_name in self.db.tables: + self._define_alt_login_table() + + def _define_messages(self): + messages = Messages(current.T) + messages.label_alt_login_username = 'Sign-in with OpenID: ' + messages.label_add_alt_login_username = 'Add a new OpenID: ' + messages.submit_button = 'Sign in' + messages.submit_button_add = 'Add' + messages.a_delete = 'Delete' + messages.comment_openid_signin = 'What is OpenID?' + messages.comment_openid_help_title = 'Start using your OpenID' + messages.comment_openid_help_url = 'http://openid.net/get-an-openid/start-using-your-openid/' + messages.openid_fail_discover = 'Failed to discover OpenID service. Check your OpenID or "More about OpenID"?' + messages.flash_openid_expired = 'OpenID expired. Please login or authenticate OpenID again. Sorry for the inconvenient.' + messages.flash_openid_associated = 'OpenID associated' + messages.flash_associate_openid = 'Please login or register an account for this OpenID.' + messages.p_openid_not_registered = "This Open ID haven't be registered. " \ + + "Please login to associate with it or register an account for it." + messages.flash_openid_authenticated = 'OpenID authenticated successfully.' + messages.flash_openid_fail_authentication = 'OpenID authentication failed. (Error message: %s)' + messages.flash_openid_canceled = 'OpenID authentication canceled by user.' + messages.flash_openid_need_setup = 'OpenID authentication needs to be setup by the user with the provider first.' + messages.h_openid_login = 'OpenID Login' + messages.h_openid_list = 'OpenID List' + return messages + + def _define_alt_login_table(self): + """ + Define the OpenID login table. + Note: type is what I used for our project. We're going to support 'fackbook' and + 'plurk' alternate login methods. Otherwise it's always 'openid' and you + may not need it. This should be easy to changed. + (Just remove the field of "type" and remove the + "and db.alt_logins.type == type_" in _find_matched_openid function) + """ + db = self.db + table = db.define_table( + self.table_alt_logins_name, + Field('username', length=512, default=''), + Field('type', length=128, default='openid', readable=False), + Field('user', self.table_user, readable=False), + ) + table.username.requires = IS_NOT_IN_DB(db, table.username) + self.table_alt_logins = table + + def logout_url(self, next): + """ + Delete the w2popenid record in session as logout + """ + if current.session.w2popenid: + del(current.session.w2popenid) + return next + + def login_form(self): + """ + Start to process the OpenID response if 'janrain_nonce' in request parameters + and not processed yet. Else return the OpenID form for login. + """ + request = current.request + if request.vars.has_key('janrain_nonce') and not self._processed(): + self._process_response() + return self.auth() + return self._form() + + def get_user(self): + """ + It supports the logout_url, implementing the get_user and login_form + for cas usage of gluon.tools.Auth. + """ + request = current.request + args = request.args + + if args[0] == 'logout': + return True # Let logout_url got called + + if current.session.w2popenid: + w2popenid = current.session.w2popenid + db = self.db + if (w2popenid.ok is True and w2popenid.oid): # OpenID authenticated + if self._w2popenid_expired(w2popenid): + del(current.session.w2popenid) + flash = self.messages.flash_openid_expired + current.session.warning = flash + redirect(self.auth.settings.login_url) + oid = self._remove_protocol(w2popenid.oid) + alt_login = self._find_matched_openid(db, oid) + + nextvar = self.nextvar + # This OpenID not in the database. If user logged in then add it + # into database, else ask user to login or register. + if not alt_login: + if self.auth.is_logged_in(): + # TODO: ask first maybe + self._associate_user_openid(self.auth.user, oid) + if current.session.w2popenid: + del(current.session.w2popenid) + current.session.flash = self.messages.flash_openid_associated + if request.vars.has_key(nextvar): + redirect(request.vars[nextvar]) + redirect(self.auth.settings.login_next) + + if not request.vars.has_key(nextvar): + # no next var, add it and do login again + # so if user login or register can go back here to associate the OpenID + redirect(URL(r=request, + args=['login'], + vars={nextvar:self.login_url})) + self.login_form = self._form_with_notification() + current.session.flash = self.messages.flash_associate_openid + return None # need to login or register to associate this openid + + # Get existed OpenID user + user = db(self.table_user.id==alt_login.user).select().first() + if user: + if current.session.w2popenid: + del(current.session.w2popenid) + if 'username' in self.table_user.fields(): + username = 'username' + elif 'email' in self.table_user.fields(): + username = 'email' + return {username: user[username]} if user else None # login success (almost) + + return None # just start to login + + def _find_matched_openid(self, db, oid, type_='openid'): + """ + Get the matched OpenID for given + """ + query = ((db.alt_logins.username == oid) & (db.alt_logins.type == type_)) + alt_login = db(query).select().first() # Get the OpenID record + return alt_login + + def _associate_user_openid(self, user, oid): + """ + Associate the user logged in with given OpenID + """ + # print "[DB] %s authenticated" % oid + self.db.alt_logins.insert(username=oid, user=user.id) + + def _form_with_notification(self): + """ + Render the form for normal login with a notice of OpenID authenticated + """ + form = DIV() + # TODO: check when will happen + if self.auth.settings.login_form in (self.auth, self): + self.auth.settings.login_form = self.auth + form = DIV(self.auth()) + + register_note = DIV(P(self.messages.p_openid_not_registered)) + form.components.append(register_note) + return lambda: form + + def _remove_protocol(self, oid): + """ + Remove https:// or http:// from oid url + """ + protocol = 'https://' + if oid.startswith(protocol): + oid = oid[len(protocol):] + return oid + protocol = 'http://' + if oid.startswith(protocol): + oid = oid[len(protocol):] + return oid + return oid + + def _init_consumerhelper(self): + """ + Initialize the ConsumerHelper + """ + if not hasattr(self, "consumerhelper"): + self.consumerhelper = ConsumerHelper(current.session, + self.db) + return self.consumerhelper + + + def _form(self, style=None): + form = DIV(H3(self.messages.h_openid_login), self._login_form(style)) + return form + + def _login_form(self, + openid_field_label=None, + submit_button=None, + _next=None, + style=None): + """ + Render the form for OpenID login + """ + def warning_openid_fail(session): + session.warning = messages.openid_fail_discover + + style = style or """ +background-attachment: scroll; +background-repeat: no-repeat; +background-image: url("http://wiki.openid.net/f/openid-16x16.gif"); +background-position: 0% 50%; +background-color: transparent; +padding-left: 18px; +width: 400px; +""" + style = style.replace("\n","") + + request = current.request + session = current.session + messages = self.messages + hidden_next_input = "" + if _next == 'profile': + profile_url = URL(r=request, f='user', args=['profile']) + hidden_next_input = INPUT(_type="hidden", _name="_next", _value=profile_url) + form = FORM(openid_field_label or self.messages.label_alt_login_username, + INPUT(_type="input", _name="oid", + requires=IS_NOT_EMPTY(error_message=messages.openid_fail_discover), + _style=style), + hidden_next_input, + INPUT(_type="submit", _value=submit_button or messages.submit_button), + " ", + A(messages.comment_openid_signin, + _href=messages.comment_openid_help_url, + _title=messages.comment_openid_help_title, + _class='openid-identifier', + _target="_blank"), + _action=self.login_url + ) + if form.accepts(request.vars, session): + oid = request.vars.oid + consumerhelper = self._init_consumerhelper() + url = self.login_url + return_to_url = self.return_to_url + if not oid: + warning_openid_fail(session) + redirect(url) + try: + if request.vars.has_key('_next'): + return_to_url = self.return_to_url + '?_next=' + request.vars._next + url = consumerhelper.begin(oid, self.realm, return_to_url) + except DiscoveryFailure: + warning_openid_fail(session) + redirect(url) + return form + + def _processed(self): + """ + Check if w2popenid authentication is processed. + Return True if processed else False. + """ + processed = (hasattr(current.session, 'w2popenid') and + current.session.w2popenid.ok is True) + return processed + + def _set_w2popenid_expiration(self, w2popenid): + """ + Set expiration for OpenID authentication. + """ + w2popenid.expiration = datetime.now() + timedelta(minutes=self.openid_expiration) + + def _w2popenid_expired(self, w2popenid): + """ + Check if w2popenid authentication is expired. + Return True if expired else False. + """ + return (not w2popenid.expiration) or (datetime.now() > w2popenid.expiration) + + def _process_response(self): + """ + Process the OpenID by ConsumerHelper. + """ + request = current.request + request_vars = request.vars + consumerhelper = self._init_consumerhelper() + process_status = consumerhelper.process_response(request_vars, self.return_to_url) + if process_status == "success": + w2popenid = current.session.w2popenid + user_data = self.consumerhelper.sreg() + current.session.w2popenid.ok = True + self._set_w2popenid_expiration(w2popenid) + w2popenid.user_data = user_data + current.session.flash = self.messages.flash_openid_authenticated + elif process_status == "failure": + flash = self.messages.flash_openid_fail_authentication % consumerhelper.error_message + current.session.warning = flash + elif process_status == "cancel": + current.session.warning = self.messages.flash_openid_canceled + elif process_status == "setup_needed": + current.session.warning = self.messages.flash_openid_need_setup + + def list_user_openids(self): + messages = self.messages + request = current.request + if request.vars.has_key('delete_openid'): + self.remove_openid(request.vars.delete_openid) + + query = self.db.alt_logins.user == self.auth.user.id + alt_logins = self.db(query).select() + l = [] + for alt_login in alt_logins: + username = alt_login.username + delete_href = URL(r=request, f='user', + args=['profile'], + vars={'delete_openid': username}) + delete_link = A(messages.a_delete, _href=delete_href) + l.append(LI(username, " ", delete_link)) + + profile_url = URL(r=request, f='user', args=['profile']) + #return_to_url = self.return_to_url + '?' + self.nextvar + '=' + profile_url + openid_list = DIV(H3(messages.h_openid_list), UL(l), + self._login_form( + _next='profile', + submit_button=messages.submit_button_add, + openid_field_label=messages.label_add_alt_login_username) + ) + return openid_list + + + def remove_openid(self, openid): + query = self.db.alt_logins.username == openid + self.db(query).delete() + +class ConsumerHelper(object): + """ + ConsumerHelper knows the python-openid and + """ + + def __init__(self, session, db): + self.session = session + store = self._init_store(db) + self.consumer = openid.consumer.consumer.Consumer(session, store) + + def _init_store(self, db): + """ + Initialize Web2pyStore + """ + if not hasattr(self, "store"): + store = Web2pyStore(db) + session = self.session + if not session.has_key('w2popenid'): + session.w2popenid = Storage() + self.store = store + return self.store + + def begin(self, oid, realm, return_to_url): + """ + Begin the OpenID authentication + """ + w2popenid = self.session.w2popenid + w2popenid.oid = oid + auth_req = self.consumer.begin(oid) + auth_req.addExtension(SRegRequest(required=['email','nickname'])) + url = auth_req.redirectURL(return_to=return_to_url, realm=realm) + return url + + def process_response(self, request_vars, return_to_url): + """ + Complete the process and + """ + resp = self.consumer.complete(request_vars, return_to_url) + if resp: + if resp.status == openid.consumer.consumer.SUCCESS: + self.resp = resp + if hasattr(resp, "identity_url"): + self.session.w2popenid.oid = resp.identity_url + return "success" + if resp.status == openid.consumer.consumer.FAILURE: + self.error_message = resp.message + return "failure" + if resp.status == openid.consumer.consumer.CANCEL: + return "cancel" + if resp.status == openid.consumer.consumer.SETUP_NEEDED: + return "setup_needed" + return "no resp" + + def sreg(self): + """ + Try to get OpenID Simple Registation + http://openid.net/specs/openid-simple-registration-extension-1_0.html + """ + if self.resp: + resp = self.resp + sreg_resp = SRegResponse.fromSuccessResponse(resp) + return sreg_resp.data if sreg_resp else None + else: + return None + + +class Web2pyStore(OpenIDStore): + """ + Web2pyStore + + This class implements the OpenIDStore interface. OpenID stores take care + of persisting nonces and associations. The Janrain Python OpenID library + comes with implementations for file and memory storage. Web2pyStore uses + the web2py db abstration layer. See the source code docs of OpenIDStore + for a comprehensive description of this interface. + """ + + def __init__(self, database): + self.database = database + self.table_oid_associations_name = 'oid_associations' + self.table_oid_nonces_name = 'oid_nonces' + self._initDB() + + def _initDB(self): + + if self.table_oid_associations_name not in self.database: + self.database.define_table(self.table_oid_associations_name, + Field('server_url', 'string', length=2047, required=True), + Field('handle', 'string', length=255, required=True), + Field('secret', 'blob', required=True), + Field('issued', 'integer', required=True), + Field('lifetime', 'integer', required=True), + Field('assoc_type', 'string', length=64, required=True) + ) + if self.table_oid_nonces_name not in self.database: + self.database.define_table(self.table_oid_nonces_name, + Field('server_url', 'string', length=2047, required=True), + Field('timestamp', 'integer', required=True), + Field('salt', 'string', length=40, required=True) + ) + + def storeAssociation(self, server_url, association): + """ + Store associations. If there already is one with the same + server_url and handle in the table replace it. + """ + + db = self.database + query = (db.oid_associations.server_url == server_url) & (db.oid_associations.handle == association.handle) + db(query).delete() + db.oid_associations.insert(server_url = server_url, + handle = association.handle, + secret = association.secret, + issued = association.issued, + lifetime = association.lifetime, + assoc_type = association.assoc_type), 'insert '*10 + + def getAssociation(self, server_url, handle=None): + """ + Return the association for server_url and handle. If handle is + not None return the latests associations for that server_url. + Return None if no association can be found. + """ + + db = self.database + query = (db.oid_associations.server_url == server_url) + if handle: + query &= (db.oid_associations.handle == handle) + rows = db(query).select(orderby=db.oid_associations.issued) + keep_assoc, _ = self._removeExpiredAssocations(rows) + if len(keep_assoc) == 0: + return None + else: + assoc = keep_assoc.pop() # pop the last one as it should be the latest one + return Association(assoc['handle'], + assoc['secret'], + assoc['issued'], + assoc['lifetime'], + assoc['assoc_type']) + + def removeAssociation(self, server_url, handle): + db = self.database + query = (db.oid_associations.server_url == server_url) & (db.oid_associations.handle == handle) + return db(query).delete() != None + + def useNonce(self, server_url, timestamp, salt): + """ + This method returns Falase if a nonce has been used before or its + timestamp is not current. + """ + + db = self.database + if abs(timestamp - time.time()) > nonce.SKEW: + return False + query = (db.oid_nonces.server_url == server_url) & (db.oid_nonces.timestamp == timestamp) & (db.oid_nonces.salt == salt) + if db(query).count() > 0: + return False + else: + db.oid_nonces.insert(server_url = server_url, + timestamp = timestamp, + salt = salt) + return True + + def _removeExpiredAssocations(self, rows): + """ + This helper function is not part of the interface. Given a list of + association rows it checks which associations have expired and + deletes them from the db. It returns a tuple of the form + ([valid_assoc], no_of_expired_assoc_deleted). + """ + + db = self.database + keep_assoc = [] + remove_assoc = [] + t1970 = time.time() + for r in rows: + if r['issued'] + r['lifetime'] < t1970: + remove_assoc.append(r) + else: + keep_assoc.append(r) + for r in remove_assoc: + del db.oid_associations[r['id']] + return (keep_assoc, len(remove_assoc)) # return tuple (list of valid associations, number of deleted associations) + + def cleanupNonces(self): + """ + Remove expired nonce entries from DB and return the number + of entries deleted. + """ + + db = self.database + query = (db.oid_nonces.timestamp < time.time() - nonce.SKEW) + return db(query).delete() + + def cleanupAssociations(self): + """ + Remove expired associations from db and return the number + of entries deleted. + """ + + db = self.database + query = (db.oid_associations.id > 0) + return self._removeExpiredAssocations(db(query).select())[1] #return number of assoc removed + + def cleanup(self): + """ + This method should be run periodically to free the db from + expired nonce and association entries. + """ + + return self.cleanupNonces(), self.cleanupAssociations() + ADDED gluon/contrib/login_methods/pam_auth.py Index: gluon/contrib/login_methods/pam_auth.py ================================================================== --- gluon/contrib/login_methods/pam_auth.py +++ gluon/contrib/login_methods/pam_auth.py @@ -0,0 +1,13 @@ +from gluon.contrib.pam import authenticate + +def pam_auth(): + """ + to use pam_login: + from gluon.contrib.login_methods.pam_auth import pam_auth + auth.settings.login_methods.append(pam_auth()) + """ + + def pam_auth_aux(username, password): + return authenticate(username, password) + + return pam_auth_aux ADDED gluon/contrib/login_methods/rpx_account.py Index: gluon/contrib/login_methods/rpx_account.py ================================================================== --- gluon/contrib/login_methods/rpx_account.py +++ gluon/contrib/login_methods/rpx_account.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python +# coding: utf8 + +""" + RPX Authentication for web2py + Developed by Nathan Freeze (Copyright © 2009) + Email + Modified by Massimo Di Pierro + + This file contains code to allow using RPXNow.com (now Jainrain.com) + services with web2py +""" + +import re +import urllib +from gluon.html import * +from gluon.tools import fetch +from gluon.storage import Storage +import gluon.contrib.simplejson as json + +class RPXAccount(object): + + """ + from gluon.contrib.login_methods.rpx_account import RPXAccount + auth.settings.actions_disabled=['register','change_password','request_reset_password'] + auth.settings.login_form = RPXAccount(request, + api_key="...", + domain="...", + url = "http://localhost:8000/%s/default/user/login" % request.application) + """ + + def __init__(self, + request, + api_key = "", + domain = "", + url = "", + embed = True, + auth_url = "https://rpxnow.com/api/v2/auth_info", + language= "en", + prompt='rpx', + on_login_failure = None, + ): + + self.request=request + self.api_key=api_key + self.embed = embed + self.auth_url = auth_url + self.domain = domain + self.token_url = url + self.language = language + self.profile = None + self.prompt = prompt + self.on_login_failure = on_login_failure + self.mappings = Storage() + + self.mappings.Facebook = lambda profile:\ + dict(registration_id = profile.get("identifier",""), + username = profile.get("preferredUsername",""), + email = profile.get("email",""), + first_name = profile.get("name","").get("givenName",""), + last_name = profile.get("name","").get("familyName","")) + self.mappings.Google = lambda profile:\ + dict(registration_id=profile.get("identifier",""), + username=profile.get("preferredUsername",""), + email=profile.get("email",""), + first_name=profile.get("name","").get("givenName",""), + last_name=profile.get("name","").get("familyName","")) + self.mappings.default = lambda profile:\ + dict(registration_id=profile.get("identifier",""), + username=profile.get("preferredUsername",""), + email=profile.get("email",""), + first_name=profile.get("preferredUsername",""), + last_name='') + + def get_user(self): + request = self.request + if request.vars.token: + user = Storage() + data = urllib.urlencode(dict(apiKey = self.api_key, token=request.vars.token)) + auth_info_json = fetch(self.auth_url+'?'+data) + auth_info = json.loads(auth_info_json) + + if auth_info['stat'] == 'ok': + self.profile = auth_info['profile'] + provider = re.sub('[^\w\-]','',self.profile['providerName']) + user = self.mappings.get(provider,self.mappings.default)(self.profile) + return user + elif self.on_login_failure: + redirect(self.on_login_failure) + return None + + def login_form(self): + request = self.request + args = request.args + if self.embed: + JANRAIN_URL = \ + "https://%s.rpxnow.com/openid/embed?token_url=%s&language_preference=%s" + rpxform = IFRAME(_src=JANRAIN_URL % (self.domain,self.token_url,self.language), + _scrolling="no", + _frameborder="no", + _style="width:400px;height:240px;") + else: + JANRAIN_URL = \ + "https://%s.rpxnow.com/openid/v2/signin?token_url=%s" + rpxform = DIV(SCRIPT(_src="https://rpxnow.com/openid/v2/widget", + _type="text/javascript"), + SCRIPT("RPXNOW.overlay = true;", + "RPXNOW.language_preference = '%s';" % self.language, + "RPXNOW.realm = '%s';" % self.domain, + "RPXNOW.token_url = '%s';" % self.token_url, + "RPXNOW.show();", + _type="text/javascript")) + return rpxform ADDED gluon/contrib/markdown/LICENSE Index: gluon/contrib/markdown/LICENSE ================================================================== --- gluon/contrib/markdown/LICENSE +++ gluon/contrib/markdown/LICENSE @@ -0,0 +1,1 @@ +markdown2.py is released under MIT license. ADDED gluon/contrib/markdown/__init__.py Index: gluon/contrib/markdown/__init__.py ================================================================== --- gluon/contrib/markdown/__init__.py +++ gluon/contrib/markdown/__init__.py @@ -0,0 +1,16 @@ +from markdown2 import * +from gluon.html import XML + +def WIKI(text, encoding="utf8", safe_mode='escape', html4tags=False, **attributes): + if not text: + test = '' + if attributes.has_key('extras'): + extras = attributes['extras'] + del attributes['extras'] + else: + extras=None + text = text.decode(encoding,'replace') + + return XML(markdown(text,extras=extras, + safe_mode=safe_mode, html4tags=html4tags)\ + .encode(encoding,'xmlcharrefreplace'),**attributes) ADDED gluon/contrib/markdown/markdown2.py Index: gluon/contrib/markdown/markdown2.py ================================================================== --- gluon/contrib/markdown/markdown2.py +++ gluon/contrib/markdown/markdown2.py @@ -0,0 +1,1889 @@ +#!/usr/bin/env python +# Copyright (c) 2007-2008 ActiveState Corp. +# License: MIT (http://www.opensource.org/licenses/mit-license.php) + +r"""A fast and complete Python implementation of Markdown. + +[from http://daringfireball.net/projects/markdown/] +> Markdown is a text-to-HTML filter; it translates an easy-to-read / +> easy-to-write structured text format into HTML. Markdown's text +> format is most similar to that of plain text email, and supports +> features such as headers, *emphasis*, code blocks, blockquotes, and +> links. +> +> Markdown's syntax is designed not as a generic markup language, but +> specifically to serve as a front-end to (X)HTML. You can use span-level +> HTML tags anywhere in a Markdown document, and you can use block level +> HTML tags (like
    and as well). + +Module usage: + + >>> import markdown2 + >>> markdown2.markdown("*boo!*") # or use `html = markdown_path(PATH)` + u'

    boo!

    \n' + + >>> markdowner = Markdown() + >>> markdowner.convert("*boo!*") + u'

    boo!

    \n' + >>> markdowner.convert("**boom!**") + u'

    boom!

    \n' + +This implementation of Markdown implements the full "core" syntax plus a +number of extras (e.g., code syntax coloring, footnotes) as described on +. +""" + +cmdln_desc = """A fast and complete Python implementation of Markdown, a +text-to-HTML conversion tool for web writers. +""" + +# Dev Notes: +# - There is already a Python markdown processor +# (http://www.freewisdom.org/projects/python-markdown/). +# - Python's regex syntax doesn't have '\z', so I'm using '\Z'. I'm +# not yet sure if there implications with this. Compare 'pydoc sre' +# and 'perldoc perlre'. + +__version_info__ = (1, 0, 1, 16) # first three nums match Markdown.pl +__version__ = '1.0.1.16' +__author__ = "Trent Mick" + +import os +import sys +from pprint import pprint +import re +import logging +try: + from hashlib import md5 +except ImportError: + from md5 import md5 +import optparse +from random import random, randint +import codecs +from urllib import quote + + + +#---- Python version compat + +if sys.version_info[:2] < (2,4): + from sets import Set as set + def reversed(sequence): + for i in sequence[::-1]: + yield i + def _unicode_decode(s, encoding, errors='xmlcharrefreplace'): + return unicode(s, encoding, errors) +else: + def _unicode_decode(s, encoding, errors='strict'): + return s.decode(encoding, errors) + + +#---- globals + +DEBUG = False +log = logging.getLogger("markdown") + +DEFAULT_TAB_WIDTH = 4 + + +try: + import uuid +except ImportError: + SECRET_SALT = str(randint(0, 1000000)) +else: + SECRET_SALT = str(uuid.uuid4()) +def _hash_ascii(s): + #return md5(s).hexdigest() # Markdown.pl effectively does this. + return 'md5-' + md5(SECRET_SALT + s).hexdigest() +def _hash_text(s): + return 'md5-' + md5(SECRET_SALT + s.encode("utf-8")).hexdigest() + +# Table of hash values for escaped characters: +g_escape_table = dict([(ch, _hash_ascii(ch)) + for ch in '\\`*_{}[]()>#+-.!']) + + + +#---- exceptions + +class MarkdownError(Exception): + pass + + + +#---- public api + +def markdown_path(path, encoding="utf-8", + html4tags=False, tab_width=DEFAULT_TAB_WIDTH, + safe_mode=None, extras=None, link_patterns=None, + use_file_vars=False): + fp = codecs.open(path, 'r', encoding) + try: + text = fp.read() + finally: + fp.close() + return Markdown(html4tags=html4tags, tab_width=tab_width, + safe_mode=safe_mode, extras=extras, + link_patterns=link_patterns, + use_file_vars=use_file_vars).convert(text) + +def markdown(text, html4tags=False, tab_width=DEFAULT_TAB_WIDTH, + safe_mode=None, extras=None, link_patterns=None, + use_file_vars=False): + return Markdown(html4tags=html4tags, tab_width=tab_width, + safe_mode=safe_mode, extras=extras, + link_patterns=link_patterns, + use_file_vars=use_file_vars).convert(text) + +class Markdown(object): + # The dict of "extras" to enable in processing -- a mapping of + # extra name to argument for the extra. Most extras do not have an + # argument, in which case the value is None. + # + # This can be set via (a) subclassing and (b) the constructor + # "extras" argument. + extras = None + + urls = None + titles = None + html_blocks = None + html_spans = None + html_removed_text = "[HTML_REMOVED]" # for compat with markdown.py + + # Used to track when we're inside an ordered or unordered list + # (see _ProcessListItems() for details): + list_level = 0 + + _ws_only_line_re = re.compile(r"^[ \t]+$", re.M) + + def __init__(self, html4tags=False, tab_width=4, safe_mode=None, + extras=None, link_patterns=None, use_file_vars=False): + if html4tags: + self.empty_element_suffix = ">" + else: + self.empty_element_suffix = " />" + self.tab_width = tab_width + + # For compatibility with earlier markdown2.py and with + # markdown.py's safe_mode being a boolean, + # safe_mode == True -> "replace" + if safe_mode is True: + self.safe_mode = "replace" + else: + self.safe_mode = safe_mode + + if self.extras is None: + self.extras = {} + elif not isinstance(self.extras, dict): + self.extras = dict([(e, None) for e in self.extras]) + if extras: + if not isinstance(extras, dict): + extras = dict([(e, None) for e in extras]) + self.extras.update(extras) + assert isinstance(self.extras, dict) + self._instance_extras = self.extras.copy() + self.link_patterns = link_patterns + self.use_file_vars = use_file_vars + self._outdent_re = re.compile(r'^(\t|[ ]{1,%d})' % tab_width, re.M) + + def reset(self): + self.urls = {} + self.titles = {} + self.html_blocks = {} + self.html_spans = {} + self.list_level = 0 + self.extras = self._instance_extras.copy() + if "footnotes" in self.extras: + self.footnotes = {} + self.footnote_ids = [] + + def convert(self, text): + """Convert the given text.""" + # Main function. The order in which other subs are called here is + # essential. Link and image substitutions need to happen before + # _EscapeSpecialChars(), so that any *'s or _'s in the + # and tags get encoded. + + # Clear the global hashes. If we don't clear these, you get conflicts + # from other articles when generating a page which contains more than + # one article (e.g. an index page that shows the N most recent + # articles): + self.reset() + + if not isinstance(text, unicode): + #TODO: perhaps shouldn't presume UTF-8 for string input? + text = unicode(text, 'utf-8') + + if self.use_file_vars: + # Look for emacs-style file variable hints. + emacs_vars = self._get_emacs_vars(text) + if "markdown-extras" in emacs_vars: + splitter = re.compile("[ ,]+") + for e in splitter.split(emacs_vars["markdown-extras"]): + if '=' in e: + ename, earg = e.split('=', 1) + try: + earg = int(earg) + except ValueError: + pass + else: + ename, earg = e, None + self.extras[ename] = earg + + # Standardize line endings: + text = re.sub("\r\n|\r", "\n", text) + + # Make sure $text ends with a couple of newlines: + text += "\n\n" + + # Convert all tabs to spaces. + text = self._detab(text) + + # Strip any lines consisting only of spaces and tabs. + # This makes subsequent regexen easier to write, because we can + # match consecutive blank lines with /\n+/ instead of something + # contorted like /[ \t]*\n+/ . + text = self._ws_only_line_re.sub("", text) + + if self.safe_mode: + text = self._hash_html_spans(text) + + # Turn block-level HTML blocks into hash entries + text = self._hash_html_blocks(text, raw=True) + + # Strip link definitions, store in hashes. + if "footnotes" in self.extras: + # Must do footnotes first because an unlucky footnote defn + # looks like a link defn: + # [^4]: this "looks like a link defn" + text = self._strip_footnote_definitions(text) + text = self._strip_link_definitions(text) + + text = self._run_block_gamut(text) + + if "footnotes" in self.extras: + text = self._add_footnotes(text) + + text = self._unescape_special_chars(text) + + if self.safe_mode: + text = self._unhash_html_spans(text) + + text += "\n" + return text + + _emacs_oneliner_vars_pat = re.compile(r"-\*-\s*([^\r\n]*?)\s*-\*-", re.UNICODE) + # This regular expression is intended to match blocks like this: + # PREFIX Local Variables: SUFFIX + # PREFIX mode: Tcl SUFFIX + # PREFIX End: SUFFIX + # Some notes: + # - "[ \t]" is used instead of "\s" to specifically exclude newlines + # - "(\r\n|\n|\r)" is used instead of "$" because the sre engine does + # not like anything other than Unix-style line terminators. + _emacs_local_vars_pat = re.compile(r"""^ + (?P(?:[^\r\n|\n|\r])*?) + [\ \t]*Local\ Variables:[\ \t]* + (?P.*?)(?:\r\n|\n|\r) + (?P.*?\1End:) + """, re.IGNORECASE | re.MULTILINE | re.DOTALL | re.VERBOSE) + + def _get_emacs_vars(self, text): + """Return a dictionary of emacs-style local variables. + + Parsing is done loosely according to this spec (and according to + some in-practice deviations from this): + http://www.gnu.org/software/emacs/manual/html_node/emacs/Specifying-File-Variables.html#Specifying-File-Variables + """ + emacs_vars = {} + SIZE = pow(2, 13) # 8kB + + # Search near the start for a '-*-'-style one-liner of variables. + head = text[:SIZE] + if "-*-" in head: + match = self._emacs_oneliner_vars_pat.search(head) + if match: + emacs_vars_str = match.group(1) + assert '\n' not in emacs_vars_str + emacs_var_strs = [s.strip() for s in emacs_vars_str.split(';') + if s.strip()] + if len(emacs_var_strs) == 1 and ':' not in emacs_var_strs[0]: + # While not in the spec, this form is allowed by emacs: + # -*- Tcl -*- + # where the implied "variable" is "mode". This form + # is only allowed if there are no other variables. + emacs_vars["mode"] = emacs_var_strs[0].strip() + else: + for emacs_var_str in emacs_var_strs: + try: + variable, value = emacs_var_str.strip().split(':', 1) + except ValueError: + log.debug("emacs variables error: malformed -*- " + "line: %r", emacs_var_str) + continue + # Lowercase the variable name because Emacs allows "Mode" + # or "mode" or "MoDe", etc. + emacs_vars[variable.lower()] = value.strip() + + tail = text[-SIZE:] + if "Local Variables" in tail: + match = self._emacs_local_vars_pat.search(tail) + if match: + prefix = match.group("prefix") + suffix = match.group("suffix") + lines = match.group("content").splitlines(0) + #print "prefix=%r, suffix=%r, content=%r, lines: %s"\ + # % (prefix, suffix, match.group("content"), lines) + + # Validate the Local Variables block: proper prefix and suffix + # usage. + for i, line in enumerate(lines): + if not line.startswith(prefix): + log.debug("emacs variables error: line '%s' " + "does not use proper prefix '%s'" + % (line, prefix)) + return {} + # Don't validate suffix on last line. Emacs doesn't care, + # neither should we. + if i != len(lines)-1 and not line.endswith(suffix): + log.debug("emacs variables error: line '%s' " + "does not use proper suffix '%s'" + % (line, suffix)) + return {} + + # Parse out one emacs var per line. + continued_for = None + for line in lines[:-1]: # no var on the last line ("PREFIX End:") + if prefix: line = line[len(prefix):] # strip prefix + if suffix: line = line[:-len(suffix)] # strip suffix + line = line.strip() + if continued_for: + variable = continued_for + if line.endswith('\\'): + line = line[:-1].rstrip() + else: + continued_for = None + emacs_vars[variable] += ' ' + line + else: + try: + variable, value = line.split(':', 1) + except ValueError: + log.debug("local variables error: missing colon " + "in local variables entry: '%s'" % line) + continue + # Do NOT lowercase the variable name, because Emacs only + # allows "mode" (and not "Mode", "MoDe", etc.) in this block. + value = value.strip() + if value.endswith('\\'): + value = value[:-1].rstrip() + continued_for = variable + else: + continued_for = None + emacs_vars[variable] = value + + # Unquote values. + for var, val in emacs_vars.items(): + if len(val) > 1 and (val.startswith('"') and val.endswith('"') + or val.startswith('"') and val.endswith('"')): + emacs_vars[var] = val[1:-1] + + return emacs_vars + + # Cribbed from a post by Bart Lateur: + # + _detab_re = re.compile(r'(.*?)\t', re.M) + def _detab_sub(self, match): + g1 = match.group(1) + return g1 + (' ' * (self.tab_width - len(g1) % self.tab_width)) + def _detab(self, text): + r"""Remove (leading?) tabs from a file. + + >>> m = Markdown() + >>> m._detab("\tfoo") + ' foo' + >>> m._detab(" \tfoo") + ' foo' + >>> m._detab("\t foo") + ' foo' + >>> m._detab(" foo") + ' foo' + >>> m._detab(" foo\n\tbar\tblam") + ' foo\n bar blam' + """ + if '\t' not in text: + return text + return self._detab_re.subn(self._detab_sub, text)[0] + + _block_tags_a = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del' + _strict_tag_block_re = re.compile(r""" + ( # save in \1 + ^ # start of line (with re.M) + <(%s) # start tag = \2 + \b # word break + (.*\n)*? # any number of lines, minimally matching + # the matching end tag + [ \t]* # trailing spaces/tabs + (?=\n+|\Z) # followed by a newline or end of document + ) + """ % _block_tags_a, + re.X | re.M) + + _block_tags_b = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math' + _liberal_tag_block_re = re.compile(r""" + ( # save in \1 + ^ # start of line (with re.M) + <(%s) # start tag = \2 + \b # word break + (.*\n)*? # any number of lines, minimally matching + .* # the matching end tag + [ \t]* # trailing spaces/tabs + (?=\n+|\Z) # followed by a newline or end of document + ) + """ % _block_tags_b, + re.X | re.M) + + def _hash_html_block_sub(self, match, raw=False): + html = match.group(1) + if raw and self.safe_mode: + html = self._sanitize_html(html) + key = _hash_text(html) + self.html_blocks[key] = html + return "\n\n" + key + "\n\n" + + def _hash_html_blocks(self, text, raw=False): + """Hashify HTML blocks + + We only want to do this for block-level HTML tags, such as headers, + lists, and tables. That's because we still want to wrap

    s around + "paragraphs" that are wrapped in non-block-level tags, such as anchors, + phrase emphasis, and spans. The list of tags we're looking for is + hard-coded. + + @param raw {boolean} indicates if these are raw HTML blocks in + the original source. It makes a difference in "safe" mode. + """ + if '<' not in text: + return text + + # Pass `raw` value into our calls to self._hash_html_block_sub. + hash_html_block_sub = _curry(self._hash_html_block_sub, raw=raw) + + # First, look for nested blocks, e.g.: + #

    + #
    + # tags for inner block must be indented. + #
    + #
    + # + # The outermost tags must start at the left margin for this to match, and + # the inner nested divs must be indented. + # We need to do this before the next, more liberal match, because the next + # match will start at the first `
    ` and stop at the first `
    `. + text = self._strict_tag_block_re.sub(hash_html_block_sub, text) + + # Now match more liberally, simply from `\n` to `\n` + text = self._liberal_tag_block_re.sub(hash_html_block_sub, text) + + # Special case just for
    . It was easier to make a special + # case than to make the other regex more complicated. + if "", start_idx) + 3 + except ValueError, ex: + break + + # Start position for next comment block search. + start = end_idx + + # Validate whitespace before comment. + if start_idx: + # - Up to `tab_width - 1` spaces before start_idx. + for i in range(self.tab_width - 1): + if text[start_idx - 1] != ' ': + break + start_idx -= 1 + if start_idx == 0: + break + # - Must be preceded by 2 newlines or hit the start of + # the document. + if start_idx == 0: + pass + elif start_idx == 1 and text[0] == '\n': + start_idx = 0 # to match minute detail of Markdown.pl regex + elif text[start_idx-2:start_idx] == '\n\n': + pass + else: + break + + # Validate whitespace after comment. + # - Any number of spaces and tabs. + while end_idx < len(text): + if text[end_idx] not in ' \t': + break + end_idx += 1 + # - Must be following by 2 newlines or hit end of text. + if text[end_idx:end_idx+2] not in ('', '\n', '\n\n'): + continue + + # Escape and hash (must match `_hash_html_block_sub`). + html = text[start_idx:end_idx] + if raw and self.safe_mode: + html = self._sanitize_html(html) + key = _hash_text(html) + self.html_blocks[key] = html + text = text[:start_idx] + "\n\n" + key + "\n\n" + text[end_idx:] + + if "xml" in self.extras: + # Treat XML processing instructions and namespaced one-liner + # tags as if they were block HTML tags. E.g., if standalone + # (i.e. are their own paragraph), the following do not get + # wrapped in a

    tag: + # + # + # + _xml_oneliner_re = _xml_oneliner_re_from_tab_width(self.tab_width) + text = _xml_oneliner_re.sub(hash_html_block_sub, text) + + return text + + def _strip_link_definitions(self, text): + # Strips link definitions from text, stores the URLs and titles in + # hash references. + less_than_tab = self.tab_width - 1 + + # Link defs are in the form: + # [id]: url "optional title" + _link_def_re = re.compile(r""" + ^[ ]{0,%d}\[(.+)\]: # id = \1 + [ \t]* + \n? # maybe *one* newline + [ \t]* + ? # url = \2 + [ \t]* + (?: + \n? # maybe one newline + [ \t]* + (?<=\s) # lookbehind for whitespace + ['"(] + ([^\n]*) # title = \3 + ['")] + [ \t]* + )? # title is optional + (?:\n+|\Z) + """ % less_than_tab, re.X | re.M | re.U) + return _link_def_re.sub(self._extract_link_def_sub, text) + + def _extract_link_def_sub(self, match): + id, url, title = match.groups() + key = id.lower() # Link IDs are case-insensitive + self.urls[key] = self._encode_amps_and_angles(url) + if title: + self.titles[key] = title.replace('"', '"') + return "" + + def _extract_footnote_def_sub(self, match): + id, text = match.groups() + text = _dedent(text, skip_first_line=not text.startswith('\n')).strip() + normed_id = re.sub(r'\W', '-', id) + # Ensure footnote text ends with a couple newlines (for some + # block gamut matches). + self.footnotes[normed_id] = text + "\n\n" + return "" + + def _strip_footnote_definitions(self, text): + """A footnote definition looks like this: + + [^note-id]: Text of the note. + + May include one or more indented paragraphs. + + Where, + - The 'note-id' can be pretty much anything, though typically it + is the number of the footnote. + - The first paragraph may start on the next line, like so: + + [^note-id]: + Text of the note. + """ + less_than_tab = self.tab_width - 1 + footnote_def_re = re.compile(r''' + ^[ ]{0,%d}\[\^(.+)\]: # id = \1 + [ \t]* + ( # footnote text = \2 + # First line need not start with the spaces. + (?:\s*.*\n+) + (?: + (?:[ ]{%d} | \t) # Subsequent lines must be indented. + .*\n+ + )* + ) + # Lookahead for non-space at line-start, or end of doc. + (?:(?=^[ ]{0,%d}\S)|\Z) + ''' % (less_than_tab, self.tab_width, self.tab_width), + re.X | re.M) + return footnote_def_re.sub(self._extract_footnote_def_sub, text) + + + _hr_res = [ + re.compile(r"^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$", re.M), + re.compile(r"^[ ]{0,2}([ ]?\-[ ]?){3,}[ \t]*$", re.M), + re.compile(r"^[ ]{0,2}([ ]?\_[ ]?){3,}[ \t]*$", re.M), + ] + + def _run_block_gamut(self, text): + # These are all the transformations that form block-level + # tags like paragraphs, headers, and list items. + + text = self._do_headers(text) + + # Do Horizontal Rules: + hr = "\n tags around block-level tags. + text = self._hash_html_blocks(text) + + text = self._form_paragraphs(text) + + return text + + def _pyshell_block_sub(self, match): + lines = match.group(0).splitlines(0) + _dedentlines(lines) + indent = ' ' * self.tab_width + s = ('\n' # separate from possible cuddled paragraph + + indent + ('\n'+indent).join(lines) + + '\n\n') + return s + + def _prepare_pyshell_blocks(self, text): + """Ensure that Python interactive shell sessions are put in + code blocks -- even if not properly indented. + """ + if ">>>" not in text: + return text + + less_than_tab = self.tab_width - 1 + _pyshell_block_re = re.compile(r""" + ^([ ]{0,%d})>>>[ ].*\n # first line + ^(\1.*\S+.*\n)* # any number of subsequent lines + ^\n # ends with a blank line + """ % less_than_tab, re.M | re.X) + + return _pyshell_block_re.sub(self._pyshell_block_sub, text) + + def _run_span_gamut(self, text): + # These are all the transformations that occur *within* block-level + # tags like paragraphs, headers, and list items. + + text = self._do_code_spans(text) + + text = self._escape_special_chars(text) + + # Process anchor and image tags. + text = self._do_links(text) + + # Make links out of things like `` + # Must come after _do_links(), because you can use < and > + # delimiters in inline links like [this](). + text = self._do_auto_links(text) + + if "link-patterns" in self.extras: + text = self._do_link_patterns(text) + + text = self._encode_amps_and_angles(text) + + text = self._do_italics_and_bold(text) + + # Do hard breaks: + text = re.sub(r" {2,}\n", " + | + # auto-link (e.g., ) + <\w+[^>]*> + | + # comment + | + <\?.*?\?> # processing instruction + ) + """, re.X) + + def _escape_special_chars(self, text): + # Python markdown note: the HTML tokenization here differs from + # that in Markdown.pl, hence the behaviour for subtle cases can + # differ (I believe the tokenizer here does a better job because + # it isn't susceptible to unmatched '<' and '>' in HTML tags). + # Note, however, that '>' is not allowed in an auto-link URL + # here. + escaped = [] + is_html_markup = False + for token in self._sorta_html_tokenize_re.split(text): + if is_html_markup: + # Within tags/HTML-comments/auto-links, encode * and _ + # so they don't conflict with their use in Markdown for + # italics and strong. We're replacing each such + # character with its corresponding MD5 checksum value; + # this is likely overkill, but it should prevent us from + # colliding with the escape values by accident. + escaped.append(token.replace('*', g_escape_table['*']) + .replace('_', g_escape_table['_'])) + else: + escaped.append(self._encode_backslash_escapes(token)) + is_html_markup = not is_html_markup + return ''.join(escaped) + + def _hash_html_spans(self, text): + # Used for safe_mode. + + def _is_auto_link(s): + if ':' in s and self._auto_link_re.match(s): + return True + elif '@' in s and self._auto_email_link_re.match(s): + return True + return False + + tokens = [] + is_html_markup = False + for token in self._sorta_html_tokenize_re.split(text): + if is_html_markup and not _is_auto_link(token): + sanitized = self._sanitize_html(token) + key = _hash_text(sanitized) + self.html_spans[key] = sanitized + tokens.append(key) + else: + tokens.append(token) + is_html_markup = not is_html_markup + return ''.join(tokens) + + def _unhash_html_spans(self, text): + for key, sanitized in self.html_spans.items(): + text = text.replace(key, sanitized) + return text + + def _sanitize_html(self, s): + if self.safe_mode == "replace": + return self.html_removed_text + elif self.safe_mode == "escape": + replacements = [ + ('&', '&'), + ('<', '<'), + ('>', '>'), + ] + for before, after in replacements: + s = s.replace(before, after) + return s + else: + raise MarkdownError("invalid value for 'safe_mode': %r (must be " + "'escape' or 'replace')" % self.safe_mode) + + _tail_of_inline_link_re = re.compile(r''' + # Match tail of: [text](/url/) or [text](/url/ "title") + \( # literal paren + [ \t]* + (?P # \1 + <.*?> + | + .*? + ) + [ \t]* + ( # \2 + (['"]) # quote char = \3 + (?P.*?) + \3 # matching quote + )? # title is optional + \) + ''', re.X | re.S) + _tail_of_reference_link_re = re.compile(r''' + # Match tail of: [text][id] + [ ]? # one optional space + (?:\n[ ]*)? # one optional newline followed by spaces + \[ + (?P<id>.*?) + \] + ''', re.X | re.S) + + def _do_links(self, text): + """Turn Markdown link shortcuts into XHTML <a> and <img> tags. + + This is a combination of Markdown.pl's _DoAnchors() and + _DoImages(). They are done together because that simplified the + approach. It was necessary to use a different approach than + Markdown.pl because of the lack of atomic matching support in + Python's regex engine used in $g_nested_brackets. + """ + MAX_LINK_TEXT_SENTINEL = 3000 # markdown2 issue 24 + + # `anchor_allowed_pos` is used to support img links inside + # anchors, but not anchors inside anchors. An anchor's start + # pos must be `>= anchor_allowed_pos`. + anchor_allowed_pos = 0 + + curr_pos = 0 + while True: # Handle the next link. + # The next '[' is the start of: + # - an inline anchor: [text](url "title") + # - a reference anchor: [text][id] + # - an inline img: ![text](url "title") + # - a reference img: ![text][id] + # - a footnote ref: [^id] + # (Only if 'footnotes' extra enabled) + # - a footnote defn: [^id]: ... + # (Only if 'footnotes' extra enabled) These have already + # been stripped in _strip_footnote_definitions() so no + # need to watch for them. + # - a link definition: [id]: url "title" + # These have already been stripped in + # _strip_link_definitions() so no need to watch for them. + # - not markup: [...anything else... + try: + start_idx = text.index('[', curr_pos) + except ValueError: + break + text_length = len(text) + + # Find the matching closing ']'. + # Markdown.pl allows *matching* brackets in link text so we + # will here too. Markdown.pl *doesn't* currently allow + # matching brackets in img alt text -- we'll differ in that + # regard. + bracket_depth = 0 + for p in range(start_idx+1, min(start_idx+MAX_LINK_TEXT_SENTINEL, + text_length)): + ch = text[p] + if ch == ']': + bracket_depth -= 1 + if bracket_depth < 0: + break + elif ch == '[': + bracket_depth += 1 + else: + # Closing bracket not found within sentinel length. + # This isn't markup. + curr_pos = start_idx + 1 + continue + link_text = text[start_idx+1:p] + + # Possibly a footnote ref? + if "footnotes" in self.extras and link_text.startswith("^"): + normed_id = re.sub(r'\W', '-', link_text[1:]) + if normed_id in self.footnotes: + self.footnote_ids.append(normed_id) + result = '<sup class="footnote-ref" id="fnref-%s">' \ + '<a href="#fn-%s">%s</a></sup>' \ + % (normed_id, normed_id, len(self.footnote_ids)) + text = text[:start_idx] + result + text[p+1:] + else: + # This id isn't defined, leave the markup alone. + curr_pos = p+1 + continue + + # Now determine what this is by the remainder. + p += 1 + if p == text_length: + return text + + # Inline anchor or img? + if text[p] == '(': # attempt at perf improvement + match = self._tail_of_inline_link_re.match(text, p) + if match: + # Handle an inline anchor or img. + is_img = start_idx > 0 and text[start_idx-1] == "!" + if is_img: + start_idx -= 1 + + url, title = match.group("url"), match.group("title") + if url and url[0] == '<': + url = url[1:-1] # '<url>' -> 'url' + # We've got to encode these to avoid conflicting + # with italics/bold. + url = url.replace('*', g_escape_table['*']) \ + .replace('_', g_escape_table['_']) + if title: + title_str = ' title="%s"' \ + % title.replace('*', g_escape_table['*']) \ + .replace('_', g_escape_table['_']) \ + .replace('"', '"') + else: + title_str = '' + if is_img: + result = '<img src="%s" alt="%s"%s%s' \ + % (url.replace('"', '"'), + link_text.replace('"', '"'), + title_str, self.empty_element_suffix) + curr_pos = start_idx + len(result) + text = text[:start_idx] + result + text[match.end():] + elif start_idx >= anchor_allowed_pos: + result_head = '<a href="%s"%s>' % (url, title_str) + result = '%s%s</a>' % (result_head, link_text) + # <img> allowed from curr_pos on, <a> from + # anchor_allowed_pos on. + curr_pos = start_idx + len(result_head) + anchor_allowed_pos = start_idx + len(result) + text = text[:start_idx] + result + text[match.end():] + else: + # Anchor not allowed here. + curr_pos = start_idx + 1 + continue + + # Reference anchor or img? + else: + match = self._tail_of_reference_link_re.match(text, p) + if match: + # Handle a reference-style anchor or img. + is_img = start_idx > 0 and text[start_idx-1] == "!" + if is_img: + start_idx -= 1 + link_id = match.group("id").lower() + if not link_id: + link_id = link_text.lower() # for links like [this][] + if link_id in self.urls: + url = self.urls[link_id] + # We've got to encode these to avoid conflicting + # with italics/bold. + url = url.replace('*', g_escape_table['*']) \ + .replace('_', g_escape_table['_']) + title = self.titles.get(link_id) + if title: + title = title.replace('*', g_escape_table['*']) \ + .replace('_', g_escape_table['_']) + title_str = ' title="%s"' % title + else: + title_str = '' + if is_img: + result = '<img src="%s" alt="%s"%s%s' \ + % (url.replace('"', '"'), + link_text.replace('"', '"'), + title_str, self.empty_element_suffix) + curr_pos = start_idx + len(result) + text = text[:start_idx] + result + text[match.end():] + elif start_idx >= anchor_allowed_pos: + result = '<a href="%s"%s>%s</a>' \ + % (url, title_str, link_text) + result_head = '<a href="%s"%s>' % (url, title_str) + result = '%s%s</a>' % (result_head, link_text) + # <img> allowed from curr_pos on, <a> from + # anchor_allowed_pos on. + curr_pos = start_idx + len(result_head) + anchor_allowed_pos = start_idx + len(result) + text = text[:start_idx] + result + text[match.end():] + else: + # Anchor not allowed here. + curr_pos = start_idx + 1 + else: + # This id isn't defined, leave the markup alone. + curr_pos = match.end() + continue + + # Otherwise, it isn't markup. + curr_pos = start_idx + 1 + + return text + + + _setext_h_re = re.compile(r'^(.+)[ \t]*\n(=+|-+)[ \t]*\n+', re.M) + def _setext_h_sub(self, match): + n = {"=": 1, "-": 2}[match.group(2)[0]] + demote_headers = self.extras.get("demote-headers") + if demote_headers: + n = min(n + demote_headers, 6) + return "<h%d>%s</h%d>\n\n" \ + % (n, self._run_span_gamut(match.group(1)), n) + + _atx_h_re = re.compile(r''' + ^(\#{1,6}) # \1 = string of #'s + [ \t]* + (.+?) # \2 = Header text + [ \t]* + (?<!\\) # ensure not an escaped trailing '#' + \#* # optional closing #'s (not counted) + \n+ + ''', re.X | re.M) + def _atx_h_sub(self, match): + n = len(match.group(1)) + demote_headers = self.extras.get("demote-headers") + if demote_headers: + n = min(n + demote_headers, 6) + return "<h%d>%s</h%d>\n\n" \ + % (n, self._run_span_gamut(match.group(2)), n) + + def _do_headers(self, text): + # Setext-style headers: + # Header 1 + # ======== + # + # Header 2 + # -------- + text = self._setext_h_re.sub(self._setext_h_sub, text) + + # atx-style headers: + # # Header 1 + # ## Header 2 + # ## Header 2 with closing hashes ## + # ... + # ###### Header 6 + text = self._atx_h_re.sub(self._atx_h_sub, text) + + return text + + + _marker_ul_chars = '*+-' + _marker_any = r'(?:[%s]|\d+\.)' % _marker_ul_chars + _marker_ul = '(?:[%s])' % _marker_ul_chars + _marker_ol = r'(?:\d+\.)' + + def _list_sub(self, match): + lst = match.group(1) + lst_type = match.group(3) in self._marker_ul_chars and "ul" or "ol" + result = self._process_list_items(lst) + if self.list_level: + return "<%s>\n%s</%s>\n" % (lst_type, result, lst_type) + else: + return "<%s>\n%s</%s>\n\n" % (lst_type, result, lst_type) + + def _do_lists(self, text): + # Form HTML ordered (numbered) and unordered (bulleted) lists. + + for marker_pat in (self._marker_ul, self._marker_ol): + # Re-usable pattern to match any entire ul or ol list: + less_than_tab = self.tab_width - 1 + whole_list = r''' + ( # \1 = whole list + ( # \2 + [ ]{0,%d} + (%s) # \3 = first list item marker + [ \t]+ + ) + (?:.+?) + ( # \4 + \Z + | + \n{2,} + (?=\S) + (?! # Negative lookahead for another list item marker + [ \t]* + %s[ \t]+ + ) + ) + ) + ''' % (less_than_tab, marker_pat, marker_pat) + + # We use a different prefix before nested lists than top-level lists. + # See extended comment in _process_list_items(). + # + # Note: There's a bit of duplication here. My original implementation + # created a scalar regex pattern as the conditional result of the test on + # $g_list_level, and then only ran the $text =~ s{...}{...}egmx + # substitution once, using the scalar as the pattern. This worked, + # everywhere except when running under MT on my hosting account at Pair + # Networks. There, this caused all rebuilds to be killed by the reaper (or + # perhaps they crashed, but that seems incredibly unlikely given that the + # same script on the same server ran fine *except* under MT. I've spent + # more time trying to figure out why this is happening than I'd like to + # admit. My only guess, backed up by the fact that this workaround works, + # is that Perl optimizes the substition when it can figure out that the + # pattern will never change, and when this optimization isn't on, we run + # afoul of the reaper. Thus, the slightly redundant code to that uses two + # static s/// patterns rather than one conditional pattern. + + if self.list_level: + sub_list_re = re.compile("^"+whole_list, re.X | re.M | re.S) + text = sub_list_re.sub(self._list_sub, text) + else: + list_re = re.compile(r"(?:(?<=\n\n)|\A\n?)"+whole_list, + re.X | re.M | re.S) + text = list_re.sub(self._list_sub, text) + + return text + + _list_item_re = re.compile(r''' + (\n)? # leading line = \1 + (^[ \t]*) # leading whitespace = \2 + (%s) [ \t]+ # list marker = \3 + ((?:.+?) # list item text = \4 + (\n{1,2})) # eols = \5 + (?= \n* (\Z | \2 (%s) [ \t]+)) + ''' % (_marker_any, _marker_any), + re.M | re.X | re.S) + + _last_li_endswith_two_eols = False + def _list_item_sub(self, match): + item = match.group(4) + leading_line = match.group(1) + leading_space = match.group(2) + if leading_line or "\n\n" in item or self._last_li_endswith_two_eols: + item = self._run_block_gamut(self._outdent(item)) + else: + # Recursion for sub-lists: + item = self._do_lists(self._outdent(item)) + if item.endswith('\n'): + item = item[:-1] + item = self._run_span_gamut(item) + self._last_li_endswith_two_eols = (len(match.group(5)) == 2) + return "<li>%s</li>\n" % item + + def _process_list_items(self, list_str): + # Process the contents of a single ordered or unordered list, + # splitting it into individual list items. + + # The $g_list_level global keeps track of when we're inside a list. + # Each time we enter a list, we increment it; when we leave a list, + # we decrement. If it's zero, we're not in a list anymore. + # + # We do this because when we're not inside a list, we want to treat + # something like this: + # + # I recommend upgrading to version + # 8. Oops, now this line is treated + # as a sub-list. + # + # As a single paragraph, despite the fact that the second line starts + # with a digit-period-space sequence. + # + # Whereas when we're inside a list (or sub-list), that line will be + # treated as the start of a sub-list. What a kludge, huh? This is + # an aspect of Markdown's syntax that's hard to parse perfectly + # without resorting to mind-reading. Perhaps the solution is to + # change the syntax rules such that sub-lists must start with a + # starting cardinal number; e.g. "1." or "a.". + self.list_level += 1 + self._last_li_endswith_two_eols = False + list_str = list_str.rstrip('\n') + '\n' + list_str = self._list_item_re.sub(self._list_item_sub, list_str) + self.list_level -= 1 + return list_str + + def _get_pygments_lexer(self, lexer_name): + try: + from pygments import lexers, util + except ImportError: + return None + try: + return lexers.get_lexer_by_name(lexer_name) + except util.ClassNotFound: + return None + + def _color_with_pygments(self, codeblock, lexer, **formatter_opts): + import pygments + import pygments.formatters + + class HtmlCodeFormatter(pygments.formatters.HtmlFormatter): + def _wrap_code(self, inner): + """A function for use in a Pygments Formatter which + wraps in <code> tags. + """ + yield 0, "<code>" + for tup in inner: + yield tup + yield 0, "</code>" + + def wrap(self, source, outfile): + """Return the source with a code, pre, and div.""" + return self._wrap_div(self._wrap_pre(self._wrap_code(source))) + + formatter = HtmlCodeFormatter(cssclass="codehilite", **formatter_opts) + return pygments.highlight(codeblock, lexer, formatter) + + def _code_block_sub(self, match): + codeblock = match.group(1) + codeblock = self._outdent(codeblock) + codeblock = self._detab(codeblock) + codeblock = codeblock.lstrip('\n') # trim leading newlines + codeblock = codeblock.rstrip() # trim trailing whitespace + + if "code-color" in self.extras and codeblock.startswith(":::"): + lexer_name, rest = codeblock.split('\n', 1) + lexer_name = lexer_name[3:].strip() + lexer = self._get_pygments_lexer(lexer_name) + codeblock = rest.lstrip("\n") # Remove lexer declaration line. + if lexer: + formatter_opts = self.extras['code-color'] or {} + colored = self._color_with_pygments(codeblock, lexer, + **formatter_opts) + return "\n\n%s\n\n" % colored + + codeblock = self._encode_code(codeblock) + return "\n\n<pre><code>%s\n</code></pre>\n\n" % codeblock + + def _do_code_blocks(self, text): + """Process Markdown `<pre><code>` blocks.""" + code_block_re = re.compile(r''' + (?:\n\n|\A) + ( # $1 = the code block -- one or more lines, starting with a space/tab + (?: + (?:[ ]{%d} | \t) # Lines must start with a tab or a tab-width of spaces + .*\n+ + )+ + ) + ((?=^[ ]{0,%d}\S)|\Z) # Lookahead for non-space at line-start, or end of doc + ''' % (self.tab_width, self.tab_width), + re.M | re.X) + + return code_block_re.sub(self._code_block_sub, text) + + + # Rules for a code span: + # - backslash escapes are not interpreted in a code span + # - to include one or or a run of more backticks the delimiters must + # be a longer run of backticks + # - cannot start or end a code span with a backtick; pad with a + # space and that space will be removed in the emitted HTML + # See `test/tm-cases/escapes.text` for a number of edge-case + # examples. + _code_span_re = re.compile(r''' + (?<!\\) + (`+) # \1 = Opening run of ` + (?!`) # See Note A test/tm-cases/escapes.text + (.+?) # \2 = The code block + (?<!`) + \1 # Matching closer + (?!`) + ''', re.X | re.S) + + def _code_span_sub(self, match): + c = match.group(2).strip(" \t") + c = self._encode_code(c) + return "<code>%s</code>" % c + + def _do_code_spans(self, text): + # * Backtick quotes are used for <code></code> spans. + # + # * You can use multiple backticks as the delimiters if you want to + # include literal backticks in the code span. So, this input: + # + # Just type ``foo `bar` baz`` at the prompt. + # + # Will translate to: + # + # <p>Just type <code>foo `bar` baz</code> at the prompt.</p> + # + # There's no arbitrary limit to the number of backticks you + # can use as delimters. If you need three consecutive backticks + # in your code, use four for delimiters, etc. + # + # * You can use spaces to get literal backticks at the edges: + # + # ... type `` `bar` `` ... + # + # Turns to: + # + # ... type <code>`bar`</code> ... + return self._code_span_re.sub(self._code_span_sub, text) + + def _encode_code(self, text): + """Encode/escape certain characters inside Markdown code runs. + The point is that in code, these characters are literals, + and lose their special Markdown meanings. + """ + replacements = [ + # Encode all ampersands; HTML entities are not + # entities within a Markdown code span. + ('&', '&'), + # Do the angle bracket song and dance: + ('<', '<'), + ('>', '>'), + # Now, escape characters that are magic in Markdown: + ('*', g_escape_table['*']), + ('_', g_escape_table['_']), + ('{', g_escape_table['{']), + ('}', g_escape_table['}']), + ('[', g_escape_table['[']), + (']', g_escape_table[']']), + ('\\', g_escape_table['\\']), + ] + for before, after in replacements: + text = text.replace(before, after) + return text + + _strong_re = re.compile(r"(?<!\w)(\*\*|__)(?=\S)(.+?[*_]*)(?<=\S)\1(?!\w)", re.S) + _em_re = re.compile(r"(?<!\w)(\*|_)(?=\S)(.+?)(?<=\S)\1(?!\w)", re.S) + _code_friendly_strong_re = re.compile(r"\*\*(?=\S)(.+?[*_]*)(?<=\S)\*\*", re.S) + _code_friendly_em_re = re.compile(r"\*(?=\S)(.+?)(?<=\S)\*", re.S) + def _do_italics_and_bold(self, text): + # <strong> must go first: + if "code-friendly" in self.extras: + text = self._code_friendly_strong_re.sub(r"<strong>\1</strong>", text) + text = self._code_friendly_em_re.sub(r"<em>\1</em>", text) + else: + text = self._strong_re.sub(r"<strong>\2</strong>", text) + text = self._em_re.sub(r"<em>\2</em>", text) + return text + + + _block_quote_re = re.compile(r''' + ( # Wrap whole match in \1 + ( + ^[ \t]*>[ \t]? # '>' at the start of a line + .+\n # rest of the first line + (.+\n)* # subsequent consecutive lines + \n* # blanks + )+ + ) + ''', re.M | re.X) + _bq_one_level_re = re.compile('^[ \t]*>[ \t]?', re.M); + + _html_pre_block_re = re.compile(r'(\s*<pre>.+?</pre>)', re.S) + def _dedent_two_spaces_sub(self, match): + return re.sub(r'(?m)^ ', '', match.group(1)) + + def _block_quote_sub(self, match): + bq = match.group(1) + bq = self._bq_one_level_re.sub('', bq) # trim one level of quoting + bq = self._ws_only_line_re.sub('', bq) # trim whitespace-only lines + bq = self._run_block_gamut(bq) # recurse + + bq = re.sub('(?m)^', ' ', bq) + # These leading spaces screw with <pre> content, so we need to fix that: + bq = self._html_pre_block_re.sub(self._dedent_two_spaces_sub, bq) + + return "<blockquote>\n%s\n</blockquote>\n\n" % bq + + def _do_block_quotes(self, text): + if '>' not in text: + return text + return self._block_quote_re.sub(self._block_quote_sub, text) + + def _form_paragraphs(self, text): + # Strip leading and trailing lines: + text = text.strip('\n') + + # Wrap <p> tags. + grafs = re.split(r"\n{2,}", text) + for i, graf in enumerate(grafs): + if graf in self.html_blocks: + # Unhashify HTML blocks + grafs[i] = self.html_blocks[graf] + else: + # Wrap <p> tags. + graf = self._run_span_gamut(graf) + grafs[i] = "<p>" + graf.lstrip(" \t") + "</p>" + + return "\n\n".join(grafs) + + def _add_footnotes(self, text): + if self.footnotes: + footer = [ + '<div class="footnotes">', + '<hr' + self.empty_element_suffix, + '<ol>', + ] + for i, id in enumerate(self.footnote_ids): + if i != 0: + footer.append('') + footer.append('<li id="fn-%s">' % id) + footer.append(self._run_block_gamut(self.footnotes[id])) + backlink = ('<a href="#fnref-%s" ' + 'class="footnoteBackLink" ' + 'title="Jump back to footnote %d in the text.">' + '↩</a>' % (id, i+1)) + if footer[-1].endswith("</p>"): + footer[-1] = footer[-1][:-len("</p>")] \ + + ' ' + backlink + "</p>" + else: + footer.append("\n<p>%s</p>" % backlink) + footer.append('</li>') + footer.append('</ol>') + footer.append('</div>') + return text + '\n\n' + '\n'.join(footer) + else: + return text + + # Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin: + # http://bumppo.net/projects/amputator/ + _ampersand_re = re.compile(r'&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)') + _naked_lt_re = re.compile(r'<(?![a-z/?\$!])', re.I) + _naked_gt_re = re.compile(r'''(?<![a-z?!/'"-])>''', re.I) + + def _encode_amps_and_angles(self, text): + # Smart processing for ampersands and angle brackets that need + # to be encoded. + text = self._ampersand_re.sub('&', text) + + # Encode naked <'s + text = self._naked_lt_re.sub('<', text) + + # Encode naked >'s + # Note: Other markdown implementations (e.g. Markdown.pl, PHP + # Markdown) don't do this. + text = self._naked_gt_re.sub('>', text) + return text + + def _encode_backslash_escapes(self, text): + for ch, escape in g_escape_table.items(): + text = text.replace("\\"+ch, escape) + return text + + _auto_link_re = re.compile(r'<((https?|ftp):[^\'">\s]+)>', re.I) + def _auto_link_sub(self, match): + g1 = match.group(1) + return '<a href="%s">%s</a>' % (g1, g1) + + _auto_email_link_re = re.compile(r""" + < + (?:mailto:)? + ( + [-.\w]+ + \@ + [-\w]+(\.[-\w]+)*\.[a-z]+ + ) + > + """, re.I | re.X | re.U) + def _auto_email_link_sub(self, match): + return self._encode_email_address( + self._unescape_special_chars(match.group(1))) + + def _do_auto_links(self, text): + text = self._auto_link_re.sub(self._auto_link_sub, text) + text = self._auto_email_link_re.sub(self._auto_email_link_sub, text) + return text + + def _encode_email_address(self, addr): + # Input: an email address, e.g. "foo@example.com" + # + # Output: the email address as a mailto link, with each character + # of the address encoded as either a decimal or hex entity, in + # the hopes of foiling most address harvesting spam bots. E.g.: + # + # <a href="mailto:foo@e + # xample.com">foo + # @example.com</a> + # + # Based on a filter by Matthew Wickline, posted to the BBEdit-Talk + # mailing list: <http://tinyurl.com/yu7ue> + chars = [_xml_encode_email_char_at_random(ch) + for ch in "mailto:" + addr] + # Strip the mailto: from the visible part. + addr = '<a href="%s">%s</a>' \ + % (''.join(chars), ''.join(chars[7:])) + return addr + + def _do_link_patterns(self, text): + """Caveat emptor: there isn't much guarding against link + patterns being formed inside other standard Markdown links, e.g. + inside a [link def][like this]. + + Dev Notes: *Could* consider prefixing regexes with a negative + lookbehind assertion to attempt to guard against this. + """ + link_from_hash = {} + for regex, repl in self.link_patterns: + replacements = [] + for match in regex.finditer(text): + if hasattr(repl, "__call__"): + href = repl(match) + else: + href = match.expand(repl) + replacements.append((match.span(), href)) + for (start, end), href in reversed(replacements): + escaped_href = ( + href.replace('"', '"') # b/c of attr quote + # To avoid markdown <em> and <strong>: + .replace('*', g_escape_table['*']) + .replace('_', g_escape_table['_'])) + link = '<a href="%s">%s</a>' % (escaped_href, text[start:end]) + hash = _hash_text(link) + link_from_hash[hash] = link + text = text[:start] + hash + text[end:] + for hash, link in link_from_hash.items(): + text = text.replace(hash, link) + return text + + def _unescape_special_chars(self, text): + # Swap back in all the special characters we've hidden. + for ch, hash in g_escape_table.items(): + text = text.replace(hash, ch) + return text + + def _outdent(self, text): + # Remove one level of line-leading tabs or spaces + return self._outdent_re.sub('', text) + + +class MarkdownWithExtras(Markdown): + """A markdowner class that enables most extras: + + - footnotes + - code-color (only has effect if 'pygments' Python module on path) + + These are not included: + - pyshell (specific to Python-related documenting) + - code-friendly (because it *disables* part of the syntax) + - link-patterns (because you need to specify some actual + link-patterns anyway) + """ + extras = ["footnotes", "code-color"] + + +#---- internal support functions + +# From http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52549 +def _curry(*args, **kwargs): + function, args = args[0], args[1:] + def result(*rest, **kwrest): + combined = kwargs.copy() + combined.update(kwrest) + return function(*args + rest, **combined) + return result + +# Recipe: regex_from_encoded_pattern (1.0) +def _regex_from_encoded_pattern(s): + """'foo' -> re.compile(re.escape('foo')) + '/foo/' -> re.compile('foo') + '/foo/i' -> re.compile('foo', re.I) + """ + if s.startswith('/') and s.rfind('/') != 0: + # Parse it: /PATTERN/FLAGS + idx = s.rfind('/') + pattern, flags_str = s[1:idx], s[idx+1:] + flag_from_char = { + "i": re.IGNORECASE, + "l": re.LOCALE, + "s": re.DOTALL, + "m": re.MULTILINE, + "u": re.UNICODE, + } + flags = 0 + for char in flags_str: + try: + flags |= flag_from_char[char] + except KeyError: + raise ValueError("unsupported regex flag: '%s' in '%s' " + "(must be one of '%s')" + % (char, s, ''.join(flag_from_char.keys()))) + return re.compile(s[1:idx], flags) + else: # not an encoded regex + return re.compile(re.escape(s)) + +# Recipe: dedent (0.1.2) +def _dedentlines(lines, tabsize=8, skip_first_line=False): + """_dedentlines(lines, tabsize=8, skip_first_line=False) -> dedented lines + + "lines" is a list of lines to dedent. + "tabsize" is the tab width to use for indent width calculations. + "skip_first_line" is a boolean indicating if the first line should + be skipped for calculating the indent width and for dedenting. + This is sometimes useful for docstrings and similar. + + Same as dedent() except operates on a sequence of lines. Note: the + lines list is modified **in-place**. + """ + DEBUG = False + if DEBUG: + print "dedent: dedent(..., tabsize=%d, skip_first_line=%r)"\ + % (tabsize, skip_first_line) + indents = [] + margin = None + for i, line in enumerate(lines): + if i == 0 and skip_first_line: continue + indent = 0 + for ch in line: + if ch == ' ': + indent += 1 + elif ch == '\t': + indent += tabsize - (indent % tabsize) + elif ch in '\r\n': + continue # skip all-whitespace lines + else: + break + else: + continue # skip all-whitespace lines + if DEBUG: print "dedent: indent=%d: %r" % (indent, line) + if margin is None: + margin = indent + else: + margin = min(margin, indent) + if DEBUG: print "dedent: margin=%r" % margin + + if margin is not None and margin > 0: + for i, line in enumerate(lines): + if i == 0 and skip_first_line: continue + removed = 0 + for j, ch in enumerate(line): + if ch == ' ': + removed += 1 + elif ch == '\t': + removed += tabsize - (removed % tabsize) + elif ch in '\r\n': + if DEBUG: print "dedent: %r: EOL -> strip up to EOL" % line + lines[i] = lines[i][j:] + break + else: + raise ValueError("unexpected non-whitespace char %r in " + "line %r while removing %d-space margin" + % (ch, line, margin)) + if DEBUG: + print "dedent: %r: %r -> removed %d/%d"\ + % (line, ch, removed, margin) + if removed == margin: + lines[i] = lines[i][j+1:] + break + elif removed > margin: + lines[i] = ' '*(removed-margin) + lines[i][j+1:] + break + else: + if removed: + lines[i] = lines[i][removed:] + return lines + +def _dedent(text, tabsize=8, skip_first_line=False): + """_dedent(text, tabsize=8, skip_first_line=False) -> dedented text + + "text" is the text to dedent. + "tabsize" is the tab width to use for indent width calculations. + "skip_first_line" is a boolean indicating if the first line should + be skipped for calculating the indent width and for dedenting. + This is sometimes useful for docstrings and similar. + + textwrap.dedent(s), but don't expand tabs to spaces + """ + lines = text.splitlines(1) + _dedentlines(lines, tabsize=tabsize, skip_first_line=skip_first_line) + return ''.join(lines) + + +class _memoized(object): + """Decorator that caches a function's return value each time it is called. + If called later with the same arguments, the cached value is returned, and + not re-evaluated. + + http://wiki.python.org/moin/PythonDecoratorLibrary + """ + def __init__(self, func): + self.func = func + self.cache = {} + def __call__(self, *args): + try: + return self.cache[args] + except KeyError: + self.cache[args] = value = self.func(*args) + return value + except TypeError: + # uncachable -- for instance, passing a list as an argument. + # Better to not cache than to blow up entirely. + return self.func(*args) + def __repr__(self): + """Return the function's docstring.""" + return self.func.__doc__ + + +def _xml_oneliner_re_from_tab_width(tab_width): + """Standalone XML processing instruction regex.""" + return re.compile(r""" + (?: + (?<=\n\n) # Starting after a blank line + | # or + \A\n? # the beginning of the doc + ) + ( # save in $1 + [ ]{0,%d} + (?: + <\?\w+\b\s+.*?\?> # XML processing instruction + | + <\w+:\w+\b\s+.*?/> # namespaced single tag + ) + [ \t]* + (?=\n{2,}|\Z) # followed by a blank line or end of document + ) + """ % (tab_width - 1), re.X) +_xml_oneliner_re_from_tab_width = _memoized(_xml_oneliner_re_from_tab_width) + +def _hr_tag_re_from_tab_width(tab_width): + return re.compile(r""" + (?: + (?<=\n\n) # Starting after a blank line + | # or + \A\n? # the beginning of the doc + ) + ( # save in \1 + [ ]{0,%d} + <(hr) # start tag = \2 + \b # word break + ([^<>])*? # + /?> # the matching end tag + [ \t]* + (?=\n{2,}|\Z) # followed by a blank line or end of document + ) + """ % (tab_width - 1), re.X) +_hr_tag_re_from_tab_width = _memoized(_hr_tag_re_from_tab_width) + + +def _xml_encode_email_char_at_random(ch): + r = random() + # Roughly 10% raw, 45% hex, 45% dec. + # '@' *must* be encoded. I [John Gruber] insist. + # Issue 26: '_' must be encoded. + if r > 0.9 and ch not in "@_": + return ch + elif r < 0.45: + # The [1:] is to drop leading '0': 0x63 -> x63 + return '&#%s;' % hex(ord(ch))[1:] + else: + return '&#%s;' % ord(ch) + + + +#---- mainline + +class _NoReflowFormatter(optparse.IndentedHelpFormatter): + """An optparse formatter that does NOT reflow the description.""" + def format_description(self, description): + return description or "" + +def _test(): + import doctest + doctest.testmod() + +def main(argv=None): + if argv is None: + argv = sys.argv + if not logging.root.handlers: + logging.basicConfig() + + usage = "usage: %prog [PATHS...]" + version = "%prog "+__version__ + parser = optparse.OptionParser(prog="markdown2", usage=usage, + version=version, description=cmdln_desc, + formatter=_NoReflowFormatter()) + parser.add_option("-v", "--verbose", dest="log_level", + action="store_const", const=logging.DEBUG, + help="more verbose output") + parser.add_option("--encoding", + help="specify encoding of text content") + parser.add_option("--html4tags", action="store_true", default=False, + help="use HTML 4 style for empty element tags") + parser.add_option("-s", "--safe", metavar="MODE", dest="safe_mode", + help="sanitize literal HTML: 'escape' escapes " + "HTML meta chars, 'replace' replaces with an " + "[HTML_REMOVED] note") + parser.add_option("-x", "--extras", action="append", + help="Turn on specific extra features (not part of " + "the core Markdown spec). Supported values: " + "'code-friendly' disables _/__ for emphasis; " + "'code-color' adds code-block syntax coloring; " + "'link-patterns' adds auto-linking based on patterns; " + "'footnotes' adds the footnotes syntax;" + "'xml' passes one-liner processing instructions and namespaced XML tags;" + "'pyshell' to put unindented Python interactive shell sessions in a <code> block.") + parser.add_option("--use-file-vars", + help="Look for and use Emacs-style 'markdown-extras' " + "file var to turn on extras. See " + "<http://code.google.com/p/python-markdown2/wiki/Extras>.") + parser.add_option("--link-patterns-file", + help="path to a link pattern file") + parser.add_option("--self-test", action="store_true", + help="run internal self-tests (some doctests)") + parser.add_option("--compare", action="store_true", + help="run against Markdown.pl as well (for testing)") + parser.set_defaults(log_level=logging.INFO, compare=False, + encoding="utf-8", safe_mode=None, use_file_vars=False) + opts, paths = parser.parse_args() + log.setLevel(opts.log_level) + + if opts.self_test: + return _test() + + if opts.extras: + extras = {} + for s in opts.extras: + splitter = re.compile("[,;: ]+") + for e in splitter.split(s): + if '=' in e: + ename, earg = e.split('=', 1) + try: + earg = int(earg) + except ValueError: + pass + else: + ename, earg = e, None + extras[ename] = earg + else: + extras = None + + if opts.link_patterns_file: + link_patterns = [] + f = open(opts.link_patterns_file) + try: + for i, line in enumerate(f.readlines()): + if not line.strip(): continue + if line.lstrip().startswith("#"): continue + try: + pat, href = line.rstrip().rsplit(None, 1) + except ValueError: + raise MarkdownError("%s:%d: invalid link pattern line: %r" + % (opts.link_patterns_file, i+1, line)) + link_patterns.append( + (_regex_from_encoded_pattern(pat), href)) + finally: + f.close() + else: + link_patterns = None + + from os.path import join, dirname, abspath, exists + markdown_pl = join(dirname(dirname(abspath(__file__))), "test", + "Markdown.pl") + for path in paths: + if opts.compare: + print "==== Markdown.pl ====" + perl_cmd = 'perl %s "%s"' % (markdown_pl, path) + o = os.popen(perl_cmd) + perl_html = o.read() + o.close() + sys.stdout.write(perl_html) + print "==== markdown2.py ====" + html = markdown_path(path, encoding=opts.encoding, + html4tags=opts.html4tags, + safe_mode=opts.safe_mode, + extras=extras, link_patterns=link_patterns, + use_file_vars=opts.use_file_vars) + sys.stdout.write( + html.encode(sys.stdout.encoding or "utf-8", 'xmlcharrefreplace')) + if opts.compare: + test_dir = join(dirname(dirname(abspath(__file__))), "test") + if exists(join(test_dir, "test_markdown2.py")): + sys.path.insert(0, test_dir) + from test_markdown2 import norm_html_from_html + norm_html = norm_html_from_html(html) + norm_perl_html = norm_html_from_html(perl_html) + else: + norm_html = html + norm_perl_html = perl_html + print "==== match? %r ====" % (norm_perl_html == norm_html) + + +if __name__ == "__main__": + sys.exit( main(sys.argv) ) + ADDED gluon/contrib/markmin/__init__.py Index: gluon/contrib/markmin/__init__.py ================================================================== --- gluon/contrib/markmin/__init__.py +++ gluon/contrib/markmin/__init__.py @@ -0,0 +1,1 @@ + ADDED gluon/contrib/markmin/markmin.html Index: gluon/contrib/markmin/markmin.html ================================================================== --- gluon/contrib/markmin/markmin.html +++ gluon/contrib/markmin/markmin.html @@ -0,0 +1,35 @@ +<html><body><h1>Markmin markup language</h1><h2>About</h2><p>This is a new markup language that we call markmin designed to produce high quality scientific papers and books and also put them online. We provide serializers for html, latex and pdf. It is implemented in the <code class="">markmin2html</code> function in the <code class="">markmin2html.py</code>.</p><p>Example of usage:</p><pre><code class="">>>> m = "Hello **world** [[link http://web2py.com]]" +>>> from markmin2html import markmin2html +>>> print markmin2html(m) +>>> from markmin2latex import markmin2latex +>>> print markmin2latex(m) +>>> from markmin2pdf import markmin2pdf # requires pdflatex +>>> print markmin2pdf(m)</code></pre><h2>Why?</h2><p>We wanted a markup language with the following requirements:</p><ul><li>less than 100 lines of functional code</li><li>easy to read</li><li>secure</li><li>support table, ul, ol, code</li><li>support html5 video and audio elements (html serialization only)</li><li>can align images and resize them</li><li>can specify class for tables and code elements</li><li>can add anchors</li><li>does not use _ for markup (since it creates odd behavior)</li><li>automatically links urls</li><li>fast</li><li>easy to extend</li><li>supports latex and pdf including references</li><li>allows to describe the markup in the markup (this document is generated from markmin syntax)</li></ul><p>(results depend on text but in average for text ~100K markmin is 30% faster than markdown, for text ~10K it is 10x faster)</p><p>The <a href="http://www.lulu.com/product/paperback/web2py-%283rd-edition%29/12822827">web2py book</a> published by lulu, for example, was entirely generated with markmin2pdf from the online <a href="http://www.web2py.com/book">web2py wiki</a></p><h2>Download</h2><ul><li>http://web2py.googlecode.com/hg/gluon/contrib/markmin/markmin2html.py</li><li>http://web2py.googlecode.com/hg/gluon/contrib/markmin/markmin2latex.py</li><li>http://web2py.googlecode.com/hg/gluon/contrib/markmin/markmin2pdf.py</li></ul><p>markmin2html.py and markmin2latex.py are single files and have no web2py dependence. Their license is BSD.</p><h2>Examples</h2><h3>Bold, italic, code and links</h3><table class=""><tr><td><b>SOURCE</b> </td><td><b>OUTPUT</b></td></tr><tr><td><code class=""># title</code> </td><td><b>title</b></td></tr><tr><td><code class="">## section</code> </td><td><b>section</b></td></tr><tr><td><code class="">### subsection</code> </td><td><b>subsection</b></td></tr><tr><td><code class="">**bold**</code> </td><td><b>bold</b></td></tr><tr><td><code class="">''italic''</code> </td><td><i>italic</i></td></tr><tr><td><code class="">``verbatim``</code> </td><td><code class="">verbatim</code></td></tr><tr><td><code class="">http://google.com</code> </td><td>http://google.com</td></tr><tr><td><code class="">[[click me #myanchor]]</code></td><td><a href="#myanchor">click me</a></td></tr></table> +<h3>More on links</h3><p>The format is always <code class="">[[title link]]</code>. Notice you can nest bold, italic and code inside the link title.</p><h3>Anchors <span id="myanchor"><span></h3><p>You can place an anchor anywhere in the text using the syntax <code class="">[[name]]</code> where <i>name</i> is the name of the anchor. +You can then link the anchor with <a href="#myanchor">link</a>, i.e. <code class="">[[link #myanchor]]</code>.</p><h3>Images</h3><p><img src="http://www.web2py.com/examples/static/web2py_logo.png" alt="some image" align="right" width="200px" /> +This paragraph has an image aligned to the right with a width of 200px. Its is placed using the code</p><p><code class="">[[some image http://www.web2py.com/examples/static/web2py_logo.png right 200px]]</code>.</p><h3>Unordered Lists</h3><pre><code class="">- Dog +- Cat +- Mouse</code></pre><p>is rendered as</p><ul><li>Dog</li><li>Cat</li><li>Mouse</li></ul><p>Two new lines between items break the list in two lists.</p><h3>Ordered Lists</h3><pre><code class="">+ Dog ++ Cat ++ Mouse</code></pre><p>is rendered as</p><ol><li>Dog</li><li>Cat</li><li>Mouse</li></ol><h3>Tables</h3><p>Something like this +<pre><code class="">--------- +**A** | **B** | **C** +0 | 0 | X +0 | X | 0 +X | 0 | 0 +-----:abc</code></pre> +is a table and is rendered as +<table class="abc"><tr><td><b>A</b></td><td><b>B</b></td><td><b>C</b></td></tr><tr><td>0</td><td>0</td><td>X</td></tr><tr><td>0</td><td>X</td><td>0</td></tr><tr><td>X</td><td>0</td><td>0</td></tr></table>Four or more dashes delimit the table and | separates the columns. +The <code class="">:abc</code> at the end sets the class for the table and it is optional.</p><h3>Blockquote</h3><p>A table with a single cell is rendered as a blockquote:</p><blockquote class="">Hello world</blockquote> +<h3>Code, <code class=""><code></code>, escaping and extra stuff</h3><pre><code class="python">def test(): + return "this is Python code"</code></pre><p>Optionally a ` inside a <code class="">``...``</code> block can be inserted escaped with !`!. +The <code class="">:python</code> after the markup is also optional. If present, by default, it is used to set the class of the <code> block. +The behavior can be overridden by passing an argument <code class="">extra</code> to the <code class="">render</code> function. For example:</p><pre><code class="python">>>> markmin2html("``aaa``:custom", + extra=dict(custom=lambda text: 'x'+text+'x'))</code></pre><p>generates</p><code class="python">'xaaax'</code><p>(the <code class="">``...``:custom</code> block is rendered by the <code class="">custom=lambda</code> function passed to <code class="">render</code>).</p><h3>Html5 support</h3><p>Markmin also supports the <video> and <audio> html5 tags using the notation: +<pre><code class="">[[title link video]] +[[title link audio]]</code></pre></p><h3>Latex</h3><p>Formulas can be embedded into HTML with <code class="">$</code><code class="">$</code>formula<code class="">$</code><code class="">$</code>. +You can use Google charts to render the formula:</p><pre><code class="">>>> LATEX = '<img src="http://chart.apis.google.com/chart?cht=tx&chl=%s" align="center"/>' +>>> markmin2html(text,{'latex':lambda code: LATEX % code.replace('"','"')})</code></pre><h3>Citations and References</h3><p>Citations are treated as internal links in html and proper citations in latex if there is a final section called "References". Items like</p><pre><code class="">- [[key]] value</code></pre><p>in the References will be translated into Latex</p><pre><code class="">\bibitem{key} value</code></pre><p>Here is an example of usage:</p><pre><code class="">As shown in Ref.``mdipierro``:cite + +## References +- [[mdipierro]] web2py Manual, 3rd Edition, lulu.com</code></pre><h3>Caveats</h3><p><code class=""><ul/></code>, <code class=""><ol/></code>, <code class=""><code/></code>, <code class=""><table/></code>, <code class=""><blockquote/></code>, <code class=""><h1/></code>, ..., <code class=""><h6/></code> do not have <code class=""><p>...</p></code> around them.</p></body></html> ADDED gluon/contrib/markmin/markmin.pdf Index: gluon/contrib/markmin/markmin.pdf ================================================================== --- gluon/contrib/markmin/markmin.pdf +++ gluon/contrib/markmin/markmin.pdf cannot compute difference between binary files ADDED gluon/contrib/markmin/markmin2html.py Index: gluon/contrib/markmin/markmin2html.py ================================================================== --- gluon/contrib/markmin/markmin2html.py +++ gluon/contrib/markmin/markmin2html.py @@ -0,0 +1,455 @@ +#!/usr/bin/env python # created my Massimo Di Pierro +# license MIT/BSD/GPL +import re +import cgi + +__all__ = ['render', 'markmin2html'] + +__doc__ = """ +# Markmin markup language + +## About + +This is a new markup language that we call markmin designed to produce high quality scientific papers and books and also put them online. We provide serializers for html, latex and pdf. It is implemented in the ``markmin2html`` function in the ``markmin2html.py``. + +Example of usage: + +`` +>>> m = "Hello **world** [[link http://web2py.com]]" +>>> from markmin2html import markmin2html +>>> print markmin2html(m) +>>> from markmin2latex import markmin2latex +>>> print markmin2latex(m) +>>> from markmin2pdf import markmin2pdf # requires pdflatex +>>> print markmin2pdf(m) +`` + +## Why? + +We wanted a markup language with the following requirements: +- less than 100 lines of functional code +- easy to read +- secure +- support table, ul, ol, code +- support html5 video and audio elements (html serialization only) +- can align images and resize them +- can specify class for tables and code elements +- can add anchors +- does not use _ for markup (since it creates odd behavior) +- automatically links urls +- fast +- easy to extend +- supports latex and pdf including references +- allows to describe the markup in the markup (this document is generated from markmin syntax) + +(results depend on text but in average for text ~100K markmin is 30% faster than markdown, for text ~10K it is 10x faster) + +The [[web2py book http://www.lulu.com/product/paperback/web2py-%283rd-edition%29/12822827]] published by lulu, for example, was entirely generated with markmin2pdf from the online [[web2py wiki http://www.web2py.com/book]] + +## Download + +- http://web2py.googlecode.com/hg/gluon/contrib/markmin/markmin2html.py +- http://web2py.googlecode.com/hg/gluon/contrib/markmin/markmin2latex.py +- http://web2py.googlecode.com/hg/gluon/contrib/markmin/markmin2pdf.py + +markmin2html.py and markmin2latex.py are single files and have no web2py dependence. Their license is BSD. + +## Examples + +### Bold, italic, code and links + +-------------------------------------------------- +**SOURCE** | **OUTPUT** +``# title`` | **title** +``## section`` | **section** +``### subsection`` | **subsection** +``**bold**`` | **bold** +``''italic''`` | ''italic'' +``!`!`verbatim`!`!`` | ``verbatim`` +``http://google.com`` | http://google.com +``[[click me #myanchor]]`` | [[click me #myanchor]] +--------------------------------------------------- + +### More on links + +The format is always ``[[title link]]``. Notice you can nest bold, italic and code inside the link title. + +### Anchors [[myanchor]] + +You can place an anchor anywhere in the text using the syntax ``[[name]]`` where ''name'' is the name of the anchor. +You can then link the anchor with [[link #myanchor]], i.e. ``[[link #myanchor]]``. + +### Images + +[[some image http://www.web2py.com/examples/static/web2py_logo.png right 200px]] +This paragraph has an image aligned to the right with a width of 200px. Its is placed using the code + +``[[some image http://www.web2py.com/examples/static/web2py_logo.png right 200px]]``. + +### Unordered Lists + +`` +- Dog +- Cat +- Mouse +`` + +is rendered as +- Dog +- Cat +- Mouse + +Two new lines between items break the list in two lists. + +### Ordered Lists + +`` ++ Dog ++ Cat ++ Mouse +`` + +is rendered as ++ Dog ++ Cat ++ Mouse + + +### Tables + +Something like this +`` +--------- +**A** | **B** | **C** +0 | 0 | X +0 | X | 0 +X | 0 | 0 +-----:abc +`` +is a table and is rendered as +--------- +**A** | **B** | **C** +0 | 0 | X +0 | X | 0 +X | 0 | 0 +-----:abc +Four or more dashes delimit the table and | separates the columns. +The ``:abc`` at the end sets the class for the table and it is optional. + +### Blockquote + +A table with a single cell is rendered as a blockquote: + +----- +Hello world +----- + +### Code, ``<code>``, escaping and extra stuff + +`` +def test(): + return "this is Python code" +``:python + +Optionally a ` inside a ``!`!`...`!`!`` block can be inserted escaped with !`!. +The ``:python`` after the markup is also optional. If present, by default, it is used to set the class of the <code> block. +The behavior can be overridden by passing an argument ``extra`` to the ``render`` function. For example: + +`` +>>> markmin2html("!`!!`!aaa!`!!`!:custom", + extra=dict(custom=lambda text: 'x'+text+'x')) +``:python + +generates + +``'xaaax'``:python + +(the ``!`!`...`!`!:custom`` block is rendered by the ``custom=lambda`` function passed to ``render``). + + +### Html5 support + +Markmin also supports the <video> and <audio> html5 tags using the notation: +`` +[[title link video]] +[[title link audio]] +`` + +### Latex and other extensions + +Formulas can be embedded into HTML with ``$````$``formula``$````$``. +You can use Google charts to render the formula: + +`` +>>> LATEX = '<img src="http://chart.apis.google.com/chart?cht=tx&chl=%s" align="ce\ +nter"/>' +>>> markmin2html(text,{'latex':lambda code: LATEX % code.replace('"','\"')}) +`` + +### Code with syntax highlighting + +This requires a syntax highlighting tool, such as the web2py CODE helper. + +`` +>>> extra={'code_cpp':lambda text: CODE(text,language='cpp').xml(), + 'code_java':lambda text: CODE(text,language='java').xml(), + 'code_python':lambda text: CODE(text,language='python').xml(), + 'code_html':lambda text: CODE(text,language='html').xml()} +>>> markmin2html(text,extra=extra) +`` + +Code can now be marked up as in this example: + +`` +!`!` +<html><body>example</body></html> +!`!`:code_html +`` + +### Citations and References + +Citations are treated as internal links in html and proper citations in latex if there is a final section called "References". Items like + +`` +- [[key]] value +`` + +in the References will be translated into Latex + +`` +\\bibitem{key} value +`` + +Here is an example of usage: + +`` +As shown in Ref.!`!`mdipierro`!`!:cite + +## References +- [[mdipierro]] web2py Manual, 3rd Edition, lulu.com +`` + +### Caveats +``<ul/>``, ``<ol/>``, ``<code/>``, ``<table/>``, ``<blockquote/>``, ``<h1/>``, ..., ``<h6/>`` do not have ``<p>...</p>`` around them. + +""" + +META = 'META' +LATEX = '<img src="http://chart.apis.google.com/chart?cht=tx&chl=%s" align="center"/>' +regex_newlines = re.compile('(\n\r)|(\r\n)') +regex_dd=re.compile('\$\$(?P<latex>.*?)\$\$') +regex_code = re.compile('('+META+')|(``(?P<t>.*?)``(:(?P<c>\w+))?)',re.S) +regex_maps = [ + (re.compile('[ \t\r]+\n'),'\n'), + (re.compile('[ \t\r]+\n'),'\n'), + (re.compile('\*\*(?P<t>[^\s\*]+( +[^\s\*]+)*)\*\*'),'<b>\g<t></b>'), + (re.compile("''(?P<t>[^\s']+( +[^\s']+)*)''"),'<i>\g<t></i>'), + (re.compile('^#{6} (?P<t>[^\n]+)',re.M),'\n\n<<h6>\g<t></h6>\n'), + (re.compile('^#{5} (?P<t>[^\n]+)',re.M),'\n\n<<h5>\g<t></h5>\n'), + (re.compile('^#{4} (?P<t>[^\n]+)',re.M),'\n\n<<h4>\g<t></h4>\n'), + (re.compile('^#{3} (?P<t>[^\n]+)',re.M),'\n\n<<h3>\g<t></h3>\n'), + (re.compile('^#{2} (?P<t>[^\n]+)',re.M),'\n\n<<h2>\g<t></h2>\n'), + (re.compile('^#{1} (?P<t>[^\n]+)',re.M),'\n\n<<h1>\g<t></h1>\n'), + (re.compile('^\- +(?P<t>.*)',re.M),'<<ul><li>\g<t></li></ul>'), + (re.compile('^\+ +(?P<t>.*)',re.M),'<<ol><li>\g<t></li></ol>'), + (re.compile('</ol>\n<<ol>'),''), + (re.compile('</ul>\n<<ul>'),''), + (re.compile('<<'),'\n\n<<'), + (re.compile('\n\s+\n'),'\n\n')] +regex_table = re.compile('^\-{4,}\n(?P<t>.*?)\n\-{4,}(:(?P<c>\w+))?\n',re.M|re.S) +regex_anchor = re.compile('\[\[(?P<t>\S+)\]\]') +regex_image_width = re.compile('\[\[(?P<t>.*?) +(?P<k>\S+) +(?P<p>left|right|center) +(?P<w>\d+px)\]\]') +regex_image = re.compile('\[\[(?P<t>.*?) +(?P<k>\S+) +(?P<p>left|right|center)\]\]') +regex_video = re.compile('\[\[(?P<t>.*?) +(?P<k>\S+) +video\]\]') +regex_audio = re.compile('\[\[(?P<t>.*?) +(?P<k>\S+) +audio\]\]') +regex_link = re.compile('\[\[(?P<t>.*?) +(?P<k>\S+)\]\]') +regex_link_popup = re.compile('\[\[(?P<t>.*?) +(?P<k>\S+) popup\]\]') +regex_link_no_anchor = re.compile('\[\[ +(?P<k>\S+)\]\]') +regex_auto = re.compile('(?<!["\w\>])(?P<k>\w+://[\w\.\-\+\?&%\/]+)',re.M) + +def render(text,extra={},allowed={},sep='p'): + """ + Arguments: + - text is the text to be processed + - extra is a dict like extra=dict(custom=lambda value: value) that process custom code + as in " ``this is custom code``:custom " + - allowed is a dictionary of list of allowed classes like + allowed = dict(code=('python','cpp','java')) + - sep can be 'p' to separate text in <p>...</p> + or can be 'br' to separate text using <br /> + + + >>> render('this is\\n# a section\\nparagraph') + '<p>this is</p><h1>a section</h1><p>paragraph</p>' + >>> render('this is\\n## a subsection\\nparagraph') + '<p>this is</p><h2>a subsection</h2><p>paragraph</p>' + >>> render('this is\\n### a subsubsection\\nparagraph') + '<p>this is</p><h3>a subsubsection</h3><p>paragraph</p>' + >>> render('**hello world**') + '<p><b>hello world</b></p>' + >>> render('``hello world``') + '<code class="">hello world</code>' + >>> render('``hello world``:python') + '<code class="python">hello world</code>' + >>> render('``\\nhello\\nworld\\n``:python') + '<pre><code class="python">hello\\nworld</code></pre>' + >>> render("''hello world''") + '<p><i>hello world</i></p>' + >>> render('** hello** **world**') + '<p>** hello** <b>world</b></p>' + + >>> render('- this\\n- is\\n- a list\\n\\nand this\\n- is\\n- another') + '<ul><li>this</li><li>is</li><li>a list</li></ul><p>and this</p><ul><li>is</li><li>another</li></ul>' + + >>> render('+ this\\n+ is\\n+ a list\\n\\nand this\\n+ is\\n+ another') + '<ol><li>this</li><li>is</li><li>a list</li></ol><p>and this</p><ol><li>is</li><li>another</li></ol>' + + >>> render("----\\na | b\\nc | d\\n----\\n") + '<table class=""><tr><td>a</td><td>b</td></tr><tr><td>c</td><td>d</td></tr></table>' + + >>> render("----\\nhello world\\n----\\n") + '<blockquote class="">hello world</blockquote>' + + >>> render('[[this is a link http://example.com]]') + '<p><a href="http://example.com">this is a link</a></p>' + + >>> render('[[this is an image http://example.com left]]') + '<p><img src="http://example.com" alt="this is an image" align="left" /></p>' + >>> render('[[this is an image http://example.com left 200px]]') + '<p><img src="http://example.com" alt="this is an image" align="left" width="200px" /></p>' + + >>> render('[[this is an image http://example.com video]]') + '<p><video src="http://example.com" controls></video></p>' + >>> render('[[this is an image http://example.com audio]]') + '<p><audio src="http://example.com" controls></audio></p>' + + >>> render('[[this is a **link** http://example.com]]') + '<p><a href="http://example.com">this is a <b>link</b></a></p>' + + >>> render("``aaa``:custom",extra=dict(custom=lambda text: 'x'+text+'x')) + 'xaaax' + >>> render(r"$$\int_a^b sin(x)dx$$") + '<code class="latex">\\\\int_a^b sin(x)dx</code>' + """ + text = str(text or '') + ############################################################# + # replace all blocks marked with ``...``:class with META + # store them into segments they will be treated as code + ############################################################# + segments, i = [], 0 + text = regex_dd.sub('``\g<latex>``:latex ',text) + text = regex_newlines.sub('\n',text) + while True: + item = regex_code.search(text,i) + if not item: break + if item.group()==META: + segments.append((None,None)) + text = text[:item.start()]+META+text[item.end():] + else: + c = item.group('c') or '' + if 'code' in allowed and not c in allowed['code']: c = '' + code = item.group('t').replace('!`!','`') + segments.append((code,c)) + text = text[:item.start()]+META+text[item.end():] + i=item.start()+3 + + ############################################################# + # do h1,h2,h3,h4,h5,h6,b,i,ol,ul and normalize spaces + ############################################################# + text = '\n'.join(t.strip() for t in text.split('\n')) + text = cgi.escape(text) + for regex, sub in regex_maps: + text = regex.sub(sub,text) + + ############################################################# + # process tables and blockquotes + ############################################################# + while True: + item = regex_table.search(text) + if not item: break + c = item.group('c') or '' + if 'table' in allowed and not c in allowed['table']: c = '' + content = item.group('t') + if ' | ' in content: + rows = content.replace('\n','</td></tr><tr><td>').replace(' | ','</td><td>') + text = text[:item.start()] + '<<table class="%s"><tr><td>'%c + rows + '</td></tr></table>' + text[item.end():] + else: + text = text[:item.start()] + '<<blockquote class="%s">'%c + content + '</blockquote>' + text[item.end():] + + ############################################################# + # deal with images, videos, audios and links + ############################################################# + + text = regex_anchor.sub('<span id="\g<t>"><span>', text) + text = regex_image_width.sub('<img src="\g<k>" alt="\g<t>" align="\g<p>" width="\g<w>" />', text) + text = regex_image.sub('<img src="\g<k>" alt="\g<t>" align="\g<p>" />', text) + text = regex_video.sub('<video src="\g<k>" controls></video>', text) + text = regex_audio.sub('<audio src="\g<k>" controls></audio>', text) + text = regex_link_popup.sub('<a href="\g<k>" target="_blank">\g<t></a>', text) + text = regex_link_no_anchor.sub('<a href="\g<k>">\g<k></a>', text) + text = regex_link.sub('<a href="\g<k>">\g<t></a>', text) + text = regex_auto.sub('<a href="\g<k>">\g<k></a>', text) + + ############################################################# + # deal with paragraphs (trick <<ul, <<ol, <<table, <<h1, etc) + # the << indicates that there should NOT be a new paragraph + # META indicates a code block therefore no new paragraph + ############################################################# + items = [item.strip() for item in text.split('\n\n')] + if sep=='p': + text = ''.join(p[:2]!='<<' and p!=META and '<p>%s</p>'%p or '%s'%p for p in items if p) + elif sep=='br': + text = '<br />'.join(items) + + ############################################################# + # finally get rid of << + ############################################################# + text=text.replace('<<','<') + + ############################################################# + # process all code text + ############################################################# + parts = text.split(META) + text = parts[0] + for i,(code,b) in enumerate(segments): + if code==None: + html = META + else: + if b in extra: + if code[:1]=='\n': code=code[1:] + if code[-1:]=='\n': code=code[:-1] + html = extra[b](code) + elif b=='cite': + html = '['+','.join('<a href="#%s" class="%s">%s</a>' \ + % (d,b,d) \ + for d in cgi.escape(code).split(','))+']' + elif b=='latex': + html = LATEX % code.replace('"','\"').replace('\n',' ') + elif code[:1]=='\n' or code[-1:]=='\n': + if code[:1]=='\n': code=code[1:] + if code[-1:]=='\n': code=code[:-1] + html = '<pre><code class="%s">%s</code></pre>' % (b,cgi.escape(code)) + else: + if code[:1]=='\n': code=code[1:] + if code[-1:]=='\n': code=code[:-1] + html = '<code class="%s">%s</code>' % (b,cgi.escape(code)) + text = text+html+parts[i+1] + return text + + +def markmin2html(text,extra={},allowed={},sep='p'): + return render(text,extra,allowed,sep) + +if __name__ == '__main__': + import sys + import doctest + if sys.argv[1:2]==['-h']: + print '<html><body>'+markmin2html(__doc__)+'</body></html>' + elif len(sys.argv)>1: + fargv = open(sys.argv[1],'r') + try: + print '<html><body>'+markmin2html(fargv.read())+'</body></html>' + finally: + fargv.close() + else: + doctest.testmod() ADDED gluon/contrib/markmin/markmin2latex.py Index: gluon/contrib/markmin/markmin2latex.py ================================================================== --- gluon/contrib/markmin/markmin2latex.py +++ gluon/contrib/markmin/markmin2latex.py @@ -0,0 +1,284 @@ +#!/usr/bin/env python +# created my Massimo Di Pierro +# license MIT/BSD/GPL +import re +import cgi +import sys +import doctest +from optparse import OptionParser + +__all__ = ['render','markmin2latex'] + +META = 'META' +regex_newlines = re.compile('(\n\r)|(\r\n)') +regex_dd=re.compile('\$\$(?P<latex>.*?)\$\$') +regex_code = re.compile('('+META+')|(``(?P<t>.*?)``(:(?P<c>\w+))?)',re.S) +regex_title = re.compile('^#{1} (?P<t>[^\n]+)',re.M) +regex_maps = [ + (re.compile('[ \t\r]+\n'),'\n'), + (re.compile('[ \t\r]+\n'),'\n'), + (re.compile('\*\*(?P<t>[^\s\*]+( +[^\s\*]+)*)\*\*'),'{\\\\bf \g<t>}'), + (re.compile("''(?P<t>[^\s']+( +[^\s']+)*)''"),'{\\it \g<t>}'), + (re.compile('^#{6} (?P<t>[^\n]+)',re.M),'\n\n{\\\\bf \g<t>}\n'), + (re.compile('^#{5} (?P<t>[^\n]+)',re.M),'\n\n{\\\\bf \g<t>}\n'), + (re.compile('^#{4} (?P<t>[^\n]+)',re.M),'\n\n\\\\goodbreak\\subsubsection{\g<t>}\n'), + (re.compile('^#{3} (?P<t>[^\n]+)',re.M),'\n\n\\\\goodbreak\\subsection{\g<t>}\n'), + (re.compile('^#{2} (?P<t>[^\n]+)',re.M),'\n\n\\\\goodbreak\\section{\g<t>}\n'), + (re.compile('^#{1} (?P<t>[^\n]+)',re.M),''), + (re.compile('^\- +(?P<t>.*)',re.M),'\\\\begin{itemize}\n\\item \g<t>\n\\end{itemize}'), + (re.compile('^\+ +(?P<t>.*)',re.M),'\\\\begin{itemize}\n\\item \g<t>\n\\end{itemize}'), + (re.compile('\\\\end\{itemize\}\s+\\\\begin\{itemize\}'),'\n'), + (re.compile('\n\s+\n'),'\n\n')] +regex_table = re.compile('^\-{4,}\n(?P<t>.*?)\n\-{4,}(:(?P<c>\w+))?\n',re.M|re.S) + +regex_anchor = re.compile('\[\[(?P<t>\S+)\]\]') +regex_bibitem = re.compile('\-\s*\[\[(?P<t>\S+)\]\]') +regex_image_width = re.compile('\[\[(?P<t>.*?) +(?P<k>\S+) +(?P<p>left|right|center) +(?P<w>\d+px)\]\]') +regex_image = re.compile('\[\[(?P<t>.*?) +(?P<k>\S+) +(?P<p>left|right|center)\]\]') +#regex_video = re.compile('\[\[(?P<t>.*?) +(?P<k>\S+) +video\]\]') +#regex_audio = re.compile('\[\[(?P<t>.*?) +(?P<k>\S+) +audio\]\]') +regex_link = re.compile('\[\[(?P<t>.*?) +(?P<k>\S+)\]\]') +regex_auto = re.compile('(?<!["\w])(?P<k>\w+://[\w\.\-\?&%]+)',re.M) +regex_commas = re.compile('[ ]+(?P<t>[,;\.])') +regex_noindent = re.compile('\n\n(?P<t>[a-z])') +regex_quote_left = re.compile('"(?=\w)') +regex_quote_right = re.compile('(?=\w\.)"') + +def latex_escape(text,pound=True): + text=text.replace('\\','{\\textbackslash}') + for c in '^_&$%{}': text=text.replace(c,'\\'+c) + text=text.replace('\\{\\textbackslash\\}','{\\textbackslash}') + if pound: text=text.replace('#','\\#') + return text + +def render(text,extra={},allowed={},sep='p',image_mapper=lambda x:x): + ############################################################# + # replace all blocks marked with ``...``:class with META + # store them into segments they will be treated as code + ############################################################# + text = str(text or '') + segments, i = [], 0 + text = regex_dd.sub('``\g<latex>``:latex ',text) + text = regex_newlines.sub('\n',text) + while True: + item = regex_code.search(text,i) + if not item: break + if item.group()==META: + segments.append((None,None)) + text = text[:item.start()]+META+text[item.end():] + else: + c = item.group('c') or '' + if 'code' in allowed and not c in allowed['code']: c = '' + code = item.group('t').replace('!`!','`') + segments.append((code,c)) + text = text[:item.start()]+META+text[item.end():] + i=item.start()+3 + + + ############################################################# + # do h1,h2,h3,h4,h5,h6,b,i,ol,ul and normalize spaces + ############################################################# + + title = regex_title.search(text) + if not title: title='Title' + else: title=title.group('t') + + text = latex_escape(text,pound=False) + + texts = text.split('## References',1) + text = regex_anchor.sub('\\label{\g<t>}', texts[0]) + if len(texts)==2: + text += '\n\\begin{thebibliography}{999}\n' + text += regex_bibitem.sub('\n\\\\bibitem{\g<t>}', texts[1]) + text += '\n\\end{thebibliography}\n' + + text = '\n'.join(t.strip() for t in text.split('\n')) + for regex, sub in regex_maps: + text = regex.sub(sub,text) + text=text.replace('#','\\#') + text=text.replace('`',"'") + + ############################################################# + # process tables and blockquotes + ############################################################# + while True: + item = regex_table.search(text) + if not item: break + c = item.group('c') or '' + if 'table' in allowed and not c in allowed['table']: c = '' + content = item.group('t') + if ' | ' in content: + rows = content.replace('\n','\\\\\n').replace(' | ',' & ') + row0,row2 = rows.split('\\\\\n',1) + cols=row0.count(' & ')+1 + cal='{'+''.join('l' for j in range(cols))+'}' + tabular = '\\begin{center}\n{\\begin{tabular}'+cal+'\\hline\n' + row0+'\\\\ \\hline\n'+row2 + ' \\\\ \\hline\n\\end{tabular}}\n\\end{center}' + if row2.count('\n')>20: tabular='\\newpage\n'+tabular + text = text[:item.start()] + tabular + text[item.end():] + else: + text = text[:item.start()] + '\\begin{quote}' + content + '\\end{quote}' + text[item.end():] + + ############################################################# + # deal with images, videos, audios and links + ############################################################# + + def sub(x): + f=image_mapper(x.group('k')) + if not f: return None + return '\n\\begin{center}\\includegraphics[width=8cm]{%s}\\end{center}\n' % (f) + text = regex_image_width.sub(sub,text) + text = regex_image.sub(sub,text) + + text = regex_link.sub('{\\\\footnotesize\\href{\g<k>}{\g<t>}}', text) + text = regex_commas.sub('\g<t>',text) + text = regex_noindent.sub('\n\\\\noindent \g<t>',text) + + ### fix paths in images + regex=re.compile('\\\\_[\w_]*\.(eps|png|jpg|gif)') + while True: + match=regex.search(text) + if not match: break + text=text[:match.start()]+text[match.start()+1:] + text = regex_quote_left.sub('``',text) + text = regex_quote_right.sub("''",text) + + ############################################################# + # process all code text + ############################################################# + parts = text.split(META) + text = parts[0] + authors = [] + for i,(code,b) in enumerate(segments): + if code==None: + html = META + else: + if b=='hidden': + html='' + elif b=='author': + author = latex_escape(code.strip()) + authors.append(author) + html='' + elif b=='inxx': + html='\inxx{%s}' % latex_escape(code) + elif b=='cite': + html='~\cite{%s}' % latex_escape(code.strip()) + elif b=='ref': + html='~\ref{%s}' % latex_escape(code.strip()) + elif b=='latex': + if '\n' in code: + html='\n\\begin{equation}\n%s\n\\end{equation}\n' % code.strip() + else: + html='$%s$' % code.strip() + elif b=='latex_eqnarray': + code=code.strip() + code='\\\\'.join(x.replace('=','&=&',1) for x in code.split('\\\\')) + html='\n\\begin{eqnarray}\n%s\n\\end{eqnarray}\n' % code + elif b.startswith('latex_'): + key=b[6:] + html='\\begin{%s}%s\\end{%s}' % (key,code,key) + elif b in extra: + if code[:1]=='\n': code=code[1:] + if code[-1:]=='\n': code=code[:-1] + html = extra[b](code) + elif code[:1]=='\n' or code[:-1]=='\n': + if code[:1]=='\n': code=code[1:] + if code[-1:]=='\n': code=code[:-1] + if code.startswith('<') or code.startswith('{{') or code.startswith('http'): + html = '\\begin{lstlisting}[keywords={}]\n%s\n\\end{lstlisting}' % code + else: + html = '\\begin{lstlisting}\n%s\n\\end{lstlisting}' % code + else: + if code[:1]=='\n': code=code[1:] + if code[-1:]=='\n': code=code[:-1] + html = '{\\ft %s}' % latex_escape(code) + try: + text = text+html+parts[i+1] + except: + text = text + '... WIKI PROCESSING ERROR ...' + break + text = text.replace(' ~\\cite','~\\cite') + return text, title, authors + +WRAPPER = """ +\\documentclass[12pt]{article} +\\usepackage{hyperref} +\\usepackage{listings} +\\usepackage{upquote} +\\usepackage{color} +\\usepackage{graphicx} +\\usepackage{grffile} +\\usepackage[utf8x]{inputenc} +\\definecolor{lg}{rgb}{0.9,0.9,0.9} +\\definecolor{dg}{rgb}{0.3,0.3,0.3} +\\def\\ft{\\small\\tt} +\\lstset{ + basicstyle=\\footnotesize, + breaklines=true, basicstyle=\\ttfamily\\color{black}\\footnotesize, + keywordstyle=\\bf\\ttfamily, + commentstyle=\\it\\ttfamily, + stringstyle=\\color{dg}\\it\\ttfamily, + numbers=left, numberstyle=\\color{dg}\\tiny, stepnumber=1, numbersep=5pt, + backgroundcolor=\\color{lg}, tabsize=4, showspaces=false, + showstringspaces=false +} +\\title{%(title)s} +\\author{%(author)s} +\\begin{document} +\\maketitle +\\tableofcontents +\\newpage +%(body)s +\\end{document} +""" + +def markmin2latex(data, image_mapper=lambda x:x, extra={}, + wrapper=WRAPPER): + body, title, authors = render(data, extra=extra, image_mapper=image_mapper) + author = '\n\\and\n'.join(a.replace('\n','\\\\\n\\footnotesize ') for a in authors) + return wrapper % dict(title=title, author=author, body=body) + +if __name__ == '__main__': + parser = OptionParser() + parser.add_option("-i", "--info", dest="info", + help="markmin help") + parser.add_option("-t", "--test", dest="test", action="store_true", + default=False) + parser.add_option("-n", "--no_wrapper", dest="no_wrapper", + action="store_true",default=False) + parser.add_option("-1", "--one", dest="one",action="store_true", + default=False,help="switch section for chapter") + parser.add_option("-w", "--wrapper", dest="wrapper", default=False, + help="latex file containing header and footer") + + (options, args) = parser.parse_args() + if options.info: + import markmin2html + markmin2latex(markmin2html.__doc__) + elif options.test: + doctest.testmod() + else: + if options.wrapper: + fwrapper = open(options.wrapper,'rb') + try: + wrapper = fwrapper.read() + finally: + fwrapper.close() + elif options.no_wrapper: + wrapper = '%(body)s' + else: + wrapper = WRAPPER + for f in args: + fargs = open(f,'r') + content_data = [] + try: + content_data.append(fargs.read()) + finally: + fargs.close() + content = '\n'.join(content_data) + output= markmin2latex(content,wrapper=wrapper) + if options.one: + output=output.replace(r'\section*{',r'\chapter*{') + output=output.replace(r'\section{',r'\chapter{') + output=output.replace(r'subsection{',r'section{') + print output + ADDED gluon/contrib/markmin/markmin2pdf.py Index: gluon/contrib/markmin/markmin2pdf.py ================================================================== --- gluon/contrib/markmin/markmin2pdf.py +++ gluon/contrib/markmin/markmin2pdf.py @@ -0,0 +1,129 @@ +""" +Created by Massimo Di Pierro +Licese BSD +""" + +import subprocess +import os +import os.path +import re +import sys +from tempfile import mkstemp, mkdtemp, NamedTemporaryFile +from markmin2latex import markmin2latex + +__all__ = ['markmin2pdf'] + +def removeall(path): + + ERROR_STR= """Error removing %(path)s, %(error)s """ + def rmgeneric(path, __func__): + try: + __func__(path) + except OSError, (errno, strerror): + print ERROR_STR % {'path' : path, 'error': strerror } + + files=[path] + + while files: + file=files[0] + if os.path.isfile(file): + f=os.remove + rmgeneric(file, os.remove) + del files[0] + elif os.path.isdir(file): + nested = os.listdir(file) + if not nested: + rmgeneric(file, os.rmdir) + del files[0] + else: + files = [os.path.join(file,x) for x in nested] + files + + +def latex2pdf(latex, pdflatex='pdflatex', passes=3): + """ + calls pdflatex in a tempfolder + + Arguments: + + - pdflatex: path to the pdflatex command. Default is just 'pdflatex'. + - passes: defines how often pdflates should be run in the texfile. + """ + + pdflatex=pdflatex + passes=passes + warnings=[] + + # setup the envoriment + tmpdir = mkdtemp() + texfile = open(tmpdir+'/test.tex','wb') + texfile.write(latex) + texfile.seek(0) + texfile.close() + texfile = os.path.abspath(texfile.name) + + # start doing some work + for i in range(0, passes): + logfd,logname = mkstemp() + outfile=os.fdopen(logfd) + try: + ret = subprocess.call([pdflatex, + '-interaction=nonstopmode', + '-output-format', 'pdf', + '-output-directory', tmpdir, + texfile], + cwd=os.path.dirname(texfile), stdout=outfile, + stderr=subprocess.PIPE) + finally: + outfile.close() + re_errors=re.compile('^\!(.*)$',re.M) + re_warnings=re.compile('^LaTeX Warning\:(.*)$',re.M) + flog = open(logname) + try: + loglines = flog.read() + finally: + flog.close() + errors=re_errors.findall(loglines) + warnings=re_warnings.findall(loglines) + os.unlink(logname) + + pdffile=texfile.rsplit('.',1)[0]+'.pdf' + if os.path.isfile(pdffile): + fpdf = open(pdffile, 'rb') + try: + data = fpdf.read() + finally: + fpdf.close() + else: + data = None + removeall(tmpdir) + return data, warnings, errors + + +def markmin2pdf(text, image_mapper=lambda x: None, extra={}): + return latex2pdf(markmin2latex(text,image_mapper=image_mapper, extra=extra)) + + +if __name__ == '__main__': + import sys + import doctest + import markmin2html + if sys.argv[1:2]==['-h']: + data, warnings, errors = markmin2pdf(markmin2html.__doc__) + if errors: + print 'ERRORS:'+'\n'.join(errors) + print 'WARNGINS:'+'\n'.join(warnings) + else: + print data + elif len(sys.argv)>1: + fargv = open(sys.argv[1],'rb') + try: + data, warnings, errors = markmin2pdf(fargv.read()) + finally: + fargv.close() + if errors: + print 'ERRORS:'+'\n'.join(errors) + print 'WARNGINS:'+'\n'.join(warnings) + else: + print data + else: + doctest.testmod() ADDED gluon/contrib/memdb.py Index: gluon/contrib/memdb.py ================================================================== --- gluon/contrib/memdb.py +++ gluon/contrib/memdb.py @@ -0,0 +1,907 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +This file is part of web2py Web Framework (Copyrighted, 2007-2009). +Developed by Massimo Di Pierro <mdipierro@cs.depaul.edu> and +Robin B <robi123@gmail.com>. +License: GPL v2 +""" + +__all__ = ['MEMDB', 'Field'] + +import re +import sys +import os +import types +import datetime +import thread +import cStringIO +import csv +import copy +import gluon.validators as validators +from gluon.storage import Storage +import random + +SQL_DIALECTS = {'memcache': { + 'boolean': bool, + 'string': unicode, + 'text': unicode, + 'password': unicode, + 'blob': unicode, + 'upload': unicode, + 'integer': long, + 'double': float, + 'date': datetime.date, + 'time': datetime.time, + 'datetime': datetime.datetime, + 'id': int, + 'reference': int, + 'lower': None, + 'upper': None, + 'is null': 'IS NULL', + 'is not null': 'IS NOT NULL', + 'extract': None, + 'left join': None, + }} + + +def cleanup(text): + if re.compile('[^0-9a-zA-Z_]').findall(text): + raise SyntaxError, \ + 'Can\'t cleanup \'%s\': only [0-9a-zA-Z_] allowed in table and field names' % text + return text + + +def assert_filter_fields(*fields): + for field in fields: + if isinstance(field, (Field, Expression)) and field.type\ + in ['text', 'blob']: + raise SyntaxError, 'AppEngine does not index by: %s'\ + % field.type + + +def dateobj_to_datetime(object): + + # convert dates,times to datetimes for AppEngine + + if isinstance(object, datetime.date): + object = datetime.datetime(object.year, object.month, + object.day) + if isinstance(object, datetime.time): + object = datetime.datetime( + 1970, + 1, + 1, + object.hour, + object.minute, + object.second, + object.microsecond, + ) + return object + + +def sqlhtml_validators(field_type, length): + v = { + 'boolean': [], + 'string': validators.IS_LENGTH(length), + 'text': [], + 'password': validators.IS_LENGTH(length), + 'blob': [], + 'upload': [], + 'double': validators.IS_FLOAT_IN_RANGE(-1e100, 1e100), + 'integer': validators.IS_INT_IN_RANGE(-1e100, 1e100), + 'date': validators.IS_DATE(), + 'time': validators.IS_TIME(), + 'datetime': validators.IS_DATETIME(), + 'reference': validators.IS_INT_IN_RANGE(0, 1e100), + } + try: + return v[field_type[:9]] + except KeyError: + return [] + + +class DALStorage(dict): + + """ + a dictionary that let you do d['a'] as well as d.a + """ + + def __getattr__(self, key): + return self[key] + + def __setattr__(self, key, value): + if key in self: + raise SyntaxError, 'Object \'%s\'exists and cannot be redefined' % key + self[key] = value + + def __repr__(self): + return '<DALStorage ' + dict.__repr__(self) + '>' + + +class SQLCallableList(list): + + def __call__(self): + return copy.copy(self) + + +class MEMDB(DALStorage): + + """ + an instance of this class represents a database connection + + Example:: + + db=MEMDB(Client()) + db.define_table('tablename',Field('fieldname1'), + Field('fieldname2')) + """ + + def __init__(self, client): + self._dbname = 'memdb' + self['_lastsql'] = '' + self.tables = SQLCallableList() + self._translator = SQL_DIALECTS['memcache'] + self.client = client + + def define_table( + self, + tablename, + *fields, + **args + ): + tablename = cleanup(tablename) + if tablename in dir(self) or tablename[0] == '_': + raise SyntaxError, 'invalid table name: %s' % tablename + if not tablename in self.tables: + self.tables.append(tablename) + else: + raise SyntaxError, 'table already defined: %s' % tablename + t = self[tablename] = Table(self, tablename, *fields) + t._create() + return t + + def __call__(self, where=''): + return Set(self, where) + + +class SQLALL(object): + + def __init__(self, table): + self.table = table + + +class Table(DALStorage): + + """ + an instance of this class represents a database table + + Example:: + + db=MEMDB(Client()) + db.define_table('users',Field('name')) + db.users.insert(name='me') + """ + + def __init__( + self, + db, + tablename, + *fields + ): + self._db = db + self._tablename = tablename + self.fields = SQLCallableList() + self._referenced_by = [] + fields = list(fields) + fields.insert(0, Field('id', 'id')) + for field in fields: + self.fields.append(field.name) + self[field.name] = field + field._tablename = self._tablename + field._table = self + field._db = self._db + self.ALL = SQLALL(self) + + def _create(self): + fields = [] + myfields = {} + for k in self.fields: + field = self[k] + attr = {} + if not field.type[:9] in ['id', 'reference']: + if field.notnull: + attr = dict(required=True) + if field.type[:2] == 'id': + continue + if field.type[:9] == 'reference': + referenced = field.type[10:].strip() + if not referenced: + raise SyntaxError, \ + 'Table %s: reference \'%s\' to nothing!' % (self._tablename, k) + if not referenced in self._db: + raise SyntaxError, \ + 'Table: table %s does not exist' % referenced + referee = self._db[referenced] + ftype = \ + self._db._translator[field.type[:9]]( + self._db[referenced]._tableobj) + if self._tablename in referee.fields: # ## THIS IS OK + raise SyntaxError, \ + 'Field: table \'%s\' has same name as a field ' \ + 'in referenced table \'%s\'' % (self._tablename, referenced) + self._db[referenced]._referenced_by.append((self._tablename, + field.name)) + elif not field.type in self._db._translator\ + or not self._db._translator[field.type]: + raise SyntaxError, 'Field: unkown field type %s' % field.type + self._tableobj = self._db.client + return None + + def create(self): + + # nothing to do, here for backward compatility + + pass + + def drop(self): + + # nothing to do, here for backward compatibility + + self._db(self.id > 0).delete() + + def insert(self, **fields): + id = self._create_id() + if self.update(id, **fields): + return long(id) + else: + return None + + def get(self, id): + val = self._tableobj.get(self._id_to_key(id)) + if val: + return Storage(val) + else: + return None + + def update(self, id, **fields): + for field in fields: + if not field in fields and self[field].default\ + != None: + fields[field] = self[field].default + if field in fields: + fields[field] = obj_represent(fields[field], + self[field].type, self._db) + return self._tableobj.set(self._id_to_key(id), fields) + + def delete(self, id): + return self._tableobj.delete(self._id_to_key(id)) + + def _shard_key(self, shard): + return self._id_to_key('s/%s' % shard) + + def _id_to_key(self, id): + return '__memdb__/t/%s/k/%s' % (self._tablename, str(id)) + + def _create_id(self): + shard = random.randint(10, 99) + shard_id = self._shard_key(shard) + id = self._tableobj.incr(shard_id) + if not id: + if self._tableobj.set(shard_id, '0'): + id = 0 + else: + raise Exception, 'cannot set memcache' + return long(str(shard) + str(id)) + + def __str__(self): + return self._tablename + + +class Expression(object): + + def __init__( + self, + name, + type='string', + db=None, + ): + (self.name, self.type, self._db) = (name, type, db) + + def __str__(self): + return self.name + + def __or__(self, other): # for use in sortby + assert_filter_fields(self, other) + return Expression(self.name + '|' + other.name, None, None) + + def __invert__(self): + assert_filter_fields(self) + return Expression('-' + self.name, self.type, None) + + # for use in Query + + def __eq__(self, value): + return Query(self, '=', value) + + def __ne__(self, value): + return Query(self, '!=', value) + + def __lt__(self, value): + return Query(self, '<', value) + + def __le__(self, value): + return Query(self, '<=', value) + + def __gt__(self, value): + return Query(self, '>', value) + + def __ge__(self, value): + return Query(self, '>=', value) + + # def like(self,value): return Query(self,' LIKE ',value) + # def belongs(self,value): return Query(self,' IN ',value) + # for use in both Query and sortby + + def __add__(self, other): + return Expression('%s+%s' % (self, other), 'float', None) + + def __sub__(self, other): + return Expression('%s-%s' % (self, other), 'float', None) + + def __mul__(self, other): + return Expression('%s*%s' % (self, other), 'float', None) + + def __div__(self, other): + return Expression('%s/%s' % (self, other), 'float', None) + + +class Field(Expression): + + """ + an instance of this class represents a database field + + example:: + + a = Field(name, 'string', length=32, required=False, + default=None, requires=IS_NOT_EMPTY(), notnull=False, + unique=False, uploadfield=True) + + to be used as argument of GQLDB.define_table + + allowed field types: + string, boolean, integer, double, text, blob, + date, time, datetime, upload, password + + strings must have a length or 512 by default. + fields should have a default or they will be required in SQLFORMs + the requires argument are used to validate the field input in SQLFORMs + + """ + + def __init__( + self, + fieldname, + type='string', + length=None, + default=None, + required=False, + requires=sqlhtml_validators, + ondelete='CASCADE', + notnull=False, + unique=False, + uploadfield=True, + ): + + self.name = cleanup(fieldname) + if fieldname in dir(Table) or fieldname[0] == '_': + raise SyntaxError, 'Field: invalid field name: %s' % fieldname + if isinstance(type, Table): + type = 'reference ' + type._tablename + if not length: + length = 512 + self.type = type # 'string', 'integer' + self.length = length # the length of the string + self.default = default # default value for field + self.required = required # is this field required + self.ondelete = ondelete.upper() # this is for reference fields only + self.notnull = notnull + self.unique = unique + self.uploadfield = uploadfield + if requires == sqlhtml_validators: + requires = sqlhtml_validators(type, length) + elif requires is None: + requires = [] + self.requires = requires # list of validators + + def formatter(self, value): + if value is None or not self.requires: + return value + if not isinstance(self.requires, (list, tuple)): + requires = [self.requires] + else: + requires = copy.copy(self.requires) + requires.reverse() + for item in requires: + if hasattr(item, 'formatter'): + value = item.formatter(value) + return value + + def __str__(self): + return '%s.%s' % (self._tablename, self.name) + + +MEMDB.Field = Field # ## required by gluon/globals.py session.connect + + +def obj_represent(object, fieldtype, db): + if object != None: + if fieldtype == 'date' and not isinstance(object, + datetime.date): + (y, m, d) = [int(x) for x in str(object).strip().split('-')] + object = datetime.date(y, m, d) + elif fieldtype == 'time' and not isinstance(object, datetime.time): + time_items = [int(x) for x in str(object).strip().split(':')[:3]] + if len(time_items) == 3: + (h, mi, s) = time_items + else: + (h, mi, s) = time_items + [0] + object = datetime.time(h, mi, s) + elif fieldtype == 'datetime' and not isinstance(object, + datetime.datetime): + (y, m, d) = [int(x) for x in + str(object)[:10].strip().split('-')] + time_items = [int(x) for x in + str(object)[11:].strip().split(':')[:3]] + if len(time_items) == 3: + (h, mi, s) = time_items + else: + (h, mi, s) = time_items + [0] + object = datetime.datetime( + y, + m, + d, + h, + mi, + s, + ) + elif fieldtype == 'integer' and not isinstance(object, long): + object = long(object) + + return object + + +class QueryException: + + def __init__(self, **a): + self.__dict__ = a + + +class Query(object): + + """ + A query object necessary to define a set. + It can be stored or can be passed to GQLDB.__call__() to obtain a Set + + Example: + query=db.users.name=='Max' + set=db(query) + records=set.select() + """ + + def __init__( + self, + left, + op=None, + right=None, + ): + if isinstance(right, (Field, Expression)): + raise SyntaxError, \ + 'Query: right side of filter must be a value or entity' + if isinstance(left, Field) and left.name == 'id': + if op == '=': + self.get_one = \ + QueryException(tablename=left._tablename, + id=long(right)) + return + else: + raise SyntaxError, 'only equality by id is supported' + raise SyntaxError, 'not supported' + + def __str__(self): + return str(self.left) + + +class Set(object): + + """ + As Set represents a set of records in the database, + the records are identified by the where=Query(...) object. + normally the Set is generated by GQLDB.__call__(Query(...)) + + given a set, for example + set=db(db.users.name=='Max') + you can: + set.update(db.users.name='Massimo') + set.delete() # all elements in the set + set.select(orderby=db.users.id,groupby=db.users.name,limitby=(0,10)) + and take subsets: + subset=set(db.users.id<5) + """ + + def __init__(self, db, where=None): + self._db = db + self._tables = [] + self.filters = [] + if hasattr(where, 'get_all'): + self.where = where + self._tables.insert(0, where.get_all) + elif hasattr(where, 'get_one') and isinstance(where.get_one, + QueryException): + self.where = where.get_one + else: + + # find out which tables are involved + + if isinstance(where, Query): + self.filters = where.left + self.where = where + self._tables = [field._tablename for (field, op, val) in + self.filters] + + def __call__(self, where): + if isinstance(self.where, QueryException) or isinstance(where, + QueryException): + raise SyntaxError, \ + 'neither self.where nor where can be a QueryException instance' + if self.where: + return Set(self._db, self.where & where) + else: + return Set(self._db, where) + + def _get_table_or_raise(self): + tablenames = list(set(self._tables)) # unique + if len(tablenames) < 1: + raise SyntaxError, 'Set: no tables selected' + if len(tablenames) > 1: + raise SyntaxError, 'Set: no join in appengine' + return self._db[tablenames[0]]._tableobj + + def _getitem_exception(self): + (tablename, id) = (self.where.tablename, self.where.id) + fields = self._db[tablename].fields + self.colnames = ['%s.%s' % (tablename, t) for t in fields] + item = self._db[tablename].get(id) + return (item, fields, tablename, id) + + def _select_except(self): + (item, fields, tablename, id) = self._getitem_exception() + if not item: + return [] + new_item = [] + for t in fields: + if t == 'id': + new_item.append(long(id)) + else: + new_item.append(getattr(item, t)) + r = [new_item] + return Rows(self._db, r, *self.colnames) + + def select(self, *fields, **attributes): + """ + Always returns a Rows object, even if it may be empty + """ + + if isinstance(self.where, QueryException): + return self._select_except() + else: + raise SyntaxError, 'select arguments not supported' + + def count(self): + return len(self.select()) + + def delete(self): + if isinstance(self.where, QueryException): + (item, fields, tablename, id) = self._getitem_exception() + if not item: + return + self._db[tablename].delete(id) + else: + raise Exception, 'deletion not implemented' + + def update(self, **update_fields): + if isinstance(self.where, QueryException): + (item, fields, tablename, id) = self._getitem_exception() + if not item: + return + for (key, value) in update_fields.items(): + setattr(item, key, value) + self._db[tablename].update(id, **item) + else: + raise Exception, 'update not implemented' + + +def update_record( + t, + s, + id, + a, + ): + item = s.get(id) + for (key, value) in a.items(): + t[key] = value + setattr(item, key, value) + s.update(id, **item) + + +class Rows(object): + + """ + A wrapper for the return value of a select. It basically represents a table. + It has an iterator and each row is represented as a dictionary. + """ + + # ## this class still needs some work to care for ID/OID + + def __init__( + self, + db, + response, + *colnames + ): + self._db = db + self.colnames = colnames + self.response = response + + def __len__(self): + return len(self.response) + + def __getitem__(self, i): + if i >= len(self.response) or i < 0: + raise SyntaxError, 'Rows: no such row: %i' % i + if len(self.response[0]) != len(self.colnames): + raise SyntaxError, 'Rows: internal error' + row = DALStorage() + for j in xrange(len(self.colnames)): + value = self.response[i][j] + if isinstance(value, unicode): + value = value.encode('utf-8') + packed = self.colnames[j].split('.') + try: + (tablename, fieldname) = packed + except: + if not '_extra' in row: + row['_extra'] = DALStorage() + row['_extra'][self.colnames[j]] = value + continue + table = self._db[tablename] + field = table[fieldname] + if not tablename in row: + row[tablename] = DALStorage() + if field.type[:9] == 'reference': + referee = field.type[10:].strip() + rid = value + row[tablename][fieldname] = rid + elif field.type == 'boolean' and value != None: + + # row[tablename][fieldname]=Set(self._db[referee].id==rid) + + if value == True or value == 'T': + row[tablename][fieldname] = True + else: + row[tablename][fieldname] = False + elif field.type == 'date' and value != None\ + and not isinstance(value, datetime.date): + (y, m, d) = [int(x) for x in + str(value).strip().split('-')] + row[tablename][fieldname] = datetime.date(y, m, d) + elif field.type == 'time' and value != None\ + and not isinstance(value, datetime.time): + time_items = [int(x) for x in + str(value).strip().split(':')[:3]] + if len(time_items) == 3: + (h, mi, s) = time_items + else: + (h, mi, s) = time_items + [0] + row[tablename][fieldname] = datetime.time(h, mi, s) + elif field.type == 'datetime' and value != None\ + and not isinstance(value, datetime.datetime): + (y, m, d) = [int(x) for x in + str(value)[:10].strip().split('-')] + time_items = [int(x) for x in + str(value)[11:].strip().split(':')[:3]] + if len(time_items) == 3: + (h, mi, s) = time_items + else: + (h, mi, s) = time_items + [0] + row[tablename][fieldname] = datetime.datetime( + y, + m, + d, + h, + mi, + s, + ) + else: + row[tablename][fieldname] = value + if fieldname == 'id': + id = row[tablename].id + row[tablename].update_record = lambda t = row[tablename], \ + s = self._db[tablename], id = id, **a: update_record(t, + s, id, a) + for (referee_table, referee_name) in \ + table._referenced_by: + s = self._db[referee_table][referee_name] + row[tablename][referee_table] = Set(self._db, s + == id) + if len(row.keys()) == 1: + return row[row.keys()[0]] + return row + + def __iter__(self): + """ + iterator over records + """ + + for i in xrange(len(self)): + yield self[i] + + def __str__(self): + """ + serializes the table into a csv file + """ + + s = cStringIO.StringIO() + writer = csv.writer(s) + writer.writerow(self.colnames) + c = len(self.colnames) + for i in xrange(len(self)): + row = [self.response[i][j] for j in xrange(c)] + for k in xrange(c): + if isinstance(row[k], unicode): + row[k] = row[k].encode('utf-8') + writer.writerow(row) + return s.getvalue() + + def xml(self): + """ + serializes the table using sqlhtml.SQLTABLE (if present) + """ + + return sqlhtml.SQLTABLE(self).xml() + + +def test_all(): + """ + How to run from web2py dir: + export PYTHONPATH=.:YOUR_PLATFORMS_APPENGINE_PATH + python gluon/contrib/memdb.py + + Setup the UTC timezone and database stubs + + >>> import os + >>> os.environ['TZ'] = 'UTC' + >>> import time + >>> if hasattr(time, 'tzset'): + ... time.tzset() + >>> + >>> from google.appengine.api import apiproxy_stub_map + >>> from google.appengine.api.memcache import memcache_stub + >>> apiproxy_stub_map.apiproxy = apiproxy_stub_map.APIProxyStubMap() + >>> apiproxy_stub_map.apiproxy.RegisterStub('memcache', memcache_stub.MemcacheServiceStub()) + + Create a table with all possible field types + >>> from google.appengine.api.memcache import Client + >>> db=MEMDB(Client()) + >>> tmp=db.define_table('users', Field('stringf','string',length=32,required=True), Field('booleanf','boolean',default=False), Field('passwordf','password',notnull=True), Field('blobf','blob'), Field('uploadf','upload'), Field('integerf','integer',unique=True), Field('doublef','double',unique=True,notnull=True), Field('datef','date',default=datetime.date.today()), Field('timef','time'), Field('datetimef','datetime'), migrate='test_user.table') + + Insert a field + + >>> user_id = db.users.insert(stringf='a',booleanf=True,passwordf='p',blobf='0A', uploadf=None, integerf=5,doublef=3.14, datef=datetime.date(2001,1,1), timef=datetime.time(12,30,15), datetimef=datetime.datetime(2002,2,2,12,30,15)) + >>> user_id != None + True + + Select all + + # >>> all = db().select(db.users.ALL) + + Drop the table + + # >>> db.users.drop() + + Select many entities + + >>> tmp = db.define_table(\"posts\", Field('body','text'), Field('total','integer'), Field('created_at','datetime')) + >>> many = 20 #2010 # more than 1000 single fetch limit (it can be slow) + >>> few = 5 + >>> most = many - few + >>> 0 < few < most < many + True + >>> for i in range(many): + ... f=db.posts.insert(body='', total=i,created_at=datetime.datetime(2008, 7, 6, 14, 15, 42, i)) + >>> + + # test timezones + >>> class TZOffset(datetime.tzinfo): + ... def __init__(self,offset=0): + ... self.offset = offset + ... def utcoffset(self, dt): return datetime.timedelta(hours=self.offset) + ... def dst(self, dt): return datetime.timedelta(0) + ... def tzname(self, dt): return 'UTC' + str(self.offset) + ... + >>> SERVER_OFFSET = -8 + >>> + >>> stamp = datetime.datetime(2008, 7, 6, 14, 15, 42, 828201) + >>> post_id = db.posts.insert(created_at=stamp,body='body1') + >>> naive_stamp = db(db.posts.id==post_id).select()[0].created_at + >>> utc_stamp=naive_stamp.replace(tzinfo=TZOffset()) + >>> server_stamp = utc_stamp.astimezone(TZOffset(SERVER_OFFSET)) + >>> stamp == naive_stamp + True + >>> utc_stamp == server_stamp + True + >>> rows = db(db.posts.id==post_id).select() + >>> len(rows) == 1 + True + >>> rows[0].body == 'body1' + True + >>> db(db.posts.id==post_id).delete() + >>> rows = db(db.posts.id==post_id).select() + >>> len(rows) == 0 + True + + >>> id = db.posts.insert(total='0') # coerce str to integer + >>> rows = db(db.posts.id==id).select() + >>> len(rows) == 1 + True + >>> rows[0].total == 0 + True + + Examples of insert, select, update, delete + + >>> tmp=db.define_table('person', Field('name'), Field('birth','date'), migrate='test_person.table') + >>> marco_id=db.person.insert(name=\"Marco\",birth='2005-06-22') + >>> person_id=db.person.insert(name=\"Massimo\",birth='1971-12-21') + >>> me=db(db.person.id==person_id).select()[0] # test select + >>> me.name + 'Massimo' + >>> db(db.person.id==person_id).update(name='massimo') # test update + >>> me = db(db.person.id==person_id).select()[0] + >>> me.name + 'massimo' + >>> str(me.birth) + '1971-12-21' + + # resave date to ensure it comes back the same + >>> me=db(db.person.id==person_id).update(birth=me.birth) # test update + >>> me = db(db.person.id==person_id).select()[0] + >>> me.birth + datetime.date(1971, 12, 21) + >>> db(db.person.id==marco_id).delete() # test delete + >>> len(db(db.person.id==marco_id).select()) + 0 + + Update a single record + + >>> me.update_record(name=\"Max\") + >>> me.name + 'Max' + >>> me = db(db.person.id == person_id).select()[0] + >>> me.name + 'Max' + + """ + +SQLField = Field +SQLTable = Table +SQLXorable = Expression +SQLQuery = Query +SQLSet = Set +SQLRows = Rows +SQLStorage = DALStorage + +if __name__ == '__main__': + import doctest + doctest.testmod() + ADDED gluon/contrib/pam.py Index: gluon/contrib/pam.py ================================================================== --- gluon/contrib/pam.py +++ gluon/contrib/pam.py @@ -0,0 +1,124 @@ +# (c) 2007 Chris AtLee <chris@atlee.ca> +# Licensed under the MIT license: +# http://www.opensource.org/licenses/mit-license.php +""" +PAM module for python + +Provides an authenticate function that will allow the caller to authenticate +a user against the Pluggable Authentication Modules (PAM) on the system. + +Implemented using ctypes, so no compilation is necessary. +""" +__all__ = ['authenticate'] + +from ctypes import CDLL, POINTER, Structure, CFUNCTYPE, cast, pointer, sizeof +from ctypes import c_void_p, c_uint, c_char_p, c_char, c_int +from ctypes.util import find_library + +LIBPAM = CDLL(find_library("pam")) +LIBC = CDLL(find_library("c")) + +CALLOC = LIBC.calloc +CALLOC.restype = c_void_p +CALLOC.argtypes = [c_uint, c_uint] + +STRDUP = LIBC.strdup +STRDUP.argstypes = [c_char_p] +STRDUP.restype = POINTER(c_char) # NOT c_char_p !!!! + +# Various constants +PAM_PROMPT_ECHO_OFF = 1 +PAM_PROMPT_ECHO_ON = 2 +PAM_ERROR_MSG = 3 +PAM_TEXT_INFO = 4 + +class PamHandle(Structure): + """wrapper class for pam_handle_t""" + _fields_ = [ + ("handle", c_void_p) + ] + + def __init__(self): + Structure.__init__(self) + self.handle = 0 + +class PamMessage(Structure): + """wrapper class for pam_message structure""" + _fields_ = [ + ("msg_style", c_int), + ("msg", c_char_p), + ] + + def __repr__(self): + return "<PamMessage %i '%s'>" % (self.msg_style, self.msg) + +class PamResponse(Structure): + """wrapper class for pam_response structure""" + _fields_ = [ + ("resp", c_char_p), + ("resp_retcode", c_int), + ] + + def __repr__(self): + return "<PamResponse %i '%s'>" % (self.resp_retcode, self.resp) + +CONV_FUNC = CFUNCTYPE(c_int, + c_int, POINTER(POINTER(PamMessage)), + POINTER(POINTER(PamResponse)), c_void_p) + +class PamConv(Structure): + """wrapper class for pam_conv structure""" + _fields_ = [ + ("conv", CONV_FUNC), + ("appdata_ptr", c_void_p) + ] + +PAM_START = LIBPAM.pam_start +PAM_START.restype = c_int +PAM_START.argtypes = [c_char_p, c_char_p, POINTER(PamConv), + POINTER(PamHandle)] + +PAM_AUTHENTICATE = LIBPAM.pam_authenticate +PAM_AUTHENTICATE.restype = c_int +PAM_AUTHENTICATE.argtypes = [PamHandle, c_int] + +def authenticate(username, password, service='login'): + """Returns True if the given username and password authenticate for the + given service. Returns False otherwise + + ``username``: the username to authenticate + + ``password``: the password in plain text + + ``service``: the PAM service to authenticate against. + Defaults to 'login'""" + @CONV_FUNC + def my_conv(n_messages, messages, p_response, app_data): + """Simple conversation function that responds to any + prompt where the echo is off with the supplied password""" + # Create an array of n_messages response objects + addr = CALLOC(n_messages, sizeof(PamResponse)) + p_response[0] = cast(addr, POINTER(PamResponse)) + for i in range(n_messages): + if messages[i].contents.msg_style == PAM_PROMPT_ECHO_OFF: + pw_copy = STRDUP(str(password)) + p_response.contents[i].resp = cast(pw_copy, c_char_p) + p_response.contents[i].resp_retcode = 0 + return 0 + + handle = PamHandle() + conv = PamConv(my_conv, 0) + retval = PAM_START(service, username, pointer(conv), pointer(handle)) + + if retval != 0: + # TODO: This is not an authentication error, something + # has gone wrong starting up PAM + return False + + retval = PAM_AUTHENTICATE(handle, 0) + return retval == 0 + +if __name__ == "__main__": + import getpass + print authenticate(getpass.getuser(), getpass.getpass()) + ADDED gluon/contrib/populate.py Index: gluon/contrib/populate.py ================================================================== --- gluon/contrib/populate.py +++ gluon/contrib/populate.py cannot compute difference between binary files ADDED gluon/contrib/pyfpdf/README Index: gluon/contrib/pyfpdf/README ================================================================== --- gluon/contrib/pyfpdf/README +++ gluon/contrib/pyfpdf/README @@ -0,0 +1,1 @@ +Read more about this http://code.google.com/p/pyfpdf ADDED gluon/contrib/pyfpdf/__init__.py Index: gluon/contrib/pyfpdf/__init__.py ================================================================== --- gluon/contrib/pyfpdf/__init__.py +++ gluon/contrib/pyfpdf/__init__.py @@ -0,0 +1,4 @@ +from fpdf import FPDF +from html import HTMLMixin +from template import Template + ADDED gluon/contrib/pyfpdf/designer.py Index: gluon/contrib/pyfpdf/designer.py ================================================================== --- gluon/contrib/pyfpdf/designer.py +++ gluon/contrib/pyfpdf/designer.py @@ -0,0 +1,735 @@ +#!/usr/bin/python +# -*- coding: latin-1 -*- +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 3, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. + +"Visual Template designer for PyFPDF (using wxPython OGL library)" + +__author__ = "Mariano Reingart <reingart@gmail.com>" +__copyright__ = "Copyright (C) 2011 Mariano Reingart" +__license__ = "GPL 3.0" +__version__ = "1.01a" + +# Based on: +# * pySjetch.py wxPython sample application +# * OGL.py and other wxPython demo modules + + +import os, sys +import wx +import wx.lib.ogl as ogl +from wx.lib.wordwrap import wordwrap + +DEBUG = True + + +class CustomDialog(wx.Dialog): + "A dinamyc dialog to ask user about arbitrary fields" + + def __init__( + self, parent, ID, title, size=wx.DefaultSize, pos=wx.DefaultPosition, + style=wx.DEFAULT_DIALOG_STYLE, fields=None, data=None, + ): + + wx.Dialog.__init__ (self, parent, ID, title, pos, size, style) + + sizer = wx.BoxSizer(wx.VERTICAL) + + self.textctrls = {} + for field in fields: + box = wx.BoxSizer(wx.HORIZONTAL) + label = wx.StaticText(self, -1, field) + label.SetHelpText("This is the help text for the label") + box.Add(label, 1, wx.ALIGN_CENTRE|wx.ALL, 5) + text = wx.TextCtrl(self, -1, "", size=(80,-1)) + text.SetHelpText("Here's some help text for field #1") + if field in data: + text.SetValue(repr(data[field])) + box.Add(text, 1, wx.ALIGN_CENTRE|wx.ALL, 1) + sizer.Add(box, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 1) + self.textctrls[field] = text + + line = wx.StaticLine(self, -1, size=(20,-1), style=wx.LI_HORIZONTAL) + sizer.Add(line, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.RIGHT|wx.TOP, 5) + + btnsizer = wx.StdDialogButtonSizer() + + btn = wx.Button(self, wx.ID_OK) + btn.SetHelpText("The OK button completes the dialog") + btn.SetDefault() + btnsizer.AddButton(btn) + + btn = wx.Button(self, wx.ID_CANCEL) + btn.SetHelpText("The Cancel button cancels the dialog. (Cool, huh?)") + btnsizer.AddButton(btn) + btnsizer.Realize() + + sizer.Add(btnsizer, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5) + + self.SetSizer(sizer) + sizer.Fit(self) + + @classmethod + def do_input(Class, parent, title, fields, data): + dlg = Class(parent, -1, title, size=(350, 200), + style=wx.DEFAULT_DIALOG_STYLE, # & ~wx.CLOSE_BOX, + fields=fields, data=data + ) + dlg.CenterOnScreen() + while 1: + val = dlg.ShowModal() + if val == wx.ID_OK: + values = {} + for field in fields: + try: + values[field] = eval(dlg.textctrls[field].GetValue()) + except Exception, e: + msg = wx.MessageDialog(parent, unicode(e), + "Error in field %s" % field, + wx.OK | wx.ICON_INFORMATION + ) + msg.ShowModal() + msg.Destroy() + break + else: + return dict([(field, values[field]) for field in fields]) + else: + return None + + +class MyEvtHandler(ogl.ShapeEvtHandler): + "Custom Event Handler for Shapes" + def __init__(self, callback): + ogl.ShapeEvtHandler.__init__(self) + self.callback = callback + + def OnLeftClick(self, x, y, keys=0, attachment=0): + shape = self.GetShape() + canvas = shape.GetCanvas() + dc = wx.ClientDC(canvas) + canvas.PrepareDC(dc) + + if shape.Selected() and keys & ogl.KEY_SHIFT: + shape.Select(False, dc) + #canvas.Redraw(dc) + canvas.Refresh(False) + else: + redraw = False + shapeList = canvas.GetDiagram().GetShapeList() + toUnselect = [] + + for s in shapeList: + if s.Selected() and not keys & ogl.KEY_SHIFT: + # If we unselect it now then some of the objects in + # shapeList will become invalid (the control points are + # shapes too!) and bad things will happen... + toUnselect.append(s) + + shape.Select(True, dc) + + if toUnselect: + for s in toUnselect: + s.Select(False, dc) + ##canvas.Redraw(dc) + canvas.Refresh(False) + + self.callback() + + def OnEndDragLeft(self, x, y, keys=0, attachment=0): + shape = self.GetShape() + ogl.ShapeEvtHandler.OnEndDragLeft(self, x, y, keys, attachment) + + if not shape.Selected(): + self.OnLeftClick(x, y, keys, attachment) + + self.callback() + + def OnSizingEndDragLeft(self, pt, x, y, keys, attch): + ogl.ShapeEvtHandler.OnSizingEndDragLeft(self, pt, x, y, keys, attch) + self.callback() + + def OnMovePost(self, dc, x, y, oldX, oldY, display): + shape = self.GetShape() + ogl.ShapeEvtHandler.OnMovePost(self, dc, x, y, oldX, oldY, display) + self.callback() + if "wxMac" in wx.PlatformInfo: + shape.GetCanvas().Refresh(False) + + def OnLeftDoubleClick(self, x, y, keys = 0, attachment = 0): + self.callback("LeftDoubleClick") + + def OnRightClick(self, *dontcare): + self.callback("RightClick") + + +class Element(object): + "Visual class that represent a placeholder in the template" + + fields = ['name', 'type', + 'x1', 'y1', 'x2', 'y2', + 'font', 'size', + 'bold', 'italic', 'underline', + 'foreground', 'background', + 'align', 'text', 'priority',] + + def __init__(self, canvas=None, frame=None, zoom=5.0, static=False, **kwargs): + self.kwargs = kwargs + self.zoom = zoom + self.frame = frame + self.canvas = canvas + self.static = static + + name = kwargs['name'] + kwargs['type'] + type = kwargs['type'] + + x, y, w, h = self.set_coordinates(kwargs['x1'], kwargs['y1'], kwargs['x2'], kwargs['y2']) + + text = kwargs['text'] + + shape = self.shape = ogl.RectangleShape(w, h) + + if not static: + shape.SetDraggable(True, True) + + shape.SetX(x) + shape.SetY(y) + #if pen: shape.SetPen(pen) + #if brush: shape.SetBrush(brush) + shape.SetBrush(wx.TRANSPARENT_BRUSH) + + if type not in ('L', 'B', 'BC'): + if not static: + pen = wx.LIGHT_GREY_PEN + else: + pen = wx.RED_PEN + shape.SetPen(pen) + + self.text = kwargs['text'] + + evthandler = MyEvtHandler(self.evt_callback) + evthandler.SetShape(shape) + evthandler.SetPreviousHandler(shape.GetEventHandler()) + shape.SetEventHandler(evthandler) + shape.SetCentreResize(False) + shape.SetMaintainAspectRatio(False) + + canvas.AddShape( shape ) + + @classmethod + def new(Class, parent): + data = dict(name='some_name', type='T', + x1=5.0, y1=5.0, x2=100.0, y2=10.0, + font="Arial", size=12, + bold=False, italic=False, underline=False, + foreground= 0x000000, background=0xFFFFFF, + align="L", text="", priority=0) + data = CustomDialog.do_input(parent, 'New element', Class.fields, data) + if data: + return Class(canvas=parent.canvas, frame=parent, **data) + + def edit(self): + "Edit current element (show a dialog box with all fields)" + data = self.kwargs.copy() + x1, y1, x2, y2 = self.get_coordinates() + data.update(dict(name=self.name, + text=self.text, + x1=x1, y1=y1, x2=x2, y2=y2, + )) + data = CustomDialog.do_input(self.frame, 'Edit element', self.fields, data) + if data: + self.kwargs.update(data) + self.name = data['name'] + self.text = data['text'] + x,y, w, h = self.set_coordinates(data['x1'], data['y1'], data['x2'], data['y2']) + self.shape.SetX(x) + self.shape.SetY(y) + self.shape.SetWidth(w) + self.shape.SetHeight(h) + self.canvas.Refresh(False) + self.canvas.GetDiagram().ShowAll(1) + + def edit_text(self): + "Allow text edition (i.e. for doubleclick)" + dlg = wx.TextEntryDialog( + self.frame, 'Text for %s' % self.name, + 'Edit Text', '') + if self.text: + dlg.SetValue(self.text) + if dlg.ShowModal() == wx.ID_OK: + self.text = dlg.GetValue().encode("latin1") + dlg.Destroy() + + def copy(self): + "Return an identical duplicate" + kwargs = self.as_dict() + element = Element(canvas=self.canvas, frame=self.frame, zoom=self.zoom, static=self.static, **kwargs) + return element + + def remove(self): + "Erases visual shape from OGL canvas (element must be deleted manually)" + self.canvas.RemoveShape(self.shape) + + def move(self, dx, dy): + "Change pdf coordinates (converting to wx internal values)" + x1, y1, x2, y2 = self.get_coordinates() + x1 += dx + x2 += dx + y1 += dy + y2 += dy + x, y, w, h = self.set_coordinates(x1, y1, x2, y2) + self.shape.SetX(x) + self.shape.SetY(y) + + def evt_callback(self, evt_type=None): + "Event dispatcher" + if evt_type=="LeftDoubleClick": + self.edit_text() + if evt_type=='RightClick': + self.edit() + + # update the status bar + x1, y1, x2, y2 = self.get_coordinates() + self.frame.SetStatusText("%s (%0.2f, %0.2f) - (%0.2f, %0.2f)" % + (self.name, x1, y1, x2, y2)) + + def get_coordinates(self): + "Convert from wx to pdf coordinates" + x, y = self.shape.GetX(), self.shape.GetY() + w, h = self.shape.GetBoundingBoxMax() + w -= 1 + h -= 1 + x1 = x/self.zoom - w/self.zoom/2.0 + x2 = x/self.zoom + w/self.zoom/2.0 + y1 = y/self.zoom - h/self.zoom/2.0 + y2 = y/self.zoom + h/self.zoom/2.0 + return x1, y1, x2, y2 + + def set_coordinates(self, x1, y1, x2, y2): + "Convert from pdf to wx coordinates" + x1 = x1 * self.zoom + x2 = x2 * self.zoom + y1 = y1 * self.zoom + y2 = y2 * self.zoom + + # shapes seems to be centred, pdf coord not + w = max(x1, x2) - min(x1, x2) + 1 + h = max(y1, y2) - min(y1, y2) + 1 + x = (min(x1, x2) + w/2.0) + y = (min(y1, y2) + h/2.0) + return x, y, w, h + + def text(self, txt=None): + if txt is not None: + if not isinstance(txt,str): + txt = str(txt) + self.kwargs['text'] = txt + self.shape.ClearText() + for line in txt.split('\n'): + self.shape.AddText(unicode(line, "latin1")) + self.canvas.Refresh(False) + return self.kwargs['text'] + text = property(text, text) + + def set_x(self, x): + self.shape.SetX(x) + self.canvas.Refresh(False) + self.evt_callback() + def set_y(self, y): + self.shape.SetY(y) + self.canvas.Refresh(False) + self.evt_callback() + def get_x(self): + return self.shape.GetX() + def get_y(self): + return self.shape.GetY() + + x = property(get_x, set_x) + y = property(get_y, set_y) + + def selected(self, sel=None): + if sel is not None: + print "Setting Select(%s)" % sel + self.shape.Select(sel) + return self.shape.Selected() + selected = property(selected, selected) + + def name(self, name=None): + if name is not None: + self.kwargs['name'] = name + return self.kwargs['name'] + name = property(name, name) + + def __contains__(self, k): + "Implement in keyword for searchs" + return k in self.name.lower() or self.text and k in self.text.lower() + + def as_dict(self): + "Return a dictionary representation, used by pyfpdf" + d = self.kwargs + x1, y1, x2, y2 = self.get_coordinates() + d.update({ + 'x1': x1, 'y1': y1, 'x2': x2, 'y2': y2, + 'text': self.text}) + return d + + +class AppFrame(wx.Frame): + "OGL Designer main window" + title = "PyFPDF Template Designer (wx OGL)" + + def __init__(self): + wx.Frame.__init__( self, + None, -1, self.title, + size=(640,480), + style=wx.DEFAULT_FRAME_STYLE ) + sys.excepthook = self.except_hook + self.filename = "" + # Create a toolbar: + tsize = (16,16) + self.toolbar = self.CreateToolBar(wx.TB_HORIZONTAL | wx.NO_BORDER | wx.TB_FLAT) + + artBmp = wx.ArtProvider.GetBitmap + self.toolbar.AddSimpleTool( + wx.ID_NEW, artBmp(wx.ART_NEW, wx.ART_TOOLBAR, tsize), "New") + self.toolbar.AddSimpleTool( + wx.ID_OPEN, artBmp(wx.ART_FILE_OPEN, wx.ART_TOOLBAR, tsize), "Open") + self.toolbar.AddSimpleTool( + wx.ID_SAVE, artBmp(wx.ART_FILE_SAVE, wx.ART_TOOLBAR, tsize), "Save") + self.toolbar.AddSimpleTool( + wx.ID_SAVEAS, artBmp(wx.ART_FILE_SAVE_AS, wx.ART_TOOLBAR, tsize), + "Save As...") + #------- + self.toolbar.AddSeparator() + self.toolbar.AddSimpleTool( + wx.ID_UNDO, artBmp(wx.ART_UNDO, wx.ART_TOOLBAR, tsize), "Undo") + self.toolbar.AddSimpleTool( + wx.ID_REDO, artBmp(wx.ART_REDO, wx.ART_TOOLBAR, tsize), "Redo") + self.toolbar.AddSeparator() + #------- + self.toolbar.AddSimpleTool( + wx.ID_CUT, artBmp(wx.ART_CUT, wx.ART_TOOLBAR, tsize), "Remove") + self.toolbar.AddSimpleTool( + wx.ID_COPY, artBmp(wx.ART_COPY, wx.ART_TOOLBAR, tsize), "Duplicate") + self.toolbar.AddSimpleTool( + wx.ID_PASTE, artBmp(wx.ART_PASTE, wx.ART_TOOLBAR, tsize), "Insert") + self.toolbar.AddSeparator() + self.toolbar.AddSimpleTool( + wx.ID_FIND, artBmp(wx.ART_FIND, wx.ART_TOOLBAR, tsize), "Find") + self.toolbar.AddSeparator() + self.toolbar.AddSimpleTool( + wx.ID_PRINT, artBmp(wx.ART_PRINT, wx.ART_TOOLBAR, tsize), "Print") + self.toolbar.AddSimpleTool( + wx.ID_ABOUT, artBmp(wx.ART_HELP, wx.ART_TOOLBAR, tsize), "About") + + self.toolbar.Realize() + + self.toolbar.EnableTool(wx.ID_SAVEAS, False) + self.toolbar.EnableTool(wx.ID_UNDO, False) + self.toolbar.EnableTool(wx.ID_REDO, False) + + menu_handlers = [ + (wx.ID_NEW, self.do_new), + (wx.ID_OPEN, self.do_open), + (wx.ID_SAVE, self.do_save), + (wx.ID_PRINT, self.do_print), + (wx.ID_FIND, self.do_find), + (wx.ID_CUT, self.do_cut), + (wx.ID_COPY, self.do_copy), + (wx.ID_PASTE, self.do_paste), + (wx.ID_ABOUT, self.do_about), + ] + for menu_id, handler in menu_handlers: + self.Bind(wx.EVT_MENU, handler, id = menu_id) + + sizer = wx.BoxSizer(wx.VERTICAL) + # put stuff into sizer + + self.CreateStatusBar() + + canvas = self.canvas = ogl.ShapeCanvas( self ) + maxWidth = 1500 + maxHeight = 2000 + canvas.SetScrollbars(20, 20, maxWidth/20, maxHeight/20) + sizer.Add( canvas, 1, wx.GROW ) + + canvas.SetBackgroundColour("WHITE") # + + diagram = self.diagram = ogl.Diagram() + canvas.SetDiagram( diagram ) + diagram.SetCanvas( canvas ) + diagram.SetSnapToGrid( False ) + + # apply sizer + self.SetSizer(sizer) + self.SetAutoLayout(1) + self.Show(1) + + self.Bind(wx.EVT_CHAR_HOOK, self.on_key_event) + self.elements = [] + + def on_key_event(self, event): + """ Respond to a keypress event. + + We make the arrow keys move the selected object(s) by one pixel in + the given direction. + """ + step = 1 + if event.ControlDown(): + step = 20 + + if event.GetKeyCode() == wx.WXK_UP: + self.move_elements(0, -step) + elif event.GetKeyCode() == wx.WXK_DOWN: + self.move_elements(0, step) + elif event.GetKeyCode() == wx.WXK_LEFT: + self.move_elements(-step, 0) + elif event.GetKeyCode() == wx.WXK_RIGHT: + self.move_elements(step, 0) + elif event.GetKeyCode() == wx.WXK_DELETE: + self.do_cut() + else: + event.Skip() + + def do_new(self, evt=None): + for element in self.elements: + element.remove() + self.elements = [] + # draw paper size guides + for k, (w, h) in [('legal', (216, 356)), ('A4', (210, 297)), ('letter', (216, 279))]: + self.create_elements( + k, 'R', 0, 0, w, h, + size=70, foreground=0x808080, priority=-100, + canvas=self.canvas, frame=self, static=True) + self.diagram.ShowAll( 1 ) + + def do_open(self, evt): + dlg = wx.FileDialog( + self, message="Choose a file", + defaultDir=os.getcwd(), + defaultFile="invoice.csv", + wildcard="CSV Files (*.csv)|*.csv", + style=wx.OPEN + ) + + if dlg.ShowModal() == wx.ID_OK: + # This returns a Python list of files that were selected. + self.filename = dlg.GetPaths()[0] + + dlg.Destroy() + self.SetTitle(self.filename + " - " + self.title) + + self.do_new() + tmp = [] + f = open(self.filename) + try: + filedata = f.readlines() + finally: + f.close() + for lno, linea in enumerate(filedata): + if DEBUG: print "processing line", lno, linea + args = [] + for i,v in enumerate(linea.split(";")): + if not v.startswith("'"): + v = v.replace(",",".") + else: + v = v#.decode('latin1') + if v.strip()=='': + v = None + else: + v = eval(v.strip()) + args.append(v) + tmp.append(args) + + # sort by z-order (priority) + for args in sorted(tmp, key=lambda t: t[-1]): + if DEBUG: print args + self.create_elements(*args) + self.diagram.ShowAll( 1 ) # + + return True + + def do_save(self, evt, filename=None): + try: + from time import gmtime, strftime + ts = strftime("%Y%m%d%H%M%S", gmtime()) + os.rename(self.filename, self.filename + ts + ".bak") + except Exception, e: + if DEBUG: print e + pass + + def csv_repr(v, decimal_sep="."): + if isinstance(v, float): + return ("%0.2f" % v).replace(".", decimal_sep) + else: + return repr(v) + + f = open(self.filename, "w") + try: + for element in sorted(self.elements, key=lambda e:e.name): + if element.static: + continue + d = element.as_dict() + l = [d['name'], d['type'], + d['x1'], d['y1'], d['x2'], d['y2'], + d['font'], d['size'], + d['bold'], d['italic'], d['underline'], + d['foreground'], d['background'], + d['align'], d['text'], d['priority'], + ] + f.write(";".join([csv_repr(v) for v in l])) + f.write("\n") + finally: + f.close() + + def do_print(self, evt): + # genero el renderizador con propiedades del PDF + from template import Template + t = Template(elements=[e.as_dict() for e in self.elements if not e.static]) + t.add_page() + if not t['logo'] or not os.path.exists(t['logo']): + # put a default logo so it doesn't trow an exception + logo = os.path.join(os.path.dirname(__file__), 'tutorial','logo.png') + t.set('logo', logo) + try: + t.render(self.filename +".pdf") + except: + if DEBUG and False: + import pdb; + pdb.pm() + else: + raise + if sys.platform=="linux2": + os.system("evince ""%s""" % self.filename +".pdf") + else: + os.startfile(self.filename +".pdf") + + def do_find(self, evt): + # busco nombre o texto + dlg = wx.TextEntryDialog( + self, 'Enter text to search for', + 'Find Text', '') + if dlg.ShowModal() == wx.ID_OK: + txt = dlg.GetValue().encode("latin1").lower() + for element in self.elements: + if txt in element: + element.selected = True + print "Found:", element.name + self.canvas.Refresh(False) + dlg.Destroy() + + def do_cut(self, evt=None): + "Delete selected elements" + new_elements = [] + for element in self.elements: + if element.selected: + print "Erasing:", element.name + element.selected = False + self.canvas.Refresh(False) + element.remove() + else: + new_elements.append(element) + self.elements = new_elements + self.canvas.Refresh(False) + self.diagram.ShowAll( 1 ) + + def do_copy(self, evt): + "Duplicate selected elements" + fields = ['qty', 'dx', 'dy'] + data = {'qty': 1, 'dx': 0.0, 'dy': 5.0} + data = CustomDialog.do_input(self, 'Copy elements', fields, data) + if data: + new_elements = [] + for i in range(1, data['qty']+1): + for element in self.elements: + if element.selected: + print "Copying:", element.name + new_element = element.copy() + name = new_element.name + if len(name)>2 and name[-2:].isdigit(): + new_element.name = name[:-2] + "%02d" % (int(name[-2:])+i) + else: + new_element.name = new_element.name + "_copy" + new_element.selected = False + new_element.move(data['dx']*i, data['dy']*i) + new_elements.append(new_element) + self.elements.extend(new_elements) + self.canvas.Refresh(False) + self.diagram.ShowAll( 1 ) + + def do_paste(self, evt): + "Insert new elements" + element = Element.new(self) + if element: + self.canvas.Refresh(False) + self.elements.append(element) + self.diagram.ShowAll( 1 ) + + def create_elements(self, name, type, x1, y1, x2, y2, + font="Arial", size=12, + bold=False, italic=False, underline=False, + foreground= 0x000000, background=0xFFFFFF, + align="L", text="", priority=0, canvas=None, frame=None, static=False, + **kwargs): + element = Element(name=name, type=type, x1=x1, y1=y1, x2=x2, y2=y2, + font=font, size=size, + bold=bold, italic=italic, underline=underline, + foreground= foreground, background=background, + align=align, text=text, priority=priority, + canvas=canvas or self.canvas, frame=frame or self, + static=static) + self.elements.append(element) + + def move_elements(self, x, y): + for element in self.elements: + if element.selected: + print "moving", element.name, x, y + element.x = element.x + x + element.y = element.y + y + + def do_about(self, evt): + info = wx.AboutDialogInfo() + info.Name = self.title + info.Version = __version__ + info.Copyright = __copyright__ + info.Description = ( + "Visual Template designer for PyFPDF (using wxPython OGL library)\n" + "Input files are CSV format describing the layout, separated by ;\n" + "Use toolbar buttons to open, save, print (preview) your template, " + "and there are buttons to find, add, remove or duplicate elements.\n" + "Over an element, a double left click opens edit text dialog, " + "and a right click opens edit properties dialog. \n" + "Multiple element can be selected with shift left click. \n" + "Use arrow keys or drag-and-drop to move elements.\n" + "For further information see project webpage:" + ) + info.WebSite = ("http://code.google.com/p/pyfpdf/wiki/Templates", + "pyfpdf Google Code Project") + info.Developers = [ __author__, ] + + info.License = wordwrap(__license__, 500, wx.ClientDC(self)) + + # Then we call wx.AboutBox giving it that info object + wx.AboutBox(info) + + def except_hook(self, type, value, trace): + import traceback + exc = traceback.format_exception(type, value, trace) + for e in exc: wx.LogError(e) + wx.LogError('Unhandled Error: %s: %s'%(str(type), str(value))) + + +app = wx.PySimpleApp() +ogl.OGLInitialize() +frame = AppFrame() +app.MainLoop() +app.Destroy() + + ADDED gluon/contrib/pyfpdf/fpdf.py Index: gluon/contrib/pyfpdf/fpdf.py ================================================================== --- gluon/contrib/pyfpdf/fpdf.py +++ gluon/contrib/pyfpdf/fpdf.py @@ -0,0 +1,1686 @@ +#!/usr/bin/env python +# -*- coding: latin-1 -*- +# ****************************************************************************** +# * Software: FPDF for python * +# * Version: 1.54c * +# * Date: 2010-09-10 * +# * License: LGPL v3.0 * +# * * +# * Original Author (PHP): Olivier PLATHEY 2004-12-31 * +# * Ported to Python 2.4 by Max (maxpat78@yahoo.it) on 2006-05 * +# * Maintainer: Mariano Reingart (reingart@gmail.com) et al since 2008 (est.) * +# * NOTE: 'I' and 'D' destinations are disabled, and simply print to STDOUT * +# *****************************************************************************/ + +from datetime import datetime +import math +import os, sys, zlib, struct + +try: + # Check if PIL is available, necessary for JPEG support. + import Image +except ImportError: + Image = None + +def substr(s, start, length=-1): + if length < 0: + length=len(s)-start + return s[start:start+length] + +def sprintf(fmt, *args): return fmt % args + +# Global variables +FPDF_VERSION='1.54b' +FPDF_FONT_DIR=os.path.join(os.path.dirname(__file__),'font') +fpdf_charwidths = {} + +class FPDF: +#Private properties +#~ page; #current page number +#~ n; #current object number +#~ offsets; #array of object offsets +#~ buffer; #buffer holding in-memory PDF +#~ pages; #array containing pages +#~ state; #current document state +#~ compress; #compression flag +#~ def_orientation; #default orientation +#~ cur_orientation; #current orientation +#~ orientation_changes; #array indicating orientation changes +#~ k; #scale factor (number of points in user unit) +#~ fw_pt,fh_pt; #dimensions of page format in points +#~ fw,fh; #dimensions of page format in user unit +#~ w_pt,h_pt; #current dimensions of page in points +#~ w,h; #current dimensions of page in user unit +#~ l_margin; #left margin +#~ t_margin; #top margin +#~ r_margin; #right margin +#~ b_margin; #page break margin +#~ c_margin; #cell margin +#~ x,y; #current position in user unit for cell positioning +#~ lasth; #height of last cell printed +#~ line_width; #line width in user unit +#~ core_fonts; #array of standard font names +#~ fonts; #array of used fonts +#~ font_files; #array of font files +#~ diffs; #array of encoding differences +#~ images; #array of used images +#~ page_links; #array of links in pages +#~ links; #array of internal links +#~ font_family; #current font family +#~ font_style; #current font style +#~ underline; #underlining flag +#~ current_font; #current font info +#~ font_size_pt; #current font size in points +#~ font_size; #current font size in user unit +#~ draw_color; #commands for drawing color +#~ fill_color; #commands for filling color +#~ text_color; #commands for text color +#~ color_flag; #indicates whether fill and text colors are different +#~ ws; #word spacing +#~ auto_page_break; #automatic page breaking +#~ page_break_trigger; #threshold used to trigger page breaks +#~ in_footer; #flag set when processing footer +#~ zoom_mode; #zoom display mode +#~ layout_mode; #layout display mode +#~ title; #title +#~ subject; #subject +#~ author; #author +#~ keywords; #keywords +#~ creator; #creator +#~ alias_nb_pages; #alias for total number of pages +#~ pdf_version; #PDF version number + +# ****************************************************************************** +# * * +# * Public methods * +# * * +# *******************************************************************************/ + def __init__(self, orientation='P',unit='mm',format='A4'): + #Some checks + self._dochecks() + #Initialization of properties + self.offsets={} + self.page=0 + self.n=2 + self.buffer='' + self.pages={} + self.orientation_changes={} + self.state=0 + self.fonts={} + self.font_files={} + self.diffs={} + self.images={} + self.page_links={} + self.links={} + self.in_footer=0 + self.lastw=0 + self.lasth=0 + self.font_family='' + self.font_style='' + self.font_size_pt=12 + self.underline=0 + self.draw_color='0 G' + self.fill_color='0 g' + self.text_color='0 g' + self.color_flag=0 + self.ws=0 + self.angle=0 + #Standard fonts + self.core_fonts={'courier':'Courier','courierB':'Courier-Bold','courierI':'Courier-Oblique','courierBI':'Courier-BoldOblique', + 'helvetica':'Helvetica','helveticaB':'Helvetica-Bold','helveticaI':'Helvetica-Oblique','helveticaBI':'Helvetica-BoldOblique', + 'times':'Times-Roman','timesB':'Times-Bold','timesI':'Times-Italic','timesBI':'Times-BoldItalic', + 'symbol':'Symbol','zapfdingbats':'ZapfDingbats'} + #Scale factor + if(unit=='pt'): + self.k=1 + elif(unit=='mm'): + self.k=72/25.4 + elif(unit=='cm'): + self.k=72/2.54 + elif(unit=='in'): + self.k=72 + else: + self.error('Incorrect unit: '+unit) + #Page format + if(isinstance(format,basestring)): + format=format.lower() + if(format=='a3'): + format=(841.89,1190.55) + elif(format=='a4'): + format=(595.28,841.89) + elif(format=='a5'): + format=(420.94,595.28) + elif(format=='letter'): + format=(612,792) + elif(format=='legal'): + format=(612,1008) + else: + self.error('Unknown page format: '+format) + self.fw_pt=format[0] + self.fh_pt=format[1] + else: + self.fw_pt=format[0]*self.k + self.fh_pt=format[1]*self.k + self.fw=self.fw_pt/self.k + self.fh=self.fh_pt/self.k + #Page orientation + orientation=orientation.lower() + if(orientation=='p' or orientation=='portrait'): + self.def_orientation='P' + self.w_pt=self.fw_pt + self.h_pt=self.fh_pt + elif(orientation=='l' or orientation=='landscape'): + self.def_orientation='L' + self.w_pt=self.fh_pt + self.h_pt=self.fw_pt + else: + self.error('Incorrect orientation: '+orientation) + self.cur_orientation=self.def_orientation + self.w=self.w_pt/self.k + self.h=self.h_pt/self.k + #Page margins (1 cm) + margin=28.35/self.k + self.set_margins(margin,margin) + #Interior cell margin (1 mm) + self.c_margin=margin/10.0 + #line width (0.2 mm) + self.line_width=.567/self.k + #Automatic page break + self.set_auto_page_break(1,2*margin) + #Full width display mode + self.set_display_mode('fullwidth') + #Enable compression + self.set_compression(1) + #Set default PDF version number + self.pdf_version='1.3' + + def set_margins(self, left,top,right=-1): + "Set left, top and right margins" + self.l_margin=left + self.t_margin=top + if(right==-1): + right=left + self.r_margin=right + + def set_left_margin(self, margin): + "Set left margin" + self.l_margin=margin + if(self.page>0 and self.x<margin): + self.x=margin + + def set_top_margin(self, margin): + "Set top margin" + self.t_margin=margin + + def set_right_margin(self, margin): + "Set right margin" + self.r_margin=margin + + def set_auto_page_break(self, auto,margin=0): + "Set auto page break mode and triggering margin" + self.auto_page_break=auto + self.b_margin=margin + self.page_break_trigger=self.h-margin + + def set_display_mode(self, zoom,layout='continuous'): + "Set display mode in viewer" + if(zoom=='fullpage' or zoom=='fullwidth' or zoom=='real' or zoom=='default' or not isinstance(zoom,basestring)): + self.zoom_mode=zoom + else: + self.error('Incorrect zoom display mode: '+zoom) + if(layout=='single' or layout=='continuous' or layout=='two' or layout=='default'): + self.layout_mode=layout + else: + self.error('Incorrect layout display mode: '+layout) + + def set_compression(self, compress): + "Set page compression" + self.compress=compress + + def set_title(self, title): + "Title of document" + self.title=title + + def set_subject(self, subject): + "Subject of document" + self.subject=subject + + def set_author(self, author): + "Author of document" + self.author=author + + def set_keywords(self, keywords): + "Keywords of document" + self.keywords=keywords + + def set_creator(self, creator): + "Creator of document" + self.creator=creator + + def alias_nb_pages(self, alias='{nb}'): + "Define an alias for total number of pages" + self.str_alias_nb_pages=alias + return alias + + def error(self, msg): + "Fatal error" + raise RuntimeError('FPDF error: '+msg) + + def open(self): + "Begin document" + self.state=1 + + def close(self): + "Terminate document" + if(self.state==3): + return + if(self.page==0): + self.add_page() + #Page footer + self.in_footer=1 + self.footer() + self.in_footer=0 + #close page + self._endpage() + #close document + self._enddoc() + + def add_page(self, orientation=''): + "Start a new page" + if(self.state==0): + self.open() + family=self.font_family + if self.underline: + style = self.font_style + 'U' + else: + style = self.font_style + size=self.font_size_pt + lw=self.line_width + dc=self.draw_color + fc=self.fill_color + tc=self.text_color + cf=self.color_flag + if(self.page>0): + #Page footer + self.in_footer=1 + self.footer() + self.in_footer=0 + #close page + self._endpage() + #Start new page + self._beginpage(orientation) + #Set line cap style to square + self._out('2 J') + #Set line width + self.line_width=lw + self._out(sprintf('%.2f w',lw*self.k)) + #Set font + if(family): + self.set_font(family,style,size) + #Set colors + self.draw_color=dc + if(dc!='0 G'): + self._out(dc) + self.fill_color=fc + if(fc!='0 g'): + self._out(fc) + self.text_color=tc + self.color_flag=cf + #Page header + self.header() + #Restore line width + if(self.line_width!=lw): + self.line_width=lw + self._out(sprintf('%.2f w',lw*self.k)) + #Restore font + if(family): + self.set_font(family,style,size) + #Restore colors + if(self.draw_color!=dc): + self.draw_color=dc + self._out(dc) + if(self.fill_color!=fc): + self.fill_color=fc + self._out(fc) + self.text_color=tc + self.color_flag=cf + + def header(self): + "Header to be implemented in your own inherited class" + pass + + def footer(self): + "Footer to be implemented in your own inherited class" + pass + + def page_no(self): + "Get current page number" + return self.page + + def set_draw_color(self, r,g=-1,b=-1): + "Set color for all stroking operations" + if((r==0 and g==0 and b==0) or g==-1): + self.draw_color=sprintf('%.3f G',r/255.0) + else: + self.draw_color=sprintf('%.3f %.3f %.3f RG',r/255.0,g/255.0,b/255.0) + if(self.page>0): + self._out(self.draw_color) + + def set_fill_color(self,r,g=-1,b=-1): + "Set color for all filling operations" + if((r==0 and g==0 and b==0) or g==-1): + self.fill_color=sprintf('%.3f g',r/255.0) + else: + self.fill_color=sprintf('%.3f %.3f %.3f rg',r/255.0,g/255.0,b/255.0) + self.color_flag=(self.fill_color!=self.text_color) + if(self.page>0): + self._out(self.fill_color) + + def set_text_color(self, r,g=-1,b=-1): + "Set color for text" + if((r==0 and g==0 and b==0) or g==-1): + self.text_color=sprintf('%.3f g',r/255.0) + else: + self.text_color=sprintf('%.3f %.3f %.3f rg',r/255.0,g/255.0,b/255.0) + self.color_flag=(self.fill_color!=self.text_color) + + def get_string_width(self, s): + "Get width of a string in the current font" + cw=self.current_font['cw'] + w=0 + l=len(s) + for i in xrange(0, l): + w += cw.get(s[i],0) + return w*self.font_size/1000.0 + + def set_line_width(self, width): + "Set line width" + self.line_width=width + if(self.page>0): + self._out(sprintf('%.2f w',width*self.k)) + + def line(self, x1,y1,x2,y2): + "Draw a line" + self._out(sprintf('%.2f %.2f m %.2f %.2f l S',x1*self.k,(self.h-y1)*self.k,x2*self.k,(self.h-y2)*self.k)) + + def rect(self, x,y,w,h,style=''): + "Draw a rectangle" + if(style=='F'): + op='f' + elif(style=='FD' or style=='DF'): + op='B' + else: + op='S' + self._out(sprintf('%.2f %.2f %.2f %.2f re %s',x*self.k,(self.h-y)*self.k,w*self.k,-h*self.k,op)) + + def add_font(self, family,style='',fname=''): + "Add a TrueType or Type1 font" + family=family.lower() + if(fname==''): + fname=family.replace(' ','')+style.lower()+'.font' + fname=os.path.join(FPDF_FONT_DIR,fname) + if(family=='arial'): + family='helvetica' + style=style.upper() + if(style=='IB'): + style='BI' + fontkey=family+style + if fontkey in self.fonts: + self.error('Font already added: '+family+' '+style) + execfile(fname, globals(), globals()) + if 'name' not in globals(): + self.error('Could not include font definition file') + i=len(self.fonts)+1 + self.fonts[fontkey]={'i':i,'type':type,'name':name,'desc':desc,'up':up,'ut':ut,'cw':cw,'enc':enc,'file':filename} + if(diff): + #Search existing encodings + d=0 + nb=len(self.diffs) + for i in xrange(1,nb+1): + if(self.diffs[i]==diff): + d=i + break + if(d==0): + d=nb+1 + self.diffs[d]=diff + self.fonts[fontkey]['diff']=d + if(filename): + if(type=='TrueType'): + self.font_files[filename]={'length1':originalsize} + else: + self.font_files[filename]={'length1':size1,'length2':size2} + + def set_font(self, family,style='',size=0): + "Select a font; size given in points" + family=family.lower() + if(family==''): + family=self.font_family + if(family=='arial'): + family='helvetica' + elif(family=='symbol' or family=='zapfdingbats'): + style='' + style=style.upper() + if('U' in style): + self.underline=1 + style=style.replace('U','') + else: + self.underline=0 + if(style=='IB'): + style='BI' + if(size==0): + size=self.font_size_pt + #Test if font is already selected + if(self.font_family==family and self.font_style==style and self.font_size_pt==size): + return + #Test if used for the first time + fontkey=family+style + if fontkey not in self.fonts: + #Check if one of the standard fonts + if fontkey in self.core_fonts: + if fontkey not in fpdf_charwidths: + #Load metric file + name=os.path.join(FPDF_FONT_DIR,family) + if(family=='times' or family=='helvetica'): + name+=style.lower() + execfile(name+'.font') + if fontkey not in fpdf_charwidths: + self.error('Could not include font metric file for'+fontkey) + i=len(self.fonts)+1 + self.fonts[fontkey]={'i':i,'type':'core','name':self.core_fonts[fontkey],'up':-100,'ut':50,'cw':fpdf_charwidths[fontkey]} + else: + self.error('Undefined font: '+family+' '+style) + #Select it + self.font_family=family + self.font_style=style + self.font_size_pt=size + self.font_size=size/self.k + self.current_font=self.fonts[fontkey] + if(self.page>0): + self._out(sprintf('BT /F%d %.2f Tf ET',self.current_font['i'],self.font_size_pt)) + + def set_font_size(self, size): + "Set font size in points" + if(self.font_size_pt==size): + return + self.font_size_pt=size + self.font_size=size/self.k + if(self.page>0): + self._out(sprintf('BT /F%d %.2f Tf ET',self.current_font['i'],self.font_size_pt)) + + def add_link(self): + "Create a new internal link" + n=len(self.links)+1 + self.links[n]=(0,0) + return n + + def set_link(self, link,y=0,page=-1): + "Set destination of internal link" + if(y==-1): + y=self.y + if(page==-1): + page=self.page + self.links[link]=[page,y] + + def link(self, x,y,w,h,link): + "Put a link on the page" + if not self.page in self.page_links: + self.page_links[self.page] = [] + self.page_links[self.page] += [(x*self.k,self.h_pt-y*self.k,w*self.k,h*self.k,link),] + + def text(self, x,y,txt): + "Output a string" + s=sprintf('BT %.2f %.2f Td (%s) Tj ET',x*self.k,(self.h-y)*self.k,self._escape(txt)) + if(self.underline and txt!=''): + s+=' '+self._dounderline(x,y,txt) + if(self.color_flag): + s='q '+self.text_color+' '+s+' Q' + self._out(s) + + def rotate(self, angle, x=None, y=None): + if x is None: + x = self.x + if y is None: + y = self.y; + if self.angle!=0: + self._out('Q') + self.angle = angle + if angle!=0: + angle *= math.pi/180; + c = math.cos(angle); + s = math.sin(angle); + cx = x*self.k; + cy = (self.h-y)*self.k + s = sprintf('q %.5F %.5F %.5F %.5F %.2F %.2F cm 1 0 0 1 %.2F %.2F cm',c,s,-s,c,cx,cy,-cx,-cy) + self._out(s) + + def accept_page_break(self): + "Accept automatic page break or not" + return self.auto_page_break + + def cell(self, w,h=0,txt='',border=0,ln=0,align='',fill=0,link=''): + "Output a cell" + k=self.k + if(self.y+h>self.page_break_trigger and not self.in_footer and self.accept_page_break()): + #Automatic page break + x=self.x + ws=self.ws + if(ws>0): + self.ws=0 + self._out('0 Tw') + self.add_page(self.cur_orientation) + self.x=x + if(ws>0): + self.ws=ws + self._out(sprintf('%.3f Tw',ws*k)) + if(w==0): + w=self.w-self.r_margin-self.x + s='' + if(fill==1 or border==1): + if(fill==1): + if border==1: + op='B' + else: + op='f' + else: + op='S' + s=sprintf('%.2f %.2f %.2f %.2f re %s ',self.x*k,(self.h-self.y)*k,w*k,-h*k,op) + if(isinstance(border,basestring)): + x=self.x + y=self.y + if('L' in border): + s+=sprintf('%.2f %.2f m %.2f %.2f l S ',x*k,(self.h-y)*k,x*k,(self.h-(y+h))*k) + if('T' in border): + s+=sprintf('%.2f %.2f m %.2f %.2f l S ',x*k,(self.h-y)*k,(x+w)*k,(self.h-y)*k) + if('R' in border): + s+=sprintf('%.2f %.2f m %.2f %.2f l S ',(x+w)*k,(self.h-y)*k,(x+w)*k,(self.h-(y+h))*k) + if('B' in border): + s+=sprintf('%.2f %.2f m %.2f %.2f l S ',x*k,(self.h-(y+h))*k,(x+w)*k,(self.h-(y+h))*k) + if(txt!=''): + if(align=='R'): + dx=w-self.c_margin-self.get_string_width(txt) + elif(align=='C'): + dx=(w-self.get_string_width(txt))/2.0 + else: + dx=self.c_margin + if(self.color_flag): + s+='q '+self.text_color+' ' + txt2=txt.replace('\\','\\\\').replace(')','\\)').replace('(','\\(') + s+=sprintf('BT %.2f %.2f Td (%s) Tj ET',(self.x+dx)*k,(self.h-(self.y+.5*h+.3*self.font_size))*k,txt2) + if(self.underline): + s+=' '+self._dounderline(self.x+dx,self.y+.5*h+.3*self.font_size,txt) + if(self.color_flag): + s+=' Q' + if(link): + self.link(self.x+dx,self.y+.5*h-.5*self.font_size,self.get_string_width(txt),self.font_size,link) + if(s): + self._out(s) + self.lasth=h + if(ln>0): + #Go to next line + self.y+=h + if(ln==1): + self.x=self.l_margin + else: + self.x+=w + + def multi_cell(self, w,h,txt,border=0,align='J',fill=0, split_only=False): + "Output text with automatic or explicit line breaks" + ret = [] # if split_only = True, returns splited text cells + cw=self.current_font['cw'] + if(w==0): + w=self.w-self.r_margin-self.x + wmax=(w-2*self.c_margin)*1000.0/self.font_size + s=txt.replace("\r",'') + nb=len(s) + if(nb>0 and s[nb-1]=="\n"): + nb-=1 + b=0 + if(border): + if(border==1): + border='LTRB' + b='LRT' + b2='LR' + else: + b2='' + if('L' in border): + b2+='L' + if('R' in border): + b2+='R' + if ('T' in border): + b=b2+'T' + else: + b=b2 + sep=-1 + i=0 + j=0 + l=0 + ns=0 + nl=1 + while(i<nb): + #Get next character + c=s[i] + if(c=="\n"): + #Explicit line break + if(self.ws>0): + self.ws=0 + self._out('0 Tw') + if not split_only: + self.cell(w,h,substr(s,j,i-j),b,2,align,fill) + else: + ret.append(substr(s,j,i-j)) + i+=1 + sep=-1 + j=i + l=0 + ns=0 + nl+=1 + if(border and nl==2): + b=b2 + continue + if(c==' '): + sep=i + ls=l + ns+=1 + l+=cw.get(c,0) + if(l>wmax): + #Automatic line break + if(sep==-1): + if(i==j): + i+=1 + if(self.ws>0): + self.ws=0 + self._out('0 Tw') + if not split_only: + self.cell(w,h,substr(s,j,i-j),b,2,align,fill) + else: + ret.append(substr(s,j,i-j)) + else: + if(align=='J'): + if ns>1: + self.ws=(wmax-ls)/1000.0*self.font_size/(ns-1) + else: + self.ws=0 + self._out(sprintf('%.3f Tw',self.ws*self.k)) + if not split_only: + self.cell(w,h,substr(s,j,sep-j),b,2,align,fill) + else: + ret.append(substr(s,j,sep-j)) + i=sep+1 + sep=-1 + j=i + l=0 + ns=0 + nl+=1 + if(border and nl==2): + b=b2 + else: + i+=1 + #Last chunk + if(self.ws>0): + self.ws=0 + self._out('0 Tw') + if(border and 'B' in border): + b+='B' + if not split_only: + self.cell(w,h,substr(s,j,i-j),b,2,align,fill) + else: + ret.append(substr(s,j,i-j)) + self.x=self.l_margin + return ret + + def write(self, h,txt,link=''): + "Output text in flowing mode" + cw=self.current_font['cw'] + w=self.w-self.r_margin-self.x + wmax=(w-2*self.c_margin)*1000.0/self.font_size + s=txt.replace("\r",'') + nb=len(s) + sep=-1 + i=0 + j=0 + l=0 + nl=1 + while(i<nb): + #Get next character + c=s[i] + if(c=="\n"): + #Explicit line break + self.cell(w,h,substr(s,j,i-j),0,2,'',0,link) + i+=1 + sep=-1 + j=i + l=0 + if(nl==1): + self.x=self.l_margin + w=self.w-self.r_margin-self.x + wmax=(w-2*self.c_margin)*1000.0/self.font_size + nl+=1 + continue + if(c==' '): + sep=i + l+=cw.get(c,0) + if(l>wmax): + #Automatic line break + if(sep==-1): + if(self.x>self.l_margin): + #Move to next line + self.x=self.l_margin + self.y+=h + w=self.w-self.r_margin-self.x + wmax=(w-2*self.c_margin)*1000.0/self.font_size + i+=1 + nl+=1 + continue + if(i==j): + i+=1 + self.cell(w,h,substr(s,j,i-j),0,2,'',0,link) + else: + self.cell(w,h,substr(s,j,sep-j),0,2,'',0,link) + i=sep+1 + sep=-1 + j=i + l=0 + if(nl==1): + self.x=self.l_margin + w=self.w-self.r_margin-self.x + wmax=(w-2*self.c_margin)*1000.0/self.font_size + nl+=1 + else: + i+=1 + #Last chunk + if(i!=j): + self.cell(l/1000.0*self.font_size,h,substr(s,j),0,0,'',0,link) + + def image(self, name,x,y,w=0,h=0,type='',link=''): + "Put an image on the page" + if not name in self.images: + #First use of image, get info + if(type==''): + pos=name.rfind('.') + if(not pos): + self.error('image file has no extension and no type was specified: '+name) + type=substr(name,pos+1) + type=type.lower() + if(type=='jpg' or type=='jpeg'): + info=self._parsejpg(name) + elif(type=='png'): + info=self._parsepng(name) + else: + #Allow for additional formats + mtd='_parse'+type + if not hasattr(self,mtd): + self.error('Unsupported image type: '+type) + info=self.mtd(name) + info['i']=len(self.images)+1 + self.images[name]=info + else: + info=self.images[name] + #Automatic width and height calculation if needed + if(w==0 and h==0): + #Put image at 72 dpi + w=info['w']/self.k + h=info['h']/self.k + if(w==0): + w=h*info['w']/info['h'] + if(h==0): + h=w*info['h']/info['w'] + self._out(sprintf('q %.2f 0 0 %.2f %.2f %.2f cm /I%d Do Q',w*self.k,h*self.k,x*self.k,(self.h-(y+h))*self.k,info['i'])) + if(link): + self.link(x,y,w,h,link) + + def ln(self, h=''): + "Line Feed; default value is last cell height" + self.x=self.l_margin + if(isinstance(h, basestring)): + self.y+=self.lasth + else: + self.y+=h + + def get_x(self): + "Get x position" + return self.x + + def set_x(self, x): + "Set x position" + if(x>=0): + self.x=x + else: + self.x=self.w+x + + def get_y(self): + "Get y position" + return self.y + + def set_y(self, y): + "Set y position and reset x" + self.x=self.l_margin + if(y>=0): + self.y=y + else: + self.y=self.h+y + + def set_xy(self, x,y): + "Set x and y positions" + self.set_y(y) + self.set_x(x) + + def output(self, name='',dest=''): + "Output PDF to some destination" + #Finish document if necessary + if(self.state<3): + self.close() + #Normalize parameters + # if(type(dest)==type(bool())): + # if dest: + # dest='D' + # else: + # dest='F' + dest=dest.upper() + if(dest==''): + if(name==''): + name='doc.pdf' + dest='I' + else: + dest='F' + if dest=='I': + print self.buffer + elif dest=='D': + print self.buffer + elif dest=='F': + #Save to local file + f=file(name,'wb') + if(not f): + self.error('Unable to create output file: '+name) + f.write(self.buffer) + f.close() + elif dest=='S': + #Return as a string + return self.buffer + else: + self.error('Incorrect output destination: '+dest) + return '' + +# ****************************************************************************** +# * * +# * Protected methods * +# * * +# *******************************************************************************/ + def _dochecks(self): + #Check for locale-related bug +# if(1.1==1): +# self.error("Don\'t alter the locale before including class file"); + #Check for decimal separator + if(sprintf('%.1f',1.0)!='1.0'): + import locale + locale.setlocale(locale.LC_NUMERIC,'C') + + def _getfontpath(self): + return FPDF_FONT_DIR+'/' + + def _putpages(self): + nb=self.page + if hasattr(self,'str_alias_nb_pages'): + #Replace number of pages + for n in xrange(1,nb+1): + self.pages[n]=self.pages[n].replace(self.str_alias_nb_pages,str(nb)) + if(self.def_orientation=='P'): + w_pt=self.fw_pt + h_pt=self.fh_pt + else: + w_pt=self.fh_pt + h_pt=self.fw_pt + if self.compress: + filter='/Filter /FlateDecode ' + else: + filter='' + for n in xrange(1,nb+1): + #Page + self._newobj() + self._out('<</Type /Page') + self._out('/Parent 1 0 R') + if n in self.orientation_changes: + self._out(sprintf('/MediaBox [0 0 %.2f %.2f]',h_pt,w_pt)) + self._out('/Resources 2 0 R') + if self.page_links and n in self.page_links: + #Links + annots='/Annots [' + for pl in self.page_links[n]: + rect=sprintf('%.2f %.2f %.2f %.2f',pl[0],pl[1],pl[0]+pl[2],pl[1]-pl[3]) + annots+='<</Type /Annot /Subtype /Link /Rect ['+rect+'] /Border [0 0 0] ' + if(isinstance(pl[4],basestring)): + annots+='/A <</S /URI /URI '+self._textstring(pl[4])+'>>>>' + else: + l=self.links[pl[4]] + if l[0] in self.orientation_changes: + h=w_pt + else: + h=h_pt + annots+=sprintf('/Dest [%d 0 R /XYZ 0 %.2f null]>>',1+2*l[0],h-l[1]*self.k) + self._out(annots+']') + self._out('/Contents '+str(self.n+1)+' 0 R>>') + self._out('endobj') + #Page content + if self.compress: + p = zlib.compress(self.pages[n]) + else: + p = self.pages[n] + self._newobj() + self._out('<<'+filter+'/Length '+str(len(p))+'>>') + self._putstream(p) + self._out('endobj') + #Pages root + self.offsets[1]=len(self.buffer) + self._out('1 0 obj') + self._out('<</Type /Pages') + kids='/Kids [' + for i in xrange(0,nb): + kids+=str(3+2*i)+' 0 R ' + self._out(kids+']') + self._out('/Count '+str(nb)) + self._out(sprintf('/MediaBox [0 0 %.2f %.2f]',w_pt,h_pt)) + self._out('>>') + self._out('endobj') + + def _putfonts(self): + nf=self.n + for diff in self.diffs: + #Encodings + self._newobj() + self._out('<</Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences ['+self.diffs[diff]+']>>') + self._out('endobj') + for name,info in self.font_files.iteritems(): + #Font file embedding + self._newobj() + self.font_files[name]['n']=self.n + font='' + f=file(self._getfontpath()+name,'rb',1) + if(not f): + self.error('Font file not found') + font=f.read() + f.close() + compressed=(substr(name,-2)=='.z') + if(not compressed and 'length2' in info): + header=(ord(font[0])==128) + if(header): + #Strip first binary header + font=substr(font,6) + if(header and ord(font[info['length1']])==128): + #Strip second binary header + font=substr(font,0,info['length1'])+substr(font,info['length1']+6) + self._out('<</Length '+str(len(font))) + if(compressed): + self._out('/Filter /FlateDecode') + self._out('/Length1 '+str(info['length1'])) + if('length2' in info): + self._out('/Length2 '+str(info['length2'])+' /Length3 0') + self._out('>>') + self._putstream(font) + self._out('endobj') + for k,font in self.fonts.iteritems(): + #Font objects + self.fonts[k]['n']=self.n+1 + type=font['type'] + name=font['name'] + if(type=='core'): + #Standard font + self._newobj() + self._out('<</Type /Font') + self._out('/BaseFont /'+name) + self._out('/Subtype /Type1') + if(name!='Symbol' and name!='ZapfDingbats'): + self._out('/Encoding /WinAnsiEncoding') + self._out('>>') + self._out('endobj') + elif(type=='Type1' or type=='TrueType'): + #Additional Type1 or TrueType font + self._newobj() + self._out('<</Type /Font') + self._out('/BaseFont /'+name) + self._out('/Subtype /'+type) + self._out('/FirstChar 32 /LastChar 255') + self._out('/Widths '+str(self.n+1)+' 0 R') + self._out('/FontDescriptor '+str(self.n+2)+' 0 R') + if(font['enc']): + if('diff' in font): + self._out('/Encoding '+str(nf+font['diff'])+' 0 R') + else: + self._out('/Encoding /WinAnsiEncoding') + self._out('>>') + self._out('endobj') + #Widths + self._newobj() + cw=font['cw'] + s='[' + for i in xrange(32,256): + # Get doesn't rise exception; returns 0 instead of None if not set + s+=str(cw.get(chr(i)) or 0)+' ' + self._out(s+']') + self._out('endobj') + #Descriptor + self._newobj() + s='<</Type /FontDescriptor /FontName /'+name + for k,v in font['desc'].iteritems(): + s+=' /'+str(k)+' '+str(v) + filename=font['file'] + if(filename): + s+=' /FontFile' + if type!='Type1': + s+='2' + s+=' '+str(self.font_files[filename]['n'])+' 0 R' + self._out(s+'>>') + self._out('endobj') + else: + #Allow for additional types + mtd='_put'+type.lower() + if(not method_exists(self,mtd)): + self.error('Unsupported font type: '+type) + self.mtd(font) + + def _putimages(self): + filter='' + if self.compress: + filter='/Filter /FlateDecode ' + for filename,info in self.images.iteritems(): + self._newobj() + self.images[filename]['n']=self.n + self._out('<</Type /XObject') + self._out('/Subtype /Image') + self._out('/Width '+str(info['w'])) + self._out('/Height '+str(info['h'])) + if(info['cs']=='Indexed'): + self._out('/ColorSpace [/Indexed /DeviceRGB '+str(len(info['pal'])/3-1)+' '+str(self.n+1)+' 0 R]') + else: + self._out('/ColorSpace /'+info['cs']) + if(info['cs']=='DeviceCMYK'): + self._out('/Decode [1 0 1 0 1 0 1 0]') + self._out('/BitsPerComponent '+str(info['bpc'])) + if 'f' in info: + self._out('/Filter /'+info['f']) + if 'parms' in info: + self._out(info['parms']) + if('trns' in info and type([])==info['trns']): + trns='' + for i in xrange(0,len(info['trns'])): + trns+=str(info['trns'][i])+' '+str(info['trns'][i])+' ' + self._out('/Mask ['+trns+']') + self._out('/Length '+str(len(info['data']))+'>>') + self._putstream(info['data']) + self.images[filename]['data'] = None + self._out('endobj') + #Palette + if(info['cs']=='Indexed'): + self._newobj() + if self.compress: + pal=zlib.compress(info['pal']) + else: + pal=info['pal'] + self._out('<<'+filter+'/Length '+str(len(pal))+'>>') + self._putstream(pal) + self._out('endobj') + + def _putxobjectdict(self): + for image in self.images.values(): + self._out('/I'+str(image['i'])+' '+str(image['n'])+' 0 R') + + def _putresourcedict(self): + self._out('/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]') + self._out('/Font <<') + for font in self.fonts.values(): + self._out('/F'+str(font['i'])+' '+str(font['n'])+' 0 R') + self._out('>>') + self._out('/XObject <<') + self._putxobjectdict() + self._out('>>') + + def _putresources(self): + self._putfonts() + self._putimages() + #Resource dictionary + self.offsets[2]=len(self.buffer) + self._out('2 0 obj') + self._out('<<') + self._putresourcedict() + self._out('>>') + self._out('endobj') + + def _putinfo(self): + self._out('/Producer '+self._textstring('PyFPDF '+FPDF_VERSION+' http://pyfpdf.googlecode.com/')) + if hasattr(self,'title'): + self._out('/Title '+self._textstring(self.title)) + if hasattr(self,'subject'): + self._out('/Subject '+self._textstring(self.subject)) + if hasattr(self,'author'): + self._out('/Author '+self._textstring(self.author)) + if hasattr (self,'keywords'): + self._out('/Keywords '+self._textstring(self.keywords)) + if hasattr(self,'creator'): + self._out('/Creator '+self._textstring(self.creator)) + self._out('/CreationDate '+self._textstring('D:'+datetime.now().strftime('%Y%m%d%H%M%S'))) + + def _putcatalog(self): + self._out('/Type /Catalog') + self._out('/Pages 1 0 R') + if(self.zoom_mode=='fullpage'): + self._out('/OpenAction [3 0 R /Fit]') + elif(self.zoom_mode=='fullwidth'): + self._out('/OpenAction [3 0 R /FitH null]') + elif(self.zoom_mode=='real'): + self._out('/OpenAction [3 0 R /XYZ null null 1]') + elif(not isinstance(self.zoom_mode,basestring)): + self._out('/OpenAction [3 0 R /XYZ null null '+(self.zoom_mode/100)+']') + if(self.layout_mode=='single'): + self._out('/PageLayout /SinglePage') + elif(self.layout_mode=='continuous'): + self._out('/PageLayout /OneColumn') + elif(self.layout_mode=='two'): + self._out('/PageLayout /TwoColumnLeft') + + def _putheader(self): + self._out('%PDF-'+self.pdf_version) + + def _puttrailer(self): + self._out('/Size '+str(self.n+1)) + self._out('/Root '+str(self.n)+' 0 R') + self._out('/Info '+str(self.n-1)+' 0 R') + + def _enddoc(self): + self._putheader() + self._putpages() + self._putresources() + #Info + self._newobj() + self._out('<<') + self._putinfo() + self._out('>>') + self._out('endobj') + #Catalog + self._newobj() + self._out('<<') + self._putcatalog() + self._out('>>') + self._out('endobj') + #Cross-ref + o=len(self.buffer) + self._out('xref') + self._out('0 '+(str(self.n+1))) + self._out('0000000000 65535 f ') + for i in xrange(1,self.n+1): + self._out(sprintf('%010d 00000 n ',self.offsets[i])) + #Trailer + self._out('trailer') + self._out('<<') + self._puttrailer() + self._out('>>') + self._out('startxref') + self._out(o) + self._out('%%EOF') + self.state=3 + + def _beginpage(self, orientation): + self.page+=1 + self.pages[self.page]='' + self.state=2 + self.x=self.l_margin + self.y=self.t_margin + self.font_family='' + #Page orientation + if(not orientation): + orientation=self.def_orientation + else: + orientation=orientation[0].upper() + if(orientation!=self.def_orientation): + self.orientation_changes[self.page]=1 + if(orientation!=self.cur_orientation): + #Change orientation + if(orientation=='P'): + self.w_pt=self.fw_pt + self.h_pt=self.fh_pt + self.w=self.fw + self.h=self.fh + else: + self.w_pt=self.fh_pt + self.h_pt=self.fw_pt + self.w=self.fh + self.h=self.fw + self.page_break_trigger=self.h-self.b_margin + self.cur_orientation=orientation + + def _endpage(self): + #End of page contents + self.state=1 + + def _newobj(self): + #Begin a new object + self.n+=1 + self.offsets[self.n]=len(self.buffer) + self._out(str(self.n)+' 0 obj') + + def _dounderline(self, x,y,txt): + #Underline text + up=self.current_font['up'] + ut=self.current_font['ut'] + w=self.get_string_width(txt)+self.ws*txt.count(' ') + return sprintf('%.2f %.2f %.2f %.2f re f',x*self.k,(self.h-(y-up/1000.0*self.font_size))*self.k,w*self.k,-ut/1000.0*self.font_size_pt) + + def _parsejpg(self, filename): + # Extract info from a JPEG file + if Image is None: + self.error('PIL not installed') + try: + f = open(filename, 'rb') + im = Image.open(f) + except Exception, e: + self.error('Missing or incorrect image file: %s. error: %s' % (filename, str(e))) + else: + a = im.size + # We shouldn't get into here, as Jpeg is RGB=8bpp right(?), but, just in case... + bpc=8 + if im.mode == 'RGB': + colspace='DeviceRGB' + elif im.mode == 'CMYK': + colspace='DeviceCMYK' + else: + colspace='DeviceGray' + + # Read whole file from the start + f.seek(0) + data = f.read() + f.close() + return {'w':a[0],'h':a[1],'cs':colspace,'bpc':bpc,'f':'DCTDecode','data':data} + + def _parsepng(self, name): + #Extract info from a PNG file + if name.startswith("http://") or name.startswith("https://"): + import urllib + f = urllib.urlopen(name) + else: + f=open(name,'rb') + if(not f): + self.error("Can't open image file: "+name) + #Check signature + if(f.read(8)!='\x89'+'PNG'+'\r'+'\n'+'\x1a'+'\n'): + self.error('Not a PNG file: '+name) + #Read header chunk + f.read(4) + if(f.read(4)!='IHDR'): + self.error('Incorrect PNG file: '+name) + w=self._freadint(f) + h=self._freadint(f) + bpc=ord(f.read(1)) + if(bpc>8): + self.error('16-bit depth not supported: '+name) + ct=ord(f.read(1)) + if(ct==0): + colspace='DeviceGray' + elif(ct==2): + colspace='DeviceRGB' + elif(ct==3): + colspace='Indexed' + else: + self.error('Alpha channel not supported: '+name) + if(ord(f.read(1))!=0): + self.error('Unknown compression method: '+name) + if(ord(f.read(1))!=0): + self.error('Unknown filter method: '+name) + if(ord(f.read(1))!=0): + self.error('Interlacing not supported: '+name) + f.read(4) + parms='/DecodeParms <</Predictor 15 /Colors ' + if ct==2: + parms+='3' + else: + parms+='1' + parms+=' /BitsPerComponent '+str(bpc)+' /Columns '+str(w)+'>>' + #Scan chunks looking for palette, transparency and image data + pal='' + trns='' + data='' + n=1 + while n != None: + n=self._freadint(f) + type=f.read(4) + if(type=='PLTE'): + #Read palette + pal=f.read(n) + f.read(4) + elif(type=='tRNS'): + #Read transparency info + t=f.read(n) + if(ct==0): + trns=[ord(substr(t,1,1)),] + elif(ct==2): + trns=[ord(substr(t,1,1)),ord(substr(t,3,1)),ord(substr(t,5,1))] + else: + pos=t.find('\x00') + if(pos!=-1): + trns=[pos,] + f.read(4) + elif(type=='IDAT'): + #Read image data block + data+=f.read(n) + f.read(4) + elif(type=='IEND'): + break + else: + f.read(n+4) + if(colspace=='Indexed' and not pal): + self.error('Missing palette in '+name) + f.close() + return {'w':w,'h':h,'cs':colspace,'bpc':bpc,'f':'FlateDecode','parms':parms,'pal':pal,'trns':trns,'data':data} + + def _freadint(self, f): + #Read a 4-byte integer from file + try: + return struct.unpack('>HH',f.read(4))[1] + except: + return None + + def _textstring(self, s): + #Format a text string + return '('+self._escape(s)+')' + + def _escape(self, s): + #Add \ before \, ( and ) + return s.replace('\\','\\\\').replace(')','\\)').replace('(','\\(') + + def _putstream(self, s): + self._out('stream') + self._out(s) + self._out('endstream') + + def _out(self, s): + #Add a line to the document + if(self.state==2): + self.pages[self.page]+=s+"\n" + else: + self.buffer+=str(s)+"\n" + + def interleaved2of5(self, txt, x, y, w=1.0, h=10.0): + "Barcode I2of5 (numeric), adds a 0 if odd lenght" + narrow = w / 3.0 + wide = w + + # wide/narrow codes for the digits + bar_char={} + bar_char['0'] = 'nnwwn' + bar_char['1'] = 'wnnnw' + bar_char['2'] = 'nwnnw' + bar_char['3'] = 'wwnnn' + bar_char['4'] = 'nnwnw' + bar_char['5'] = 'wnwnn' + bar_char['6'] = 'nwwnn' + bar_char['7'] = 'nnnww' + bar_char['8'] = 'wnnwn' + bar_char['9'] = 'nwnwn' + bar_char['A'] = 'nn' + bar_char['Z'] = 'wn' + + self.set_fill_color(0) + code = txt + # add leading zero if code-length is odd + if len(code) % 2 != 0: + code = '0' + code + + # add start and stop codes + code = 'AA' + code.lower() + 'ZA' + + for i in xrange(0, len(code), 2): + # choose next pair of digits + char_bar = code[i]; + char_space = code[i+1]; + # check whether it is a valid digit + if not char_bar in bar_char.keys(): + raise RuntimeError ('Caractr "%s" invlido para el cdigo de barras I25: ' % char_bar) + if not char_space in bar_char.keys(): + raise RuntimeError ('Caractr "%s" invlido para el cdigo de barras I25: ' % char_space) + + # create a wide/narrow-sequence (first digit=bars, second digit=spaces) + seq = '' + for s in xrange(0, len(bar_char[char_bar])): + seq += bar_char[char_bar][s] + bar_char[char_space][s] + + for bar in xrange(0, len(seq)): + # set line_width depending on value + if seq[bar] == 'n': + line_width = narrow + else: + line_width = wide + + # draw every second value, because the second digit of the pair is represented by the spaces + if bar % 2 == 0: + self.rect(x, y, line_width, h, 'F') + + x += line_width + + + def code39(self, txt, x, y, w=1.5, h=5.0): + "Barcode 3of9" + wide = w + narrow = w /3.0 + gap = narrow + + bar_char={} + bar_char['0'] = 'nnnwwnwnn' + bar_char['1'] = 'wnnwnnnnw' + bar_char['2'] = 'nnwwnnnnw' + bar_char['3'] = 'wnwwnnnnn' + bar_char['4'] = 'nnnwwnnnw' + bar_char['5'] = 'wnnwwnnnn' + bar_char['6'] = 'nnwwwnnnn' + bar_char['7'] = 'nnnwnnwnw' + bar_char['8'] = 'wnnwnnwnn' + bar_char['9'] = 'nnwwnnwnn' + bar_char['A'] = 'wnnnnwnnw' + bar_char['B'] = 'nnwnnwnnw' + bar_char['C'] = 'wnwnnwnnn' + bar_char['D'] = 'nnnnwwnnw' + bar_char['E'] = 'wnnnwwnnn' + bar_char['F'] = 'nnwnwwnnn' + bar_char['G'] = 'nnnnnwwnw' + bar_char['H'] = 'wnnnnwwnn' + bar_char['I'] = 'nnwnnwwnn' + bar_char['J'] = 'nnnnwwwnn' + bar_char['K'] = 'wnnnnnnww' + bar_char['L'] = 'nnwnnnnww' + bar_char['M'] = 'wnwnnnnwn' + bar_char['N'] = 'nnnnwnnww' + bar_char['O'] = 'wnnnwnnwn' + bar_char['P'] = 'nnwnwnnwn' + bar_char['Q'] = 'nnnnnnwww' + bar_char['R'] = 'wnnnnnwwn' + bar_char['S'] = 'nnwnnnwwn' + bar_char['T'] = 'nnnnwnwwn' + bar_char['U'] = 'wwnnnnnnw' + bar_char['V'] = 'nwwnnnnnw' + bar_char['W'] = 'wwwnnnnnn' + bar_char['X'] = 'nwnnwnnnw' + bar_char['Y'] = 'wwnnwnnnn' + bar_char['Z'] = 'nwwnwnnnn' + bar_char['-'] = 'nwnnnnwnw' + bar_char['.'] = 'wwnnnnwnn' + bar_char[' '] = 'nwwnnnwnn' + bar_char['*'] = 'nwnnwnwnn' + bar_char['$'] = 'nwnwnwnnn' + bar_char['/'] = 'nwnwnnnwn' + bar_char['+'] = 'nwnnnwnwn' + bar_char['%'] = 'nnnwnwnwn' + + self.set_fill_color(0) + code = txt + + code = code.upper() + for i in xrange (0, len(code), 2): + char_bar = code[i]; + + if not char_bar in bar_char.keys(): + raise RuntimeError ('Caracter "%s" invlido para el cdigo de barras' % char_bar) + + seq= '' + for s in xrange(0, len(bar_char[char_bar])): + seq += bar_char[char_bar][s] + + for bar in xrange(0, len(seq)): + if seq[bar] == 'n': + line_width = narrow + else: + line_width = wide + + if bar % 2 == 0: + self.rect(x,y,line_width,h,'F') + x += line_width + x += gap + +#End of class + +# Fonts: + +fpdf_charwidths['courier']={} + +for i in xrange(0,256): + fpdf_charwidths['courier'][chr(i)]=600 + fpdf_charwidths['courierB']=fpdf_charwidths['courier'] + fpdf_charwidths['courierI']=fpdf_charwidths['courier'] + fpdf_charwidths['courierBI']=fpdf_charwidths['courier'] + +fpdf_charwidths['helvetica']={ + '\x00':278,'\x01':278,'\x02':278,'\x03':278,'\x04':278,'\x05':278,'\x06':278,'\x07':278,'\x08':278,'\t':278,'\n':278,'\x0b':278,'\x0c':278,'\r':278,'\x0e':278,'\x0f':278,'\x10':278,'\x11':278,'\x12':278,'\x13':278,'\x14':278,'\x15':278, + '\x16':278,'\x17':278,'\x18':278,'\x19':278,'\x1a':278,'\x1b':278,'\x1c':278,'\x1d':278,'\x1e':278,'\x1f':278,' ':278,'!':278,'"':355,'#':556,'$':556,'%':889,'&':667,'\'':191,'(':333,')':333,'*':389,'+':584, + ',':278,'-':333,'.':278,'/':278,'0':556,'1':556,'2':556,'3':556,'4':556,'5':556,'6':556,'7':556,'8':556,'9':556,':':278,';':278,'<':584,'=':584,'>':584,'?':556,'@':1015,'A':667, + 'B':667,'C':722,'D':722,'E':667,'F':611,'G':778,'H':722,'I':278,'J':500,'K':667,'L':556,'M':833,'N':722,'O':778,'P':667,'Q':778,'R':722,'S':667,'T':611,'U':722,'V':667,'W':944, + 'X':667,'Y':667,'Z':611,'[':278,'\\':278,']':278,'^':469,'_':556,'`':333,'a':556,'b':556,'c':500,'d':556,'e':556,'f':278,'g':556,'h':556,'i':222,'j':222,'k':500,'l':222,'m':833, + 'n':556,'o':556,'p':556,'q':556,'r':333,'s':500,'t':278,'u':556,'v':500,'w':722,'x':500,'y':500,'z':500,'{':334,'|':260,'}':334,'~':584,'\x7f':350,'\x80':556,'\x81':350,'\x82':222,'\x83':556, + '\x84':333,'\x85':1000,'\x86':556,'\x87':556,'\x88':333,'\x89':1000,'\x8a':667,'\x8b':333,'\x8c':1000,'\x8d':350,'\x8e':611,'\x8f':350,'\x90':350,'\x91':222,'\x92':222,'\x93':333,'\x94':333,'\x95':350,'\x96':556,'\x97':1000,'\x98':333,'\x99':1000, + '\x9a':500,'\x9b':333,'\x9c':944,'\x9d':350,'\x9e':500,'\x9f':667,'\xa0':278,'\xa1':333,'\xa2':556,'\xa3':556,'\xa4':556,'\xa5':556,'\xa6':260,'\xa7':556,'\xa8':333,'\xa9':737,'\xaa':370,'\xab':556,'\xac':584,'\xad':333,'\xae':737,'\xaf':333, + '\xb0':400,'\xb1':584,'\xb2':333,'\xb3':333,'\xb4':333,'\xb5':556,'\xb6':537,'\xb7':278,'\xb8':333,'\xb9':333,'\xba':365,'\xbb':556,'\xbc':834,'\xbd':834,'\xbe':834,'\xbf':611,'\xc0':667,'\xc1':667,'\xc2':667,'\xc3':667,'\xc4':667,'\xc5':667, + '\xc6':1000,'\xc7':722,'\xc8':667,'\xc9':667,'\xca':667,'\xcb':667,'\xcc':278,'\xcd':278,'\xce':278,'\xcf':278,'\xd0':722,'\xd1':722,'\xd2':778,'\xd3':778,'\xd4':778,'\xd5':778,'\xd6':778,'\xd7':584,'\xd8':778,'\xd9':722,'\xda':722,'\xdb':722, + '\xdc':722,'\xdd':667,'\xde':667,'\xdf':611,'\xe0':556,'\xe1':556,'\xe2':556,'\xe3':556,'\xe4':556,'\xe5':556,'\xe6':889,'\xe7':500,'\xe8':556,'\xe9':556,'\xea':556,'\xeb':556,'\xec':278,'\xed':278,'\xee':278,'\xef':278,'\xf0':556,'\xf1':556, + '\xf2':556,'\xf3':556,'\xf4':556,'\xf5':556,'\xf6':556,'\xf7':584,'\xf8':611,'\xf9':556,'\xfa':556,'\xfb':556,'\xfc':556,'\xfd':500,'\xfe':556,'\xff':500} + +fpdf_charwidths['helveticaB']={ + '\x00':278,'\x01':278,'\x02':278,'\x03':278,'\x04':278,'\x05':278,'\x06':278,'\x07':278,'\x08':278,'\t':278,'\n':278,'\x0b':278,'\x0c':278,'\r':278,'\x0e':278,'\x0f':278,'\x10':278,'\x11':278,'\x12':278,'\x13':278,'\x14':278,'\x15':278, + '\x16':278,'\x17':278,'\x18':278,'\x19':278,'\x1a':278,'\x1b':278,'\x1c':278,'\x1d':278,'\x1e':278,'\x1f':278,' ':278,'!':333,'"':474,'#':556,'$':556,'%':889,'&':722,'\'':238,'(':333,')':333,'*':389,'+':584, + ',':278,'-':333,'.':278,'/':278,'0':556,'1':556,'2':556,'3':556,'4':556,'5':556,'6':556,'7':556,'8':556,'9':556,':':333,';':333,'<':584,'=':584,'>':584,'?':611,'@':975,'A':722, + 'B':722,'C':722,'D':722,'E':667,'F':611,'G':778,'H':722,'I':278,'J':556,'K':722,'L':611,'M':833,'N':722,'O':778,'P':667,'Q':778,'R':722,'S':667,'T':611,'U':722,'V':667,'W':944, + 'X':667,'Y':667,'Z':611,'[':333,'\\':278,']':333,'^':584,'_':556,'`':333,'a':556,'b':611,'c':556,'d':611,'e':556,'f':333,'g':611,'h':611,'i':278,'j':278,'k':556,'l':278,'m':889, + 'n':611,'o':611,'p':611,'q':611,'r':389,'s':556,'t':333,'u':611,'v':556,'w':778,'x':556,'y':556,'z':500,'{':389,'|':280,'}':389,'~':584,'\x7f':350,'\x80':556,'\x81':350,'\x82':278,'\x83':556, + '\x84':500,'\x85':1000,'\x86':556,'\x87':556,'\x88':333,'\x89':1000,'\x8a':667,'\x8b':333,'\x8c':1000,'\x8d':350,'\x8e':611,'\x8f':350,'\x90':350,'\x91':278,'\x92':278,'\x93':500,'\x94':500,'\x95':350,'\x96':556,'\x97':1000,'\x98':333,'\x99':1000, + '\x9a':556,'\x9b':333,'\x9c':944,'\x9d':350,'\x9e':500,'\x9f':667,'\xa0':278,'\xa1':333,'\xa2':556,'\xa3':556,'\xa4':556,'\xa5':556,'\xa6':280,'\xa7':556,'\xa8':333,'\xa9':737,'\xaa':370,'\xab':556,'\xac':584,'\xad':333,'\xae':737,'\xaf':333, + '\xb0':400,'\xb1':584,'\xb2':333,'\xb3':333,'\xb4':333,'\xb5':611,'\xb6':556,'\xb7':278,'\xb8':333,'\xb9':333,'\xba':365,'\xbb':556,'\xbc':834,'\xbd':834,'\xbe':834,'\xbf':611,'\xc0':722,'\xc1':722,'\xc2':722,'\xc3':722,'\xc4':722,'\xc5':722, + '\xc6':1000,'\xc7':722,'\xc8':667,'\xc9':667,'\xca':667,'\xcb':667,'\xcc':278,'\xcd':278,'\xce':278,'\xcf':278,'\xd0':722,'\xd1':722,'\xd2':778,'\xd3':778,'\xd4':778,'\xd5':778,'\xd6':778,'\xd7':584,'\xd8':778,'\xd9':722,'\xda':722,'\xdb':722, + '\xdc':722,'\xdd':667,'\xde':667,'\xdf':611,'\xe0':556,'\xe1':556,'\xe2':556,'\xe3':556,'\xe4':556,'\xe5':556,'\xe6':889,'\xe7':556,'\xe8':556,'\xe9':556,'\xea':556,'\xeb':556,'\xec':278,'\xed':278,'\xee':278,'\xef':278,'\xf0':611,'\xf1':611, + '\xf2':611,'\xf3':611,'\xf4':611,'\xf5':611,'\xf6':611,'\xf7':584,'\xf8':611,'\xf9':611,'\xfa':611,'\xfb':611,'\xfc':611,'\xfd':556,'\xfe':611,'\xff':556 +} + +fpdf_charwidths['helveticaBI']={ + '\x00':278,'\x01':278,'\x02':278,'\x03':278,'\x04':278,'\x05':278,'\x06':278,'\x07':278,'\x08':278,'\t':278,'\n':278,'\x0b':278,'\x0c':278,'\r':278,'\x0e':278,'\x0f':278,'\x10':278,'\x11':278,'\x12':278,'\x13':278,'\x14':278,'\x15':278, + '\x16':278,'\x17':278,'\x18':278,'\x19':278,'\x1a':278,'\x1b':278,'\x1c':278,'\x1d':278,'\x1e':278,'\x1f':278,' ':278,'!':333,'"':474,'#':556,'$':556,'%':889,'&':722,'\'':238,'(':333,')':333,'*':389,'+':584, + ',':278,'-':333,'.':278,'/':278,'0':556,'1':556,'2':556,'3':556,'4':556,'5':556,'6':556,'7':556,'8':556,'9':556,':':333,';':333,'<':584,'=':584,'>':584,'?':611,'@':975,'A':722, + 'B':722,'C':722,'D':722,'E':667,'F':611,'G':778,'H':722,'I':278,'J':556,'K':722,'L':611,'M':833,'N':722,'O':778,'P':667,'Q':778,'R':722,'S':667,'T':611,'U':722,'V':667,'W':944, + 'X':667,'Y':667,'Z':611,'[':333,'\\':278,']':333,'^':584,'_':556,'`':333,'a':556,'b':611,'c':556,'d':611,'e':556,'f':333,'g':611,'h':611,'i':278,'j':278,'k':556,'l':278,'m':889, + 'n':611,'o':611,'p':611,'q':611,'r':389,'s':556,'t':333,'u':611,'v':556,'w':778,'x':556,'y':556,'z':500,'{':389,'|':280,'}':389,'~':584,'\x7f':350,'\x80':556,'\x81':350,'\x82':278,'\x83':556, + '\x84':500,'\x85':1000,'\x86':556,'\x87':556,'\x88':333,'\x89':1000,'\x8a':667,'\x8b':333,'\x8c':1000,'\x8d':350,'\x8e':611,'\x8f':350,'\x90':350,'\x91':278,'\x92':278,'\x93':500,'\x94':500,'\x95':350,'\x96':556,'\x97':1000,'\x98':333,'\x99':1000, + '\x9a':556,'\x9b':333,'\x9c':944,'\x9d':350,'\x9e':500,'\x9f':667,'\xa0':278,'\xa1':333,'\xa2':556,'\xa3':556,'\xa4':556,'\xa5':556,'\xa6':280,'\xa7':556,'\xa8':333,'\xa9':737,'\xaa':370,'\xab':556,'\xac':584,'\xad':333,'\xae':737,'\xaf':333, + '\xb0':400,'\xb1':584,'\xb2':333,'\xb3':333,'\xb4':333,'\xb5':611,'\xb6':556,'\xb7':278,'\xb8':333,'\xb9':333,'\xba':365,'\xbb':556,'\xbc':834,'\xbd':834,'\xbe':834,'\xbf':611,'\xc0':722,'\xc1':722,'\xc2':722,'\xc3':722,'\xc4':722,'\xc5':722, + '\xc6':1000,'\xc7':722,'\xc8':667,'\xc9':667,'\xca':667,'\xcb':667,'\xcc':278,'\xcd':278,'\xce':278,'\xcf':278,'\xd0':722,'\xd1':722,'\xd2':778,'\xd3':778,'\xd4':778,'\xd5':778,'\xd6':778,'\xd7':584,'\xd8':778,'\xd9':722,'\xda':722,'\xdb':722, + '\xdc':722,'\xdd':667,'\xde':667,'\xdf':611,'\xe0':556,'\xe1':556,'\xe2':556,'\xe3':556,'\xe4':556,'\xe5':556,'\xe6':889,'\xe7':556,'\xe8':556,'\xe9':556,'\xea':556,'\xeb':556,'\xec':278,'\xed':278,'\xee':278,'\xef':278,'\xf0':611,'\xf1':611, + '\xf2':611,'\xf3':611,'\xf4':611,'\xf5':611,'\xf6':611,'\xf7':584,'\xf8':611,'\xf9':611,'\xfa':611,'\xfb':611,'\xfc':611,'\xfd':556,'\xfe':611,'\xff':556} + +fpdf_charwidths['helveticaI']={ + '\x00':278,'\x01':278,'\x02':278,'\x03':278,'\x04':278,'\x05':278,'\x06':278,'\x07':278,'\x08':278,'\t':278,'\n':278,'\x0b':278,'\x0c':278,'\r':278,'\x0e':278,'\x0f':278,'\x10':278,'\x11':278,'\x12':278,'\x13':278,'\x14':278,'\x15':278, + '\x16':278,'\x17':278,'\x18':278,'\x19':278,'\x1a':278,'\x1b':278,'\x1c':278,'\x1d':278,'\x1e':278,'\x1f':278,' ':278,'!':278,'"':355,'#':556,'$':556,'%':889,'&':667,'\'':191,'(':333,')':333,'*':389,'+':584, + ',':278,'-':333,'.':278,'/':278,'0':556,'1':556,'2':556,'3':556,'4':556,'5':556,'6':556,'7':556,'8':556,'9':556,':':278,';':278,'<':584,'=':584,'>':584,'?':556,'@':1015,'A':667, + 'B':667,'C':722,'D':722,'E':667,'F':611,'G':778,'H':722,'I':278,'J':500,'K':667,'L':556,'M':833,'N':722,'O':778,'P':667,'Q':778,'R':722,'S':667,'T':611,'U':722,'V':667,'W':944, + 'X':667,'Y':667,'Z':611,'[':278,'\\':278,']':278,'^':469,'_':556,'`':333,'a':556,'b':556,'c':500,'d':556,'e':556,'f':278,'g':556,'h':556,'i':222,'j':222,'k':500,'l':222,'m':833, + 'n':556,'o':556,'p':556,'q':556,'r':333,'s':500,'t':278,'u':556,'v':500,'w':722,'x':500,'y':500,'z':500,'{':334,'|':260,'}':334,'~':584,'\x7f':350,'\x80':556,'\x81':350,'\x82':222,'\x83':556, + '\x84':333,'\x85':1000,'\x86':556,'\x87':556,'\x88':333,'\x89':1000,'\x8a':667,'\x8b':333,'\x8c':1000,'\x8d':350,'\x8e':611,'\x8f':350,'\x90':350,'\x91':222,'\x92':222,'\x93':333,'\x94':333,'\x95':350,'\x96':556,'\x97':1000,'\x98':333,'\x99':1000, + '\x9a':500,'\x9b':333,'\x9c':944,'\x9d':350,'\x9e':500,'\x9f':667,'\xa0':278,'\xa1':333,'\xa2':556,'\xa3':556,'\xa4':556,'\xa5':556,'\xa6':260,'\xa7':556,'\xa8':333,'\xa9':737,'\xaa':370,'\xab':556,'\xac':584,'\xad':333,'\xae':737,'\xaf':333, + '\xb0':400,'\xb1':584,'\xb2':333,'\xb3':333,'\xb4':333,'\xb5':556,'\xb6':537,'\xb7':278,'\xb8':333,'\xb9':333,'\xba':365,'\xbb':556,'\xbc':834,'\xbd':834,'\xbe':834,'\xbf':611,'\xc0':667,'\xc1':667,'\xc2':667,'\xc3':667,'\xc4':667,'\xc5':667, + '\xc6':1000,'\xc7':722,'\xc8':667,'\xc9':667,'\xca':667,'\xcb':667,'\xcc':278,'\xcd':278,'\xce':278,'\xcf':278,'\xd0':722,'\xd1':722,'\xd2':778,'\xd3':778,'\xd4':778,'\xd5':778,'\xd6':778,'\xd7':584,'\xd8':778,'\xd9':722,'\xda':722,'\xdb':722, + '\xdc':722,'\xdd':667,'\xde':667,'\xdf':611,'\xe0':556,'\xe1':556,'\xe2':556,'\xe3':556,'\xe4':556,'\xe5':556,'\xe6':889,'\xe7':500,'\xe8':556,'\xe9':556,'\xea':556,'\xeb':556,'\xec':278,'\xed':278,'\xee':278,'\xef':278,'\xf0':556,'\xf1':556, + '\xf2':556,'\xf3':556,'\xf4':556,'\xf5':556,'\xf6':556,'\xf7':584,'\xf8':611,'\xf9':556,'\xfa':556,'\xfb':556,'\xfc':556,'\xfd':500,'\xfe':556,'\xff':500} + +fpdf_charwidths['symbol']={ + '\x00':250,'\x01':250,'\x02':250,'\x03':250,'\x04':250,'\x05':250,'\x06':250,'\x07':250,'\x08':250,'\t':250,'\n':250,'\x0b':250,'\x0c':250,'\r':250,'\x0e':250,'\x0f':250,'\x10':250,'\x11':250,'\x12':250,'\x13':250,'\x14':250,'\x15':250, + '\x16':250,'\x17':250,'\x18':250,'\x19':250,'\x1a':250,'\x1b':250,'\x1c':250,'\x1d':250,'\x1e':250,'\x1f':250,' ':250,'!':333,'"':713,'#':500,'$':549,'%':833,'&':778,'\'':439,'(':333,')':333,'*':500,'+':549, + ',':250,'-':549,'.':250,'/':278,'0':500,'1':500,'2':500,'3':500,'4':500,'5':500,'6':500,'7':500,'8':500,'9':500,':':278,';':278,'<':549,'=':549,'>':549,'?':444,'@':549,'A':722, + 'B':667,'C':722,'D':612,'E':611,'F':763,'G':603,'H':722,'I':333,'J':631,'K':722,'L':686,'M':889,'N':722,'O':722,'P':768,'Q':741,'R':556,'S':592,'T':611,'U':690,'V':439,'W':768, + 'X':645,'Y':795,'Z':611,'[':333,'\\':863,']':333,'^':658,'_':500,'`':500,'a':631,'b':549,'c':549,'d':494,'e':439,'f':521,'g':411,'h':603,'i':329,'j':603,'k':549,'l':549,'m':576, + 'n':521,'o':549,'p':549,'q':521,'r':549,'s':603,'t':439,'u':576,'v':713,'w':686,'x':493,'y':686,'z':494,'{':480,'|':200,'}':480,'~':549,'\x7f':0,'\x80':0,'\x81':0,'\x82':0,'\x83':0, + '\x84':0,'\x85':0,'\x86':0,'\x87':0,'\x88':0,'\x89':0,'\x8a':0,'\x8b':0,'\x8c':0,'\x8d':0,'\x8e':0,'\x8f':0,'\x90':0,'\x91':0,'\x92':0,'\x93':0,'\x94':0,'\x95':0,'\x96':0,'\x97':0,'\x98':0,'\x99':0, + '\x9a':0,'\x9b':0,'\x9c':0,'\x9d':0,'\x9e':0,'\x9f':0,'\xa0':750,'\xa1':620,'\xa2':247,'\xa3':549,'\xa4':167,'\xa5':713,'\xa6':500,'\xa7':753,'\xa8':753,'\xa9':753,'\xaa':753,'\xab':1042,'\xac':987,'\xad':603,'\xae':987,'\xaf':603, + '\xb0':400,'\xb1':549,'\xb2':411,'\xb3':549,'\xb4':549,'\xb5':713,'\xb6':494,'\xb7':460,'\xb8':549,'\xb9':549,'\xba':549,'\xbb':549,'\xbc':1000,'\xbd':603,'\xbe':1000,'\xbf':658,'\xc0':823,'\xc1':686,'\xc2':795,'\xc3':987,'\xc4':768,'\xc5':768, + '\xc6':823,'\xc7':768,'\xc8':768,'\xc9':713,'\xca':713,'\xcb':713,'\xcc':713,'\xcd':713,'\xce':713,'\xcf':713,'\xd0':768,'\xd1':713,'\xd2':790,'\xd3':790,'\xd4':890,'\xd5':823,'\xd6':549,'\xd7':250,'\xd8':713,'\xd9':603,'\xda':603,'\xdb':1042, + '\xdc':987,'\xdd':603,'\xde':987,'\xdf':603,'\xe0':494,'\xe1':329,'\xe2':790,'\xe3':790,'\xe4':786,'\xe5':713,'\xe6':384,'\xe7':384,'\xe8':384,'\xe9':384,'\xea':384,'\xeb':384,'\xec':494,'\xed':494,'\xee':494,'\xef':494,'\xf0':0,'\xf1':329, + '\xf2':274,'\xf3':686,'\xf4':686,'\xf5':686,'\xf6':384,'\xf7':384,'\xf8':384,'\xf9':384,'\xfa':384,'\xfb':384,'\xfc':494,'\xfd':494,'\xfe':494,'\xff':0} + +fpdf_charwidths['times']={ + '\x00':250,'\x01':250,'\x02':250,'\x03':250,'\x04':250,'\x05':250,'\x06':250,'\x07':250,'\x08':250,'\t':250,'\n':250,'\x0b':250,'\x0c':250,'\r':250,'\x0e':250,'\x0f':250,'\x10':250,'\x11':250,'\x12':250,'\x13':250,'\x14':250,'\x15':250, + '\x16':250,'\x17':250,'\x18':250,'\x19':250,'\x1a':250,'\x1b':250,'\x1c':250,'\x1d':250,'\x1e':250,'\x1f':250,' ':250,'!':333,'"':408,'#':500,'$':500,'%':833,'&':778,'\'':180,'(':333,')':333,'*':500,'+':564, + ',':250,'-':333,'.':250,'/':278,'0':500,'1':500,'2':500,'3':500,'4':500,'5':500,'6':500,'7':500,'8':500,'9':500,':':278,';':278,'<':564,'=':564,'>':564,'?':444,'@':921,'A':722, + 'B':667,'C':667,'D':722,'E':611,'F':556,'G':722,'H':722,'I':333,'J':389,'K':722,'L':611,'M':889,'N':722,'O':722,'P':556,'Q':722,'R':667,'S':556,'T':611,'U':722,'V':722,'W':944, + 'X':722,'Y':722,'Z':611,'[':333,'\\':278,']':333,'^':469,'_':500,'`':333,'a':444,'b':500,'c':444,'d':500,'e':444,'f':333,'g':500,'h':500,'i':278,'j':278,'k':500,'l':278,'m':778, + 'n':500,'o':500,'p':500,'q':500,'r':333,'s':389,'t':278,'u':500,'v':500,'w':722,'x':500,'y':500,'z':444,'{':480,'|':200,'}':480,'~':541,'\x7f':350,'\x80':500,'\x81':350,'\x82':333,'\x83':500, + '\x84':444,'\x85':1000,'\x86':500,'\x87':500,'\x88':333,'\x89':1000,'\x8a':556,'\x8b':333,'\x8c':889,'\x8d':350,'\x8e':611,'\x8f':350,'\x90':350,'\x91':333,'\x92':333,'\x93':444,'\x94':444,'\x95':350,'\x96':500,'\x97':1000,'\x98':333,'\x99':980, + '\x9a':389,'\x9b':333,'\x9c':722,'\x9d':350,'\x9e':444,'\x9f':722,'\xa0':250,'\xa1':333,'\xa2':500,'\xa3':500,'\xa4':500,'\xa5':500,'\xa6':200,'\xa7':500,'\xa8':333,'\xa9':760,'\xaa':276,'\xab':500,'\xac':564,'\xad':333,'\xae':760,'\xaf':333, + '\xb0':400,'\xb1':564,'\xb2':300,'\xb3':300,'\xb4':333,'\xb5':500,'\xb6':453,'\xb7':250,'\xb8':333,'\xb9':300,'\xba':310,'\xbb':500,'\xbc':750,'\xbd':750,'\xbe':750,'\xbf':444,'\xc0':722,'\xc1':722,'\xc2':722,'\xc3':722,'\xc4':722,'\xc5':722, + '\xc6':889,'\xc7':667,'\xc8':611,'\xc9':611,'\xca':611,'\xcb':611,'\xcc':333,'\xcd':333,'\xce':333,'\xcf':333,'\xd0':722,'\xd1':722,'\xd2':722,'\xd3':722,'\xd4':722,'\xd5':722,'\xd6':722,'\xd7':564,'\xd8':722,'\xd9':722,'\xda':722,'\xdb':722, + '\xdc':722,'\xdd':722,'\xde':556,'\xdf':500,'\xe0':444,'\xe1':444,'\xe2':444,'\xe3':444,'\xe4':444,'\xe5':444,'\xe6':667,'\xe7':444,'\xe8':444,'\xe9':444,'\xea':444,'\xeb':444,'\xec':278,'\xed':278,'\xee':278,'\xef':278,'\xf0':500,'\xf1':500, + '\xf2':500,'\xf3':500,'\xf4':500,'\xf5':500,'\xf6':500,'\xf7':564,'\xf8':500,'\xf9':500,'\xfa':500,'\xfb':500,'\xfc':500,'\xfd':500,'\xfe':500,'\xff':500} + +fpdf_charwidths['timesB']={ + '\x00':250,'\x01':250,'\x02':250,'\x03':250,'\x04':250,'\x05':250,'\x06':250,'\x07':250,'\x08':250,'\t':250,'\n':250,'\x0b':250,'\x0c':250,'\r':250,'\x0e':250,'\x0f':250,'\x10':250,'\x11':250,'\x12':250,'\x13':250,'\x14':250,'\x15':250, + '\x16':250,'\x17':250,'\x18':250,'\x19':250,'\x1a':250,'\x1b':250,'\x1c':250,'\x1d':250,'\x1e':250,'\x1f':250,' ':250,'!':333,'"':555,'#':500,'$':500,'%':1000,'&':833,'\'':278,'(':333,')':333,'*':500,'+':570, + ',':250,'-':333,'.':250,'/':278,'0':500,'1':500,'2':500,'3':500,'4':500,'5':500,'6':500,'7':500,'8':500,'9':500,':':333,';':333,'<':570,'=':570,'>':570,'?':500,'@':930,'A':722, + 'B':667,'C':722,'D':722,'E':667,'F':611,'G':778,'H':778,'I':389,'J':500,'K':778,'L':667,'M':944,'N':722,'O':778,'P':611,'Q':778,'R':722,'S':556,'T':667,'U':722,'V':722,'W':1000, + 'X':722,'Y':722,'Z':667,'[':333,'\\':278,']':333,'^':581,'_':500,'`':333,'a':500,'b':556,'c':444,'d':556,'e':444,'f':333,'g':500,'h':556,'i':278,'j':333,'k':556,'l':278,'m':833, + 'n':556,'o':500,'p':556,'q':556,'r':444,'s':389,'t':333,'u':556,'v':500,'w':722,'x':500,'y':500,'z':444,'{':394,'|':220,'}':394,'~':520,'\x7f':350,'\x80':500,'\x81':350,'\x82':333,'\x83':500, + '\x84':500,'\x85':1000,'\x86':500,'\x87':500,'\x88':333,'\x89':1000,'\x8a':556,'\x8b':333,'\x8c':1000,'\x8d':350,'\x8e':667,'\x8f':350,'\x90':350,'\x91':333,'\x92':333,'\x93':500,'\x94':500,'\x95':350,'\x96':500,'\x97':1000,'\x98':333,'\x99':1000, + '\x9a':389,'\x9b':333,'\x9c':722,'\x9d':350,'\x9e':444,'\x9f':722,'\xa0':250,'\xa1':333,'\xa2':500,'\xa3':500,'\xa4':500,'\xa5':500,'\xa6':220,'\xa7':500,'\xa8':333,'\xa9':747,'\xaa':300,'\xab':500,'\xac':570,'\xad':333,'\xae':747,'\xaf':333, + '\xb0':400,'\xb1':570,'\xb2':300,'\xb3':300,'\xb4':333,'\xb5':556,'\xb6':540,'\xb7':250,'\xb8':333,'\xb9':300,'\xba':330,'\xbb':500,'\xbc':750,'\xbd':750,'\xbe':750,'\xbf':500,'\xc0':722,'\xc1':722,'\xc2':722,'\xc3':722,'\xc4':722,'\xc5':722, + '\xc6':1000,'\xc7':722,'\xc8':667,'\xc9':667,'\xca':667,'\xcb':667,'\xcc':389,'\xcd':389,'\xce':389,'\xcf':389,'\xd0':722,'\xd1':722,'\xd2':778,'\xd3':778,'\xd4':778,'\xd5':778,'\xd6':778,'\xd7':570,'\xd8':778,'\xd9':722,'\xda':722,'\xdb':722, + '\xdc':722,'\xdd':722,'\xde':611,'\xdf':556,'\xe0':500,'\xe1':500,'\xe2':500,'\xe3':500,'\xe4':500,'\xe5':500,'\xe6':722,'\xe7':444,'\xe8':444,'\xe9':444,'\xea':444,'\xeb':444,'\xec':278,'\xed':278,'\xee':278,'\xef':278,'\xf0':500,'\xf1':556, + '\xf2':500,'\xf3':500,'\xf4':500,'\xf5':500,'\xf6':500,'\xf7':570,'\xf8':500,'\xf9':556,'\xfa':556,'\xfb':556,'\xfc':556,'\xfd':500,'\xfe':556,'\xff':500} + +fpdf_charwidths['timesBI']={ + '\x00':250,'\x01':250,'\x02':250,'\x03':250,'\x04':250,'\x05':250,'\x06':250,'\x07':250,'\x08':250,'\t':250,'\n':250,'\x0b':250,'\x0c':250,'\r':250,'\x0e':250,'\x0f':250,'\x10':250,'\x11':250,'\x12':250,'\x13':250,'\x14':250,'\x15':250, + '\x16':250,'\x17':250,'\x18':250,'\x19':250,'\x1a':250,'\x1b':250,'\x1c':250,'\x1d':250,'\x1e':250,'\x1f':250,' ':250,'!':389,'"':555,'#':500,'$':500,'%':833,'&':778,'\'':278,'(':333,')':333,'*':500,'+':570, + ',':250,'-':333,'.':250,'/':278,'0':500,'1':500,'2':500,'3':500,'4':500,'5':500,'6':500,'7':500,'8':500,'9':500,':':333,';':333,'<':570,'=':570,'>':570,'?':500,'@':832,'A':667, + 'B':667,'C':667,'D':722,'E':667,'F':667,'G':722,'H':778,'I':389,'J':500,'K':667,'L':611,'M':889,'N':722,'O':722,'P':611,'Q':722,'R':667,'S':556,'T':611,'U':722,'V':667,'W':889, + 'X':667,'Y':611,'Z':611,'[':333,'\\':278,']':333,'^':570,'_':500,'`':333,'a':500,'b':500,'c':444,'d':500,'e':444,'f':333,'g':500,'h':556,'i':278,'j':278,'k':500,'l':278,'m':778, + 'n':556,'o':500,'p':500,'q':500,'r':389,'s':389,'t':278,'u':556,'v':444,'w':667,'x':500,'y':444,'z':389,'{':348,'|':220,'}':348,'~':570,'\x7f':350,'\x80':500,'\x81':350,'\x82':333,'\x83':500, + '\x84':500,'\x85':1000,'\x86':500,'\x87':500,'\x88':333,'\x89':1000,'\x8a':556,'\x8b':333,'\x8c':944,'\x8d':350,'\x8e':611,'\x8f':350,'\x90':350,'\x91':333,'\x92':333,'\x93':500,'\x94':500,'\x95':350,'\x96':500,'\x97':1000,'\x98':333,'\x99':1000, + '\x9a':389,'\x9b':333,'\x9c':722,'\x9d':350,'\x9e':389,'\x9f':611,'\xa0':250,'\xa1':389,'\xa2':500,'\xa3':500,'\xa4':500,'\xa5':500,'\xa6':220,'\xa7':500,'\xa8':333,'\xa9':747,'\xaa':266,'\xab':500,'\xac':606,'\xad':333,'\xae':747,'\xaf':333, + '\xb0':400,'\xb1':570,'\xb2':300,'\xb3':300,'\xb4':333,'\xb5':576,'\xb6':500,'\xb7':250,'\xb8':333,'\xb9':300,'\xba':300,'\xbb':500,'\xbc':750,'\xbd':750,'\xbe':750,'\xbf':500,'\xc0':667,'\xc1':667,'\xc2':667,'\xc3':667,'\xc4':667,'\xc5':667, + '\xc6':944,'\xc7':667,'\xc8':667,'\xc9':667,'\xca':667,'\xcb':667,'\xcc':389,'\xcd':389,'\xce':389,'\xcf':389,'\xd0':722,'\xd1':722,'\xd2':722,'\xd3':722,'\xd4':722,'\xd5':722,'\xd6':722,'\xd7':570,'\xd8':722,'\xd9':722,'\xda':722,'\xdb':722, + '\xdc':722,'\xdd':611,'\xde':611,'\xdf':500,'\xe0':500,'\xe1':500,'\xe2':500,'\xe3':500,'\xe4':500,'\xe5':500,'\xe6':722,'\xe7':444,'\xe8':444,'\xe9':444,'\xea':444,'\xeb':444,'\xec':278,'\xed':278,'\xee':278,'\xef':278,'\xf0':500,'\xf1':556, + '\xf2':500,'\xf3':500,'\xf4':500,'\xf5':500,'\xf6':500,'\xf7':570,'\xf8':500,'\xf9':556,'\xfa':556,'\xfb':556,'\xfc':556,'\xfd':444,'\xfe':500,'\xff':444} + +fpdf_charwidths['timesI']={ + '\x00':250,'\x01':250,'\x02':250,'\x03':250,'\x04':250,'\x05':250,'\x06':250,'\x07':250,'\x08':250,'\t':250,'\n':250,'\x0b':250,'\x0c':250,'\r':250,'\x0e':250,'\x0f':250,'\x10':250,'\x11':250,'\x12':250,'\x13':250,'\x14':250,'\x15':250, + '\x16':250,'\x17':250,'\x18':250,'\x19':250,'\x1a':250,'\x1b':250,'\x1c':250,'\x1d':250,'\x1e':250,'\x1f':250,' ':250,'!':333,'"':420,'#':500,'$':500,'%':833,'&':778,'\'':214,'(':333,')':333,'*':500,'+':675, + ',':250,'-':333,'.':250,'/':278,'0':500,'1':500,'2':500,'3':500,'4':500,'5':500,'6':500,'7':500,'8':500,'9':500,':':333,';':333,'<':675,'=':675,'>':675,'?':500,'@':920,'A':611, + 'B':611,'C':667,'D':722,'E':611,'F':611,'G':722,'H':722,'I':333,'J':444,'K':667,'L':556,'M':833,'N':667,'O':722,'P':611,'Q':722,'R':611,'S':500,'T':556,'U':722,'V':611,'W':833, + 'X':611,'Y':556,'Z':556,'[':389,'\\':278,']':389,'^':422,'_':500,'`':333,'a':500,'b':500,'c':444,'d':500,'e':444,'f':278,'g':500,'h':500,'i':278,'j':278,'k':444,'l':278,'m':722, + 'n':500,'o':500,'p':500,'q':500,'r':389,'s':389,'t':278,'u':500,'v':444,'w':667,'x':444,'y':444,'z':389,'{':400,'|':275,'}':400,'~':541,'\x7f':350,'\x80':500,'\x81':350,'\x82':333,'\x83':500, + '\x84':556,'\x85':889,'\x86':500,'\x87':500,'\x88':333,'\x89':1000,'\x8a':500,'\x8b':333,'\x8c':944,'\x8d':350,'\x8e':556,'\x8f':350,'\x90':350,'\x91':333,'\x92':333,'\x93':556,'\x94':556,'\x95':350,'\x96':500,'\x97':889,'\x98':333,'\x99':980, + '\x9a':389,'\x9b':333,'\x9c':667,'\x9d':350,'\x9e':389,'\x9f':556,'\xa0':250,'\xa1':389,'\xa2':500,'\xa3':500,'\xa4':500,'\xa5':500,'\xa6':275,'\xa7':500,'\xa8':333,'\xa9':760,'\xaa':276,'\xab':500,'\xac':675,'\xad':333,'\xae':760,'\xaf':333, + '\xb0':400,'\xb1':675,'\xb2':300,'\xb3':300,'\xb4':333,'\xb5':500,'\xb6':523,'\xb7':250,'\xb8':333,'\xb9':300,'\xba':310,'\xbb':500,'\xbc':750,'\xbd':750,'\xbe':750,'\xbf':500,'\xc0':611,'\xc1':611,'\xc2':611,'\xc3':611,'\xc4':611,'\xc5':611, + '\xc6':889,'\xc7':667,'\xc8':611,'\xc9':611,'\xca':611,'\xcb':611,'\xcc':333,'\xcd':333,'\xce':333,'\xcf':333,'\xd0':722,'\xd1':667,'\xd2':722,'\xd3':722,'\xd4':722,'\xd5':722,'\xd6':722,'\xd7':675,'\xd8':722,'\xd9':722,'\xda':722,'\xdb':722, + '\xdc':722,'\xdd':556,'\xde':611,'\xdf':500,'\xe0':500,'\xe1':500,'\xe2':500,'\xe3':500,'\xe4':500,'\xe5':500,'\xe6':667,'\xe7':444,'\xe8':444,'\xe9':444,'\xea':444,'\xeb':444,'\xec':278,'\xed':278,'\xee':278,'\xef':278,'\xf0':500,'\xf1':500, + '\xf2':500,'\xf3':500,'\xf4':500,'\xf5':500,'\xf6':500,'\xf7':675,'\xf8':500,'\xf9':500,'\xfa':500,'\xfb':500,'\xfc':500,'\xfd':444,'\xfe':500,'\xff':444} + +fpdf_charwidths['zapfdingbats']={ + '\x00':0,'\x01':0,'\x02':0,'\x03':0,'\x04':0,'\x05':0,'\x06':0,'\x07':0,'\x08':0,'\t':0,'\n':0,'\x0b':0,'\x0c':0,'\r':0,'\x0e':0,'\x0f':0,'\x10':0,'\x11':0,'\x12':0,'\x13':0,'\x14':0,'\x15':0, + '\x16':0,'\x17':0,'\x18':0,'\x19':0,'\x1a':0,'\x1b':0,'\x1c':0,'\x1d':0,'\x1e':0,'\x1f':0,' ':278,'!':974,'"':961,'#':974,'$':980,'%':719,'&':789,'\'':790,'(':791,')':690,'*':960,'+':939, + ',':549,'-':855,'.':911,'/':933,'0':911,'1':945,'2':974,'3':755,'4':846,'5':762,'6':761,'7':571,'8':677,'9':763,':':760,';':759,'<':754,'=':494,'>':552,'?':537,'@':577,'A':692, + 'B':786,'C':788,'D':788,'E':790,'F':793,'G':794,'H':816,'I':823,'J':789,'K':841,'L':823,'M':833,'N':816,'O':831,'P':923,'Q':744,'R':723,'S':749,'T':790,'U':792,'V':695,'W':776, + 'X':768,'Y':792,'Z':759,'[':707,'\\':708,']':682,'^':701,'_':826,'`':815,'a':789,'b':789,'c':707,'d':687,'e':696,'f':689,'g':786,'h':787,'i':713,'j':791,'k':785,'l':791,'m':873, + 'n':761,'o':762,'p':762,'q':759,'r':759,'s':892,'t':892,'u':788,'v':784,'w':438,'x':138,'y':277,'z':415,'{':392,'|':392,'}':668,'~':668,'\x7f':0,'\x80':390,'\x81':390,'\x82':317,'\x83':317, + '\x84':276,'\x85':276,'\x86':509,'\x87':509,'\x88':410,'\x89':410,'\x8a':234,'\x8b':234,'\x8c':334,'\x8d':334,'\x8e':0,'\x8f':0,'\x90':0,'\x91':0,'\x92':0,'\x93':0,'\x94':0,'\x95':0,'\x96':0,'\x97':0,'\x98':0,'\x99':0, + '\x9a':0,'\x9b':0,'\x9c':0,'\x9d':0,'\x9e':0,'\x9f':0,'\xa0':0,'\xa1':732,'\xa2':544,'\xa3':544,'\xa4':910,'\xa5':667,'\xa6':760,'\xa7':760,'\xa8':776,'\xa9':595,'\xaa':694,'\xab':626,'\xac':788,'\xad':788,'\xae':788,'\xaf':788, + '\xb0':788,'\xb1':788,'\xb2':788,'\xb3':788,'\xb4':788,'\xb5':788,'\xb6':788,'\xb7':788,'\xb8':788,'\xb9':788,'\xba':788,'\xbb':788,'\xbc':788,'\xbd':788,'\xbe':788,'\xbf':788,'\xc0':788,'\xc1':788,'\xc2':788,'\xc3':788,'\xc4':788,'\xc5':788, + '\xc6':788,'\xc7':788,'\xc8':788,'\xc9':788,'\xca':788,'\xcb':788,'\xcc':788,'\xcd':788,'\xce':788,'\xcf':788,'\xd0':788,'\xd1':788,'\xd2':788,'\xd3':788,'\xd4':894,'\xd5':838,'\xd6':1016,'\xd7':458,'\xd8':748,'\xd9':924,'\xda':748,'\xdb':918, + '\xdc':927,'\xdd':928,'\xde':928,'\xdf':834,'\xe0':873,'\xe1':828,'\xe2':924,'\xe3':924,'\xe4':917,'\xe5':930,'\xe6':931,'\xe7':463,'\xe8':883,'\xe9':836,'\xea':836,'\xeb':867,'\xec':867,'\xed':696,'\xee':696,'\xef':874,'\xf0':0,'\xf1':874, + '\xf2':760,'\xf3':946,'\xf4':771,'\xf5':865,'\xf6':771,'\xf7':888,'\xf8':967,'\xf9':888,'\xfa':831,'\xfb':873,'\xfc':927,'\xfd':970,'\xfe':918,'\xff':0} + + ADDED gluon/contrib/pyfpdf/html.py Index: gluon/contrib/pyfpdf/html.py ================================================================== --- gluon/contrib/pyfpdf/html.py +++ gluon/contrib/pyfpdf/html.py @@ -0,0 +1,456 @@ +# -*- coding: latin-1 -*- + +"HTML Renderer for FPDF.py" + +__author__ = "Mariano Reingart <reingart@gmail.com>" +__copyright__ = "Copyright (C) 2010 Mariano Reingart" +__license__ = "LGPL 3.0" + +# Inspired by tuto5.py and several examples from fpdf.org, html2fpdf, etc. + +from fpdf import FPDF +from HTMLParser import HTMLParser + +DEBUG = False + +def px2mm(px): + return int(px)*25.4/72.0 + +def hex2dec(color = "#000000"): + if color: + r = int(color[1:3], 16) + g = int(color[3:5], 16) + b = int(color[5:7], 16) + return r, g, b + +class HTML2FPDF(HTMLParser): + "Render basic HTML to FPDF" + + def __init__(self, pdf, image_map): + HTMLParser.__init__(self) + self.image_map = image_map + self.style = {} + self.pre = False + self.href = '' + self.align = '' + self.page_links = {} + self.font_list = ("times","courier", "helvetica") + self.pdf = pdf + self.r = self.g = self.b = 0 + self.indent = 0 + self.bullet = [] + self.set_font("times", 12) + self.table = None # table attributes + self.table_col_width = None # column (header) widths + self.table_col_index = None # current column index + self.td = None # cell attributes + self.th = False # header enabled + self.tr = None + self.theader = None # table header cells + self.tfooter = None # table footer cells + self.thead = None + self.tfoot = None + self.theader_out = self.tfooter_out = False + + def width2mm(self, length): + if length[-1]=='%': + total = self.pdf.w - self.pdf.r_margin - self.pdf.l_margin + if self.table['width'][-1]=='%': + total *= int(self.table['width'][:-1])/100.0 + return int(length[:-1]) * total / 101.0 + else: + return int(length) / 6.0 + + def handle_data(self, txt): + if self.td is not None: # drawing a table? + if 'width' not in self.td and 'colspan' not in self.td: + l = [self.table_col_width[self.table_col_index]] + elif 'colspan' in self.td: + i = self.table_col_index + colspan = int(self.td['colspan']) + l = self.table_col_width[i:i+colspan] + else: + l = [self.td.get('width','240')] + w = sum([self.width2mm(lenght) for lenght in l]) + h = int(self.td.get('height', 0)) / 4 or self.h*1.30 + self.table_h = h + border = int(self.table.get('border', 0)) + if not self.th: + align = self.td.get('align', 'L')[0].upper() + border = border and 'LR' + else: + self.set_style('B',True) + border = border or 'B' + align = 'C' + bgcolor = hex2dec(self.td.get('bgcolor', self.tr.get('bgcolor', ''))) + # parsing table header/footer (drawn later): + if self.thead is not None: + self.theader.append(((w,h,txt,border,0,align), bgcolor)) + if self.tfoot is not None: + self.tfooter.append(((w,h,txt,border,0,align), bgcolor)) + # check if reached end of page, add table footer and header: + height = h + (self.tfooter and self.tfooter[0][0][1] or 0) + if self.pdf.y+height>self.pdf.page_break_trigger and not self.th: + self.output_table_footer() + self.pdf.add_page() + self.theader_out = self.tfooter_out = False + if self.tfoot is None and self.thead is None: + if not self.theader_out: + self.output_table_header() + self.box_shadow(w, h, bgcolor) + if DEBUG: print "td cell", self.pdf.x, w, txt, "*" + self.pdf.cell(w,h,txt,border,0,align) + elif self.table is not None: + # ignore anything else than td inside a table + pass + elif self.align: + if DEBUG: print "cell", txt, "*" + self.pdf.cell(0,self.h,txt,0,1,self.align[0].upper(), self.href) + else: + txt = txt.replace("\n"," ") + if self.href: + self.put_link(self.href,txt) + else: + if DEBUG: print "write", txt, "*" + self.pdf.write(self.h,txt) + + def box_shadow(self, w, h, bgcolor): + if DEBUG: print "box_shadow", w, h, bgcolor + if bgcolor: + fill_color = self.pdf.fill_color + self.pdf.set_fill_color(*bgcolor) + self.pdf.rect(self.pdf.x, self.pdf.y, w, h, 'F') + self.pdf.fill_color = fill_color + + def output_table_header(self): + if self.theader: + b = self.b + x = self.pdf.x + self.pdf.set_x(self.table_offset) + self.set_style('B',True) + for cell, bgcolor in self.theader: + self.box_shadow(cell[0], cell[1], bgcolor) + self.pdf.cell(*cell) + self.set_style('B',b) + self.pdf.ln(self.theader[0][0][1]) + self.pdf.set_x(self.table_offset) + #self.pdf.set_x(x) + self.theader_out = True + + def output_table_footer(self): + if self.tfooter: + x = self.pdf.x + self.pdf.set_x(self.table_offset) + #TODO: self.output_table_sep() + for cell, bgcolor in self.tfooter: + self.box_shadow(cell[0], cell[1], bgcolor) + self.pdf.cell(*cell) + self.pdf.ln(self.tfooter[0][0][1]) + self.pdf.set_x(x) + if int(self.table.get('border', 0)): + self.output_table_sep() + self.tfooter_out = True + + def output_table_sep(self): + self.pdf.set_x(self.table_offset) + x1 = self.pdf.x + y1 = self.pdf.y + w = sum([self.width2mm(lenght) for lenght in self.table_col_width]) + self.pdf.line(x1,y1,x1+w,y1) + + + def handle_starttag(self, tag, attrs): + attrs = dict(attrs) + if DEBUG: print "STARTTAG", tag, attrs + if tag=='b' or tag=='i' or tag=='u': + self.set_style(tag,1) + if tag=='a': + self.href=attrs['href'] + if tag=='br': + self.pdf.ln(5) + if tag=='p': + self.pdf.ln(5) + if attrs: + self.align=attrs['align'].lower() + if tag in ('h1', 'h2', 'h3', 'h4', 'h5', 'h6'): + k = (2, 1.5, 1.17, 1, 0.83, 0.67)[int(tag[1])] + self.pdf.ln(5*k) + self.pdf.set_text_color(150,0,0) + self.pdf.set_font_size(12 * k) + if attrs: self.align = attrs.get('align') + if tag=='hr': + self.put_line() + if tag=='pre': + self.pdf.set_font('Courier','',11) + self.pdf.set_font_size(11) + self.set_style('B',False) + self.set_style('I',False) + self.pre = True + if tag=='blockquote': + self.set_text_color(100,0,45) + self.pdf.ln(3) + if tag=='ul': + self.indent+=1 + self.bullet.append('\x95') + if tag=='ol': + self.indent+=1 + self.bullet.append(0) + if tag=='li': + self.pdf.ln(self.h+2) + self.pdf.set_text_color(190,0,0) + bullet = self.bullet[self.indent-1] + if not isinstance(bullet, basestring): + bullet += 1 + self.bullet[self.indent-1] = bullet + bullet = "%s. " % bullet + self.pdf.write(self.h,'%s%s ' % (' '*5*self.indent, bullet)) + self.set_text_color() + if tag=='font': + if 'color' in attrs: + self.color = hex2dec(attrs['color']) + self.set_text_color(*color) + self.color = color + if 'face' in attrs and attrs['face'].lower() in self.font_list: + face = attrs.get('face').lower() + self.pdf.set_font(face) + self.font_face = face + if 'size' in attrs: + face = attrs.get('size') + self.pdf.set_font('', size) + self.font_size = size + if tag=='table': + self.table = dict([(k.lower(), v) for k,v in attrs.items()]) + if not 'width' in self.table: + self.table['width'] = '100%' + if self.table['width'][-1]=='%': + w = self.pdf.w - self.pdf.r_margin - self.pdf.l_margin + w *= int(self.table['width'][:-1])/100.0 + self.table_offset = (self.pdf.w-w)/2.0 + self.table_col_width = [] + self.theader_out = self.tfooter_out = False + self.theader = [] + self.tfooter = [] + self.thead = None + self.tfoot = None + self.pdf.ln() + if tag=='tr': + self.tr = dict([(k.lower(), v) for k,v in attrs.items()]) + self.table_col_index = 0 + self.pdf.set_x(self.table_offset) + if tag=='td': + self.td = dict([(k.lower(), v) for k,v in attrs.items()]) + if tag=='th': + self.td = dict([(k.lower(), v) for k,v in attrs.items()]) + self.th = True + if self.td['width']: + self.table_col_width.append(self.td['width']) + if tag=='thead': + self.thead = {} + if tag=='tfoot': + self.tfoot = {} + if tag=='img': + if 'src' in attrs: + x = self.pdf.get_x() + y = self.pdf.get_y() + w = px2mm(attrs.get('width', 0)) + h = px2mm(attrs.get('height',0)) + if self.align and self.align[0].upper() == 'C': + x = (self.pdf.w-x)/2.0 - w/2.0 + self.pdf.image(self.image_map(attrs['src']), + x, y, w, h, link=self.href) + self.pdf.set_x(x+w) + self.pdf.set_y(y+h) + if tag=='b' or tag=='i' or tag=='u': + self.set_style(tag, True) + if tag=='center': + self.align = 'Center' + + def handle_endtag(self, tag): + #Closing tag + if DEBUG: print "ENDTAG", tag + if tag=='h1' or tag=='h2' or tag=='h3' or tag=='h4': + self.pdf.ln(6) + self.set_font() + self.set_style() + self.align = None + if tag=='pre': + self.pdf.set_font(self.font or 'Times','',12) + self.pdf.set_font_size(12) + self.pre=False + if tag=='blockquote': + self.set_text_color(0,0,0) + self.pdf.ln(3) + if tag=='strong': + tag='b' + if tag=='em': + tag='i' + if tag=='b' or tag=='i' or tag=='u': + self.set_style(tag, False) + if tag=='a': + self.href='' + if tag=='p': + self.align='' + if tag in ('ul', 'ol'): + self.indent-=1 + self.bullet.pop() + if tag=='table': + if not self.tfooter_out: + self.output_table_footer() + self.table = None + self.th = False + self.theader = None + self.tfooter = None + self.pdf.ln() + if tag=='thead': + self.thead = None + if tag=='tfoot': + self.tfoot = None + if tag=='tbody': + # draw a line separator between table bodies + self.pdf.set_x(self.table_offset) + self.output_table_sep() + if tag=='tr': + h = self.table_h + if self.tfoot is None: + self.pdf.ln(h) + self.tr = None + if tag=='td' or tag=='th': + if self.th: + if DEBUG: print "revert style" + self.set_style('B', False) # revert style + self.table_col_index += int(self.td.get('colspan','1')) + self.td = None + self.th = False + if tag=='font': + if self.color: + self.pdf.set_text_color(0,0,0) + self.color = None + if self.font: + self.SetFont('Times','',12) + self.font = None + if tag=='center': + self.align = None + + def set_font(self, face=None, size=None): + if face: + self.font_face = face + if size: + self.font_size = size + self.h = size / 72.0*25.4 + if DEBUG: print "H", self.h + self.pdf.set_font(self.font_face or 'times','',12) + self.pdf.set_font_size(self.font_size or 12) + self.set_style('u', False) + self.set_style('b', False) + self.set_style('i', False) + self.set_text_color() + + def set_style(self, tag=None, enable=None): + #Modify style and select corresponding font + if tag: + t = self.style.get(tag.lower()) + self.style[tag.lower()] = enable + style='' + for s in ('b','i','u'): + if self.style.get(s): + style+=s + if DEBUG: print "SET_FONT_STYLE", style + self.pdf.set_font('',style) + + def set_text_color(self, r=None, g=0, b=0): + if r is None: + self.pdf.set_text_color(self.r,self.g,self.b) + else: + self.pdf.set_text_color(r, g, b) + self.r = r + self.g = g + self.b = b + + def put_link(self, url, txt): + #Put a hyperlink + self.set_text_color(0,0,255) + self.set_style('u', True) + self.pdf.write(5,txt,url) + self.set_style('u', False) + self.set_text_color(0) + + def put_line(self): + self.pdf.ln(2) + self.pdf.line(self.pdf.get_x(),self.pdf.get_y(),self.pdf.get_x()+187,self.pdf.get_y()) + self.pdf.ln(3) + +class HTMLMixin(): + def write_html(self, text, image_map=lambda x:x): + "Parse HTML and convert it to PDF" + h2p = HTML2FPDF(self,image_map=image_map) + h2p.feed(text) + +if __name__=='__main__': + html=""" +<H1 align="center">html2fpdf</H1> +<h2>Basic usage</h2> +<p>You can now easily print text mixing different +styles : <B>bold</B>, <I>italic</I>, <U>underlined</U>, or +<B><I><U>all at once</U></I></B>!<BR>You can also insert links +on text, such as <A HREF="http://www.fpdf.org">www.fpdf.org</A>, +or on an image: click on the logo.<br> +<center> +<A HREF="http://www.fpdf.org"><img src="tutorial/logo.png" width="104" height="71"></A> +</center> +<h3>Sample List</h3> +<ul><li>option 1</li> +<ol><li>option 2</li></ol> +<li>option 3</li></ul> + +<table border="0" align="center" width="50%"> +<thead><tr><th width="30%">Header 1</th><th width="70%">header 2</th></tr></thead> +<tbody> +<tr><td>cell 1</td><td>cell 2</td></tr> +<tr><td>cell 2</td><td>cell 3</td></tr> +</tbody> +</table> + + +<table border="1"> +<thead><tr bgcolor="#A0A0A0"><th width="30%">Header 1</th><th width="70%">header 2</th></tr></thead> +<tfoot><tr bgcolor="#E0E0E0"><td>footer 1</td><td>footer 2</td></tr></tfoot> +<tbody> +<tr><td>cell 1</td><td>cell 2</td></tr> +<tr> +<td width="30%">cell 1</td><td width="70%" bgcolor="#D0D0FF" align='right'>cell 2</td> +</tr> +</tbody> +<tbody><tr><td colspan="2">cell spanned</td></tr></tbody> +<tbody> +""" + """<tr bgcolor="#F0F0F0"> +<td>cell 3</td><td>cell 4</td> +</tr><tr bgcolor="#FFFFFF"> +<td>cell 5</td><td>cell 6</td> +</tr>""" * 200 + """ +</tbody> +</table> +""" + + class MyFPDF(FPDF, HTMLMixin): + def header(self): + self.image('tutorial/logo_pb.png',10,8,33) + self.set_font('Arial','B',15) + self.cell(80) + self.cell(30,10,'Title',1,0,'C') + self.ln(20) + + def footer(self): + self.set_y(-15) + self.set_font('Arial','I',8) + txt = 'Page %s of %s' % (self.page_no(), self.alias_nb_pages()) + self.cell(0,10,txt,0,0,'C') + + pdf=MyFPDF() + #First page + pdf.add_page() + pdf.write_html(html) + pdf.output('html.pdf','F') + + import os + os.system("evince html.pdf") + ADDED gluon/contrib/pyfpdf/template.py Index: gluon/contrib/pyfpdf/template.py ================================================================== --- gluon/contrib/pyfpdf/template.py +++ gluon/contrib/pyfpdf/template.py @@ -0,0 +1,277 @@ +# -*- coding: iso-8859-1 -*- + +"PDF Template Helper for FPDF.py" + +__author__ = "Mariano Reingart <reingart@gmail.com>" +__copyright__ = "Copyright (C) 2010 Mariano Reingart" +__license__ = "LGPL 3.0" + +import sys,os,csv +from fpdf import FPDF + +def rgb(col): + return (col // 65536), (col // 256 % 256), (col% 256) + +class Template: + def __init__(self, infile=None, elements=None, format='A4', orientation='portrait', + title='', author='', subject='', creator='', keywords=''): + if elements: + self.elements = dict([(v['name'].lower(),v) for v in elements]) + self.handlers = {'T': self.text, 'L': self.line, 'I': self.image, + 'B': self.rect, 'BC': self.barcode, } + self.pg_no = 0 + self.texts = {} + pdf = self.pdf = FPDF(format=format,orientation=orientation, unit="mm") + pdf.set_title(title) + pdf.set_author(author) + pdf.set_creator(creator) + pdf.set_subject(subject) + pdf.set_keywords(keywords) + + def parse_csv(self, infile, delimiter=",", decimal_sep="."): + "Parse template format csv file and create elements dict" + keys = ('name','type','x1','y1','x2','y2','font','size', + 'bold','italic','underline','foreground','background', + 'align','text','priority') + self.elements = {} + f = open(infile, 'rb') + try: + for row in csv.reader(f, delimiter=delimiter): + kargs = {} + for i,v in enumerate(row): + if not v.startswith("'") and decimal_sep!=".": + v = v.replace(decimal_sep,".") + else: + v = v + if v=='': + v = None + else: + v = eval(v.strip()) + kargs[keys[i]] = v + self.elements[kargs['name'].lower()] = kargs + finally: + f.close() + + def add_page(self): + self.pg_no += 1 + self.texts[self.pg_no] = {} + + def __setitem__(self, name, value): + if name.lower() in self.elements: + if isinstance(value,unicode): + value = value.encode("latin1","ignore") + else: + value = str(value) + self.texts[self.pg_no][name.lower()] = value + + # setitem shortcut (may be further extended) + set = __setitem__ + + def __getitem__(self, name): + if name.lower() in self.elements: + return self.texts[self.pg_no].get(name.lower(), self.elements[name.lower()]['text']) + + def split_multicell(self, text, element_name): + "Divide (\n) a string using a given element width" + pdf = self.pdf + element = self.elements[element_name.lower()] + style = "" + if element['bold']: style += "B" + if element['italic']: style += "I" + if element['underline']: style += "U" + pdf.set_font(element['font'],style,element['size']) + align = {'L':'L','R':'R','I':'L','D':'R','C':'C','':''}.get(element['align']) # D/I in spanish + if isinstance(text, unicode): + text = text.encode("latin1","ignore") + else: + text = str(text) + return pdf.multi_cell(w=element['x2']-element['x1'], + h=element['y2']-element['y1'], + txt=text,align=align,split_only=True) + + def render(self, outfile, dest="F"): + pdf = self.pdf + for pg in range(1, self.pg_no+1): + pdf.add_page() + pdf.set_font('Arial','B',16) + pdf.set_auto_page_break(False,margin=0) + + for element in sorted(self.elements.values(),key=lambda x: x['priority']): + #print "dib",element['type'], element['name'], element['x1'], element['y1'], element['x2'], element['y2'] + element = element.copy() + element['text'] = self.texts[pg].get(element['name'].lower(), element['text']) + if 'rotate' in element: + pdf.rotate(element['rotate'], element['x1'], element['y1']) + self.handlers[element['type'].upper()](pdf, **element) + if 'rotate' in element: + pdf.rotate(0) + + return pdf.output(outfile, dest) + + def text(self, pdf, x1=0, y1=0, x2=0, y2=0, text='', font="arial", size=10, + bold=False, italic=False, underline=False, align="", + foreground=0, backgroud=65535, + *args, **kwargs): + if text: + if pdf.text_color!=rgb(foreground): + pdf.set_text_color(*rgb(foreground)) + if pdf.fill_color!=rgb(backgroud): + pdf.set_fill_color(*rgb(backgroud)) + + font = font.strip().lower() + if font == 'arial black': + font = 'arial' + style = "" + for tag in 'B', 'I', 'U': + if (text.startswith("<%s>" % tag) and text.endswith("</%s>" %tag)): + text = text[3:-4] + style += tag + if bold: style += "B" + if italic: style += "I" + if underline: style += "U" + align = {'L':'L','R':'R','I':'L','D':'R','C':'C','':''}.get(align) # D/I in spanish + pdf.set_font(font,style,size) + ##m_k = 72 / 2.54 + ##h = (size/m_k) + pdf.set_xy(x1,y1) + pdf.cell(w=x2-x1,h=y2-y1,txt=text,border=0,ln=0,align=align) + #pdf.Text(x=x1,y=y1,txt=text) + + def line(self, pdf, x1=0, y1=0, x2=0, y2=0, size=0, foreground=0, *args, **kwargs): + if pdf.draw_color!=rgb(foreground): + #print "SetDrawColor", hex(foreground) + pdf.set_draw_color(*rgb(foreground)) + #print "SetLineWidth", size + pdf.set_line_width(size) + pdf.line(x1, y1, x2, y2) + + def rect(self, pdf, x1=0, y1=0, x2=0, y2=0, size=0, foreground=0, backgroud=65535, *args, **kwargs): + if pdf.draw_color!=rgb(foreground): + pdf.set_draw_color(*rgb(foreground)) + if pdf.fill_color!=rgb(backgroud): + pdf.set_fill_color(*rgb(backgroud)) + pdf.set_line_width(size) + pdf.rect(x1, y1, x2-x1, y2-y1) + + def image(self, pdf, x1=0, y1=0, x2=0, y2=0, text='', *args,**kwargs): + pdf.image(text,x1,y1,w=x2-x1,h=y2-y1,type='',link='') + + def barcode(self, pdf, x1=0, y1=0, x2=0, y2=0, text='', font="arial", size=1, + foreground=0, *args, **kwargs): + if pdf.draw_color!=rgb(foreground): + pdf.set_draw_color(*rgb(foreground)) + font = font.lower().strip() + if font == 'interleaved 2of5 nt': + pdf.interleaved2of5(text,x1,y1,w=size,h=y2-y1) + + +if __name__ == "__main__": + + # generate sample invoice (according Argentina's regulations) + + import random + from decimal import Decimal + + f = Template(format="A4", + title="Sample Invoice", author="Sample Company", + subject="Sample Customer", keywords="Electronic TAX Invoice") + f.parse_csv(infile="invoice.csv", delimiter=";", decimal_sep=",") + + detail = "Lorem ipsum dolor sit amet, consectetur. " * 30 + items = [] + for i in range(1, 30): + ds = "Sample product %s" % i + qty = random.randint(1,10) + price = round(random.random()*100,3) + code = "%s%s%02d" % (chr(random.randint(65,90)), chr(random.randint(65,90)),i) + items.append(dict(code=code, unit='u', + qty=qty, price=price, + amount=qty*price, + ds="%s: %s" % (i,ds))) + + # divide and count lines + lines = 0 + li_items = [] + for it in items: + qty = it['qty'] + code = it['code'] + unit = it['unit'] + for ds in f.split_multicell(it['ds'], 'item_description01'): + # add item description line (without price nor amount) + li_items.append(dict(code=code, ds=ds, qty=qty, unit=unit, price=None, amount=None)) + # clean qty and code (show only at first) + unit = qty = code = None + # set last item line price and amount + li_items[-1].update(amount = it['amount'], + price = it['price']) + + obs="\n<U>Detail:</U>\n\n" + detail + for ds in f.split_multicell(obs, 'item_description01'): + li_items.append(dict(code=code, ds=ds, qty=qty, unit=unit, price=None, amount=None)) + + # calculate pages: + lines = len(li_items) + max_lines_per_page = 24 + pages = lines / (max_lines_per_page - 1) + if lines % (max_lines_per_page - 1): pages = pages + 1 + + # completo campos y hojas + for page in range(1, pages+1): + f.add_page() + f['page'] = 'Page %s of %s' % (page, pages) + if pages>1 and page<pages: + s = 'Continues on page %s' % (page+1) + else: + s = '' + f['item_description%02d' % (max_lines_per_page+1)] = s + + f["company_name"] = "Sample Company" + f["company_logo"] = "tutorial/logo.png" + f["company_header1"] = "Some Address - somewhere -" + f["company_header2"] = "http://www.example.com" + f["company_footer1"] = "Tax Code ..." + f["company_footer2"] = "Tax/VAT ID ..." + f['number'] = '0001-00001234' + f['issue_date'] = '2010-09-10' + f['due_date'] = '2099-09-10' + f['customer_name'] = "Sample Client" + f['customer_address'] = "Siempreviva 1234" + + # print line item... + li = 0 + k = 0 + total = Decimal("0.00") + for it in li_items: + k = k + 1 + if k > page * (max_lines_per_page - 1): + break + if it['amount']: + total += Decimal("%.6f" % it['amount']) + if k > (page - 1) * (max_lines_per_page - 1): + li += 1 + if it['qty'] is not None: + f['item_quantity%02d' % li] = it['qty'] + if it['code'] is not None: + f['item_code%02d' % li] = it['code'] + if it['unit'] is not None: + f['item_unit%02d' % li] = it['unit'] + f['item_description%02d' % li] = it['ds'] + if it['price'] is not None: + f['item_price%02d' % li] = "%0.3f" % it['price'] + if it['amount'] is not None: + f['item_amount%02d' % li] = "%0.2f" % it['amount'] + + if pages == page: + f['net'] = "%0.2f" % (total/Decimal("1.21")) + f['vat'] = "%0.2f" % (total*(1-1/Decimal("1.21"))) + f['total_label'] = 'Total:' + else: + f['total_label'] = 'SubTotal:' + f['total'] = "%0.2f" % total + + f.render("./invoice.pdf") + if sys.platform.startswith("linux"): + os.system("evince ./invoice.pdf") + else: + os.system("./invoice.pdf") + ADDED gluon/contrib/pymysql/LICENSE Index: gluon/contrib/pymysql/LICENSE ================================================================== --- gluon/contrib/pymysql/LICENSE +++ gluon/contrib/pymysql/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2010 PyMySQL contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. ADDED gluon/contrib/pymysql/README Index: gluon/contrib/pymysql/README ================================================================== --- gluon/contrib/pymysql/README +++ gluon/contrib/pymysql/README @@ -0,0 +1,37 @@ +==================== +PyMySQL Installation +==================== + +.. contents:: +.. + This package contains a pure-Python MySQL client library. + Documentation on the MySQL client/server protocol can be found here: + http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol + If you would like to run the test suite, create a ~/.my.cnf file and + a database called "test_pymysql". The goal of pymysql is to be a drop-in + replacement for MySQLdb and work on CPython 2.3+, Jython, IronPython, PyPy + and Python 3. We test for compatibility by simply changing the import + statements in the Django MySQL backend and running its unit tests as well + as running it against the MySQLdb and myconnpy unit tests. + +Requirements +------------- + ++ Python 2.4 or higher + + * http://www.python.org/ + + * 2.6 is the primary test environment. + +* MySQL 4.1 or higher + + * protocol41 support, experimental 4.0 support + +Installation +------------ + +# easy_install pymysql +# ... or ... +# python setup.py install + + ADDED gluon/contrib/pymysql/__init__.py Index: gluon/contrib/pymysql/__init__.py ================================================================== --- gluon/contrib/pymysql/__init__.py +++ gluon/contrib/pymysql/__init__.py @@ -0,0 +1,131 @@ +''' +PyMySQL: A pure-Python drop-in replacement for MySQLdb. + +Copyright (c) 2010 PyMySQL contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +''' + +VERSION = (0, 4, None) + +from constants import FIELD_TYPE +from converters import escape_dict, escape_sequence, escape_string +from err import Warning, Error, InterfaceError, DataError, \ + DatabaseError, OperationalError, IntegrityError, InternalError, \ + NotSupportedError, ProgrammingError +from times import Date, Time, Timestamp, \ + DateFromTicks, TimeFromTicks, TimestampFromTicks + +import sys + +try: + frozenset +except NameError: + from sets import ImmutableSet as frozenset + try: + from sets import BaseSet as set + except ImportError: + from sets import Set as set + +threadsafety = 1 +apilevel = "2.0" +paramstyle = "format" + +class DBAPISet(frozenset): + + + def __ne__(self, other): + if isinstance(other, set): + return super(DBAPISet, self).__ne__(self, other) + else: + return other not in self + + def __eq__(self, other): + if isinstance(other, frozenset): + return frozenset.__eq__(self, other) + else: + return other in self + + def __hash__(self): + return frozenset.__hash__(self) + + +STRING = DBAPISet([FIELD_TYPE.ENUM, FIELD_TYPE.STRING, + FIELD_TYPE.VAR_STRING]) +BINARY = DBAPISet([FIELD_TYPE.BLOB, FIELD_TYPE.LONG_BLOB, + FIELD_TYPE.MEDIUM_BLOB, FIELD_TYPE.TINY_BLOB]) +NUMBER = DBAPISet([FIELD_TYPE.DECIMAL, FIELD_TYPE.DOUBLE, FIELD_TYPE.FLOAT, + FIELD_TYPE.INT24, FIELD_TYPE.LONG, FIELD_TYPE.LONGLONG, + FIELD_TYPE.TINY, FIELD_TYPE.YEAR]) +DATE = DBAPISet([FIELD_TYPE.DATE, FIELD_TYPE.NEWDATE]) +TIME = DBAPISet([FIELD_TYPE.TIME]) +TIMESTAMP = DBAPISet([FIELD_TYPE.TIMESTAMP, FIELD_TYPE.DATETIME]) +DATETIME = TIMESTAMP +ROWID = DBAPISet() + +def Binary(x): + """Return x as a binary type.""" + return str(x) + +def Connect(*args, **kwargs): + """ + Connect to the database; see connections.Connection.__init__() for + more information. + """ + from connections import Connection + return Connection(*args, **kwargs) + +def get_client_info(): # for MySQLdb compatibility + return '%s.%s.%s' % VERSION + +connect = Connection = Connect + +# we include a doctored version_info here for MySQLdb compatibility +version_info = (1,2,2,"final",0) + +NULL = "NULL" + +__version__ = get_client_info() + +def thread_safe(): + return True # match MySQLdb.thread_safe() + +def install_as_MySQLdb(): + """ + After this function is called, any application that imports MySQLdb or + _mysql will unwittingly actually use + """ + sys.modules["MySQLdb"] = sys.modules["_mysql"] = sys.modules["pymysql"] + +__all__ = [ + 'BINARY', 'Binary', 'Connect', 'Connection', 'DATE', 'Date', + 'Time', 'Timestamp', 'DateFromTicks', 'TimeFromTicks', 'TimestampFromTicks', + 'DataError', 'DatabaseError', 'Error', 'FIELD_TYPE', 'IntegrityError', + 'InterfaceError', 'InternalError', 'MySQLError', 'NULL', 'NUMBER', + 'NotSupportedError', 'DBAPISet', 'OperationalError', 'ProgrammingError', + 'ROWID', 'STRING', 'TIME', 'TIMESTAMP', 'Warning', 'apilevel', 'connect', + 'connections', 'constants', 'converters', 'cursors', 'debug', 'escape', + 'escape_dict', 'escape_sequence', 'escape_string', 'get_client_info', + 'paramstyle', 'string_literal', 'threadsafety', 'version_info', + + "install_as_MySQLdb", + + "NULL","__version__", + ] ADDED gluon/contrib/pymysql/charset.py Index: gluon/contrib/pymysql/charset.py ================================================================== --- gluon/contrib/pymysql/charset.py +++ gluon/contrib/pymysql/charset.py @@ -0,0 +1,174 @@ +MBLENGTH = { + 8:1, + 33:3, + 88:2, + 91:2 + } + +class Charset: + def __init__(self, id, name, collation, is_default): + self.id, self.name, self.collation = id, name, collation + self.is_default = is_default == 'Yes' + +class Charsets: + def __init__(self): + self._by_id = {} + + def add(self, c): + self._by_id[c.id] = c + + def by_id(self, id): + return self._by_id[id] + + def by_name(self, name): + for c in self._by_id.values(): + if c.name == name and c.is_default: + return c + +_charsets = Charsets() +""" +Generated with: + +mysql -N -s -e "select id, character_set_name, collation_name, is_default +from information_schema.collations order by id;" | python -c "import sys +for l in sys.stdin.readlines(): + id, name, collation, is_default = l.split(chr(9)) + print '_charsets.add(Charset(%s, \'%s\', \'%s\', \'%s\'))' \ + % (id, name, collation, is_default.strip()) +" + +""" +_charsets.add(Charset(1, 'big5', 'big5_chinese_ci', 'Yes')) +_charsets.add(Charset(2, 'latin2', 'latin2_czech_cs', '')) +_charsets.add(Charset(3, 'dec8', 'dec8_swedish_ci', 'Yes')) +_charsets.add(Charset(4, 'cp850', 'cp850_general_ci', 'Yes')) +_charsets.add(Charset(5, 'latin1', 'latin1_german1_ci', '')) +_charsets.add(Charset(6, 'hp8', 'hp8_english_ci', 'Yes')) +_charsets.add(Charset(7, 'koi8r', 'koi8r_general_ci', 'Yes')) +_charsets.add(Charset(8, 'latin1', 'latin1_swedish_ci', 'Yes')) +_charsets.add(Charset(9, 'latin2', 'latin2_general_ci', 'Yes')) +_charsets.add(Charset(10, 'swe7', 'swe7_swedish_ci', 'Yes')) +_charsets.add(Charset(11, 'ascii', 'ascii_general_ci', 'Yes')) +_charsets.add(Charset(12, 'ujis', 'ujis_japanese_ci', 'Yes')) +_charsets.add(Charset(13, 'sjis', 'sjis_japanese_ci', 'Yes')) +_charsets.add(Charset(14, 'cp1251', 'cp1251_bulgarian_ci', '')) +_charsets.add(Charset(15, 'latin1', 'latin1_danish_ci', '')) +_charsets.add(Charset(16, 'hebrew', 'hebrew_general_ci', 'Yes')) +_charsets.add(Charset(18, 'tis620', 'tis620_thai_ci', 'Yes')) +_charsets.add(Charset(19, 'euckr', 'euckr_korean_ci', 'Yes')) +_charsets.add(Charset(20, 'latin7', 'latin7_estonian_cs', '')) +_charsets.add(Charset(21, 'latin2', 'latin2_hungarian_ci', '')) +_charsets.add(Charset(22, 'koi8u', 'koi8u_general_ci', 'Yes')) +_charsets.add(Charset(23, 'cp1251', 'cp1251_ukrainian_ci', '')) +_charsets.add(Charset(24, 'gb2312', 'gb2312_chinese_ci', 'Yes')) +_charsets.add(Charset(25, 'greek', 'greek_general_ci', 'Yes')) +_charsets.add(Charset(26, 'cp1250', 'cp1250_general_ci', 'Yes')) +_charsets.add(Charset(27, 'latin2', 'latin2_croatian_ci', '')) +_charsets.add(Charset(28, 'gbk', 'gbk_chinese_ci', 'Yes')) +_charsets.add(Charset(29, 'cp1257', 'cp1257_lithuanian_ci', '')) +_charsets.add(Charset(30, 'latin5', 'latin5_turkish_ci', 'Yes')) +_charsets.add(Charset(31, 'latin1', 'latin1_german2_ci', '')) +_charsets.add(Charset(32, 'armscii8', 'armscii8_general_ci', 'Yes')) +_charsets.add(Charset(33, 'utf8', 'utf8_general_ci', 'Yes')) +_charsets.add(Charset(34, 'cp1250', 'cp1250_czech_cs', '')) +_charsets.add(Charset(35, 'ucs2', 'ucs2_general_ci', 'Yes')) +_charsets.add(Charset(36, 'cp866', 'cp866_general_ci', 'Yes')) +_charsets.add(Charset(37, 'keybcs2', 'keybcs2_general_ci', 'Yes')) +_charsets.add(Charset(38, 'macce', 'macce_general_ci', 'Yes')) +_charsets.add(Charset(39, 'macroman', 'macroman_general_ci', 'Yes')) +_charsets.add(Charset(40, 'cp852', 'cp852_general_ci', 'Yes')) +_charsets.add(Charset(41, 'latin7', 'latin7_general_ci', 'Yes')) +_charsets.add(Charset(42, 'latin7', 'latin7_general_cs', '')) +_charsets.add(Charset(43, 'macce', 'macce_bin', '')) +_charsets.add(Charset(44, 'cp1250', 'cp1250_croatian_ci', '')) +_charsets.add(Charset(47, 'latin1', 'latin1_bin', '')) +_charsets.add(Charset(48, 'latin1', 'latin1_general_ci', '')) +_charsets.add(Charset(49, 'latin1', 'latin1_general_cs', '')) +_charsets.add(Charset(50, 'cp1251', 'cp1251_bin', '')) +_charsets.add(Charset(51, 'cp1251', 'cp1251_general_ci', 'Yes')) +_charsets.add(Charset(52, 'cp1251', 'cp1251_general_cs', '')) +_charsets.add(Charset(53, 'macroman', 'macroman_bin', '')) +_charsets.add(Charset(57, 'cp1256', 'cp1256_general_ci', 'Yes')) +_charsets.add(Charset(58, 'cp1257', 'cp1257_bin', '')) +_charsets.add(Charset(59, 'cp1257', 'cp1257_general_ci', 'Yes')) +_charsets.add(Charset(63, 'binary', 'binary', 'Yes')) +_charsets.add(Charset(64, 'armscii8', 'armscii8_bin', '')) +_charsets.add(Charset(65, 'ascii', 'ascii_bin', '')) +_charsets.add(Charset(66, 'cp1250', 'cp1250_bin', '')) +_charsets.add(Charset(67, 'cp1256', 'cp1256_bin', '')) +_charsets.add(Charset(68, 'cp866', 'cp866_bin', '')) +_charsets.add(Charset(69, 'dec8', 'dec8_bin', '')) +_charsets.add(Charset(70, 'greek', 'greek_bin', '')) +_charsets.add(Charset(71, 'hebrew', 'hebrew_bin', '')) +_charsets.add(Charset(72, 'hp8', 'hp8_bin', '')) +_charsets.add(Charset(73, 'keybcs2', 'keybcs2_bin', '')) +_charsets.add(Charset(74, 'koi8r', 'koi8r_bin', '')) +_charsets.add(Charset(75, 'koi8u', 'koi8u_bin', '')) +_charsets.add(Charset(77, 'latin2', 'latin2_bin', '')) +_charsets.add(Charset(78, 'latin5', 'latin5_bin', '')) +_charsets.add(Charset(79, 'latin7', 'latin7_bin', '')) +_charsets.add(Charset(80, 'cp850', 'cp850_bin', '')) +_charsets.add(Charset(81, 'cp852', 'cp852_bin', '')) +_charsets.add(Charset(82, 'swe7', 'swe7_bin', '')) +_charsets.add(Charset(83, 'utf8', 'utf8_bin', '')) +_charsets.add(Charset(84, 'big5', 'big5_bin', '')) +_charsets.add(Charset(85, 'euckr', 'euckr_bin', '')) +_charsets.add(Charset(86, 'gb2312', 'gb2312_bin', '')) +_charsets.add(Charset(87, 'gbk', 'gbk_bin', '')) +_charsets.add(Charset(88, 'sjis', 'sjis_bin', '')) +_charsets.add(Charset(89, 'tis620', 'tis620_bin', '')) +_charsets.add(Charset(90, 'ucs2', 'ucs2_bin', '')) +_charsets.add(Charset(91, 'ujis', 'ujis_bin', '')) +_charsets.add(Charset(92, 'geostd8', 'geostd8_general_ci', 'Yes')) +_charsets.add(Charset(93, 'geostd8', 'geostd8_bin', '')) +_charsets.add(Charset(94, 'latin1', 'latin1_spanish_ci', '')) +_charsets.add(Charset(95, 'cp932', 'cp932_japanese_ci', 'Yes')) +_charsets.add(Charset(96, 'cp932', 'cp932_bin', '')) +_charsets.add(Charset(97, 'eucjpms', 'eucjpms_japanese_ci', 'Yes')) +_charsets.add(Charset(98, 'eucjpms', 'eucjpms_bin', '')) +_charsets.add(Charset(99, 'cp1250', 'cp1250_polish_ci', '')) +_charsets.add(Charset(128, 'ucs2', 'ucs2_unicode_ci', '')) +_charsets.add(Charset(129, 'ucs2', 'ucs2_icelandic_ci', '')) +_charsets.add(Charset(130, 'ucs2', 'ucs2_latvian_ci', '')) +_charsets.add(Charset(131, 'ucs2', 'ucs2_romanian_ci', '')) +_charsets.add(Charset(132, 'ucs2', 'ucs2_slovenian_ci', '')) +_charsets.add(Charset(133, 'ucs2', 'ucs2_polish_ci', '')) +_charsets.add(Charset(134, 'ucs2', 'ucs2_estonian_ci', '')) +_charsets.add(Charset(135, 'ucs2', 'ucs2_spanish_ci', '')) +_charsets.add(Charset(136, 'ucs2', 'ucs2_swedish_ci', '')) +_charsets.add(Charset(137, 'ucs2', 'ucs2_turkish_ci', '')) +_charsets.add(Charset(138, 'ucs2', 'ucs2_czech_ci', '')) +_charsets.add(Charset(139, 'ucs2', 'ucs2_danish_ci', '')) +_charsets.add(Charset(140, 'ucs2', 'ucs2_lithuanian_ci', '')) +_charsets.add(Charset(141, 'ucs2', 'ucs2_slovak_ci', '')) +_charsets.add(Charset(142, 'ucs2', 'ucs2_spanish2_ci', '')) +_charsets.add(Charset(143, 'ucs2', 'ucs2_roman_ci', '')) +_charsets.add(Charset(144, 'ucs2', 'ucs2_persian_ci', '')) +_charsets.add(Charset(145, 'ucs2', 'ucs2_esperanto_ci', '')) +_charsets.add(Charset(146, 'ucs2', 'ucs2_hungarian_ci', '')) +_charsets.add(Charset(192, 'utf8', 'utf8_unicode_ci', '')) +_charsets.add(Charset(193, 'utf8', 'utf8_icelandic_ci', '')) +_charsets.add(Charset(194, 'utf8', 'utf8_latvian_ci', '')) +_charsets.add(Charset(195, 'utf8', 'utf8_romanian_ci', '')) +_charsets.add(Charset(196, 'utf8', 'utf8_slovenian_ci', '')) +_charsets.add(Charset(197, 'utf8', 'utf8_polish_ci', '')) +_charsets.add(Charset(198, 'utf8', 'utf8_estonian_ci', '')) +_charsets.add(Charset(199, 'utf8', 'utf8_spanish_ci', '')) +_charsets.add(Charset(200, 'utf8', 'utf8_swedish_ci', '')) +_charsets.add(Charset(201, 'utf8', 'utf8_turkish_ci', '')) +_charsets.add(Charset(202, 'utf8', 'utf8_czech_ci', '')) +_charsets.add(Charset(203, 'utf8', 'utf8_danish_ci', '')) +_charsets.add(Charset(204, 'utf8', 'utf8_lithuanian_ci', '')) +_charsets.add(Charset(205, 'utf8', 'utf8_slovak_ci', '')) +_charsets.add(Charset(206, 'utf8', 'utf8_spanish2_ci', '')) +_charsets.add(Charset(207, 'utf8', 'utf8_roman_ci', '')) +_charsets.add(Charset(208, 'utf8', 'utf8_persian_ci', '')) +_charsets.add(Charset(209, 'utf8', 'utf8_esperanto_ci', '')) +_charsets.add(Charset(210, 'utf8', 'utf8_hungarian_ci', '')) + +def charset_by_name(name): + return _charsets.by_name(name) + +def charset_by_id(id): + return _charsets.by_id(id) + ADDED gluon/contrib/pymysql/connections.py Index: gluon/contrib/pymysql/connections.py ================================================================== --- gluon/contrib/pymysql/connections.py +++ gluon/contrib/pymysql/connections.py @@ -0,0 +1,932 @@ +# Python implementation of the MySQL client-server protocol +# http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol + +try: + import hashlib + sha_new = lambda *args, **kwargs: hashlib.new("sha1", *args, **kwargs) +except ImportError: + import sha + sha_new = sha.new + +import socket +try: + import ssl + SSL_ENABLED = True +except ImportError: + SSL_ENABLED = False + +import struct +import sys +import os +import ConfigParser + +try: + import cStringIO as StringIO +except ImportError: + import StringIO + +from charset import MBLENGTH, charset_by_name, charset_by_id +from cursors import Cursor +from constants import FIELD_TYPE, FLAG +from constants import SERVER_STATUS +from constants.CLIENT import * +from constants.COMMAND import * +from util import join_bytes, byte2int, int2byte +from converters import escape_item, encoders, decoders +from err import raise_mysql_exception, Warning, Error, \ + InterfaceError, DataError, DatabaseError, OperationalError, \ + IntegrityError, InternalError, NotSupportedError, ProgrammingError + +DEBUG = False + +NULL_COLUMN = 251 +UNSIGNED_CHAR_COLUMN = 251 +UNSIGNED_SHORT_COLUMN = 252 +UNSIGNED_INT24_COLUMN = 253 +UNSIGNED_INT64_COLUMN = 254 +UNSIGNED_CHAR_LENGTH = 1 +UNSIGNED_SHORT_LENGTH = 2 +UNSIGNED_INT24_LENGTH = 3 +UNSIGNED_INT64_LENGTH = 8 + +DEFAULT_CHARSET = 'latin1' +MAX_PACKET_LENGTH = 256*256*256-1 + + +def dump_packet(data): + + def is_ascii(data): + if byte2int(data) >= 65 and byte2int(data) <= 122: #data.isalnum(): + return data + return '.' + print "packet length %d" % len(data) + print "method call[1]: %s" % sys._getframe(1).f_code.co_name + print "method call[2]: %s" % sys._getframe(2).f_code.co_name + print "method call[3]: %s" % sys._getframe(3).f_code.co_name + print "method call[4]: %s" % sys._getframe(4).f_code.co_name + print "method call[5]: %s" % sys._getframe(5).f_code.co_name + print "-" * 88 + dump_data = [data[i:i+16] for i in xrange(len(data)) if i%16 == 0] + for d in dump_data: + print ' '.join(map(lambda x:"%02X" % byte2int(x), d)) + \ + ' ' * (16 - len(d)) + ' ' * 2 + \ + ' '.join(map(lambda x:"%s" % is_ascii(x), d)) + print "-" * 88 + print "" + +def _scramble(password, message): + if password == None or len(password) == 0: + return int2byte(0) + if DEBUG: print 'password=' + password + stage1 = sha_new(password).digest() + stage2 = sha_new(stage1).digest() + s = sha_new() + s.update(message) + s.update(stage2) + result = s.digest() + return _my_crypt(result, stage1) + +def _my_crypt(message1, message2): + length = len(message1) + result = struct.pack('B', length) + for i in xrange(length): + x = (struct.unpack('B', message1[i:i+1])[0] ^ \ + struct.unpack('B', message2[i:i+1])[0]) + result += struct.pack('B', x) + return result + +# old_passwords support ported from libmysql/password.c +SCRAMBLE_LENGTH_323 = 8 + +class RandStruct_323(object): + def __init__(self, seed1, seed2): + self.max_value = 0x3FFFFFFFL + self.seed1 = seed1 % self.max_value + self.seed2 = seed2 % self.max_value + + def my_rnd(self): + self.seed1 = (self.seed1 * 3L + self.seed2) % self.max_value + self.seed2 = (self.seed1 + self.seed2 + 33L) % self.max_value + return float(self.seed1) / float(self.max_value) + +def _scramble_323(password, message): + hash_pass = _hash_password_323(password) + hash_message = _hash_password_323(message[:SCRAMBLE_LENGTH_323]) + hash_pass_n = struct.unpack(">LL", hash_pass) + hash_message_n = struct.unpack(">LL", hash_message) + + rand_st = RandStruct_323(hash_pass_n[0] ^ hash_message_n[0], + hash_pass_n[1] ^ hash_message_n[1]) + outbuf = StringIO.StringIO() + for _ in xrange(min(SCRAMBLE_LENGTH_323, len(message))): + outbuf.write(int2byte(int(rand_st.my_rnd() * 31) + 64)) + extra = int2byte(int(rand_st.my_rnd() * 31)) + out = outbuf.getvalue() + outbuf = StringIO.StringIO() + for c in out: + outbuf.write(int2byte(byte2int(c) ^ byte2int(extra))) + return outbuf.getvalue() + +def _hash_password_323(password): + nr = 1345345333L + add = 7L + nr2 = 0x12345671L + + for c in [byte2int(x) for x in password if x not in (' ', '\t')]: + nr^= (((nr & 63)+add)*c)+ (nr << 8) & 0xFFFFFFFF + nr2= (nr2 + ((nr2 << 8) ^ nr)) & 0xFFFFFFFF + add= (add + c) & 0xFFFFFFFF + + r1 = nr & ((1L << 31) - 1L) # kill sign bits + r2 = nr2 & ((1L << 31) - 1L) + + # pack + return struct.pack(">LL", r1, r2) + +def pack_int24(n): + return struct.pack('BBB', n&0xFF, (n>>8)&0xFF, (n>>16)&0xFF) + +def unpack_uint16(n): + return struct.unpack('<H', n[0:2])[0] + + +# TODO: stop using bit-shifting in these functions... +# TODO: rename to "uint" to make it clear they're unsigned... +def unpack_int24(n): + return struct.unpack('B',n[0])[0] + (struct.unpack('B', n[1])[0] << 8) +\ + (struct.unpack('B',n[2])[0] << 16) + +def unpack_int32(n): + return struct.unpack('B',n[0])[0] + (struct.unpack('B', n[1])[0] << 8) +\ + (struct.unpack('B',n[2])[0] << 16) + (struct.unpack('B', n[3])[0] << 24) + +def unpack_int64(n): + return struct.unpack('B',n[0])[0] + (struct.unpack('B', n[1])[0]<<8) +\ + (struct.unpack('B',n[2])[0] << 16) + (struct.unpack('B',n[3])[0]<<24)+\ + (struct.unpack('B',n[4])[0] << 32) + (struct.unpack('B',n[5])[0]<<40)+\ + (struct.unpack('B',n[6])[0] << 48) + (struct.unpack('B',n[7])[0]<<56) + +def defaulterrorhandler(connection, cursor, errorclass, errorvalue): + err = errorclass, errorvalue + if DEBUG: + raise + + if cursor: + cursor.messages.append(err) + else: + connection.messages.append(err) + del cursor + del connection + + if not issubclass(errorclass, Error): + raise Error(errorclass, errorvalue) + else: + raise errorclass, errorvalue + + +class MysqlPacket(object): + """Representation of a MySQL response packet. Reads in the packet + from the network socket, removes packet header and provides an interface + for reading/parsing the packet results.""" + + def __init__(self, socket): + self.__position = 0 + self.__recv_packet(socket) + del socket + + def __recv_packet(self, socket): + """Parse the packet header and read entire packet payload into buffer.""" + packet_header = socket.recv(4) + while len(packet_header) < 4: + d = socket.recv(4 - len(packet_header)) + if len(d) == 0: + raise OperationalError(2013, "Lost connection to MySQL server during query") + packet_header += d + + if DEBUG: dump_packet(packet_header) + packet_length_bin = packet_header[:3] + self.__packet_number = byte2int(packet_header[3]) + # TODO: check packet_num is correct (+1 from last packet) + + bin_length = packet_length_bin + int2byte(0) # pad little-endian number + bytes_to_read = struct.unpack('<I', bin_length)[0] + + payload_buff = [] # this is faster than cStringIO + while bytes_to_read > 0: + recv_data = socket.recv(bytes_to_read) + if len(recv_data) == 0: + raise OperationalError(2013, "Lost connection to MySQL server during query") + if DEBUG: dump_packet(recv_data) + payload_buff.append(recv_data) + bytes_to_read -= len(recv_data) + self.__data = join_bytes(payload_buff) + + def packet_number(self): return self.__packet_number + + def get_all_data(self): return self.__data + + def read(self, size): + """Read the first 'size' bytes in packet and advance cursor past them.""" + result = self.peek(size) + self.advance(size) + return result + + def read_all(self): + """Read all remaining data in the packet. + + (Subsequent read() or peek() will return errors.) + """ + result = self.__data[self.__position:] + self.__position = None # ensure no subsequent read() or peek() + return result + + def advance(self, length): + """Advance the cursor in data buffer 'length' bytes.""" + new_position = self.__position + length + if new_position < 0 or new_position > len(self.__data): + raise Exception('Invalid advance amount (%s) for cursor. ' + 'Position=%s' % (length, new_position)) + self.__position = new_position + + def rewind(self, position=0): + """Set the position of the data buffer cursor to 'position'.""" + if position < 0 or position > len(self.__data): + raise Exception("Invalid position to rewind cursor to: %s." % position) + self.__position = position + + def peek(self, size): + """Look at the first 'size' bytes in packet without moving cursor.""" + result = self.__data[self.__position:(self.__position+size)] + if len(result) != size: + error = ('Result length not requested length:\n' + 'Expected=%s. Actual=%s. Position: %s. Data Length: %s' + % (size, len(result), self.__position, len(self.__data))) + if DEBUG: + print error + self.dump() + raise AssertionError(error) + return result + + def get_bytes(self, position, length=1): + """Get 'length' bytes starting at 'position'. + + Position is start of payload (first four packet header bytes are not + included) starting at index '0'. + + No error checking is done. If requesting outside end of buffer + an empty string (or string shorter than 'length') may be returned! + """ + return self.__data[position:(position+length)] + + def read_length_coded_binary(self): + """Read a 'Length Coded Binary' number from the data buffer. + + Length coded numbers can be anywhere from 1 to 9 bytes depending + on the value of the first byte. + """ + c = byte2int(self.read(1)) + if c == NULL_COLUMN: + return None + if c < UNSIGNED_CHAR_COLUMN: + return c + elif c == UNSIGNED_SHORT_COLUMN: + return unpack_uint16(self.read(UNSIGNED_SHORT_LENGTH)) + elif c == UNSIGNED_INT24_COLUMN: + return unpack_int24(self.read(UNSIGNED_INT24_LENGTH)) + elif c == UNSIGNED_INT64_COLUMN: + # TODO: what was 'longlong'? confirm it wasn't used? + return unpack_int64(self.read(UNSIGNED_INT64_LENGTH)) + + def read_length_coded_string(self): + """Read a 'Length Coded String' from the data buffer. + + A 'Length Coded String' consists first of a length coded + (unsigned, positive) integer represented in 1-9 bytes followed by + that many bytes of binary data. (For example "cat" would be "3cat".) + """ + length = self.read_length_coded_binary() + if length is None: + return None + return self.read(length) + + def is_ok_packet(self): + return byte2int(self.get_bytes(0)) == 0 + + def is_eof_packet(self): + return byte2int(self.get_bytes(0)) == 254 # 'fe' + + def is_resultset_packet(self): + field_count = byte2int(self.get_bytes(0)) + return field_count >= 1 and field_count <= 250 + + def is_error_packet(self): + return byte2int(self.get_bytes(0)) == 255 + + def check_error(self): + if self.is_error_packet(): + self.rewind() + self.advance(1) # field_count == error (we already know that) + errno = unpack_uint16(self.read(2)) + if DEBUG: print "errno = %d" % errno + raise_mysql_exception(self.__data) + + def dump(self): + dump_packet(self.__data) + + +class FieldDescriptorPacket(MysqlPacket): + """A MysqlPacket that represents a specific column's metadata in the result. + + Parsing is automatically done and the results are exported via public + attributes on the class such as: db, table_name, name, length, type_code. + """ + + def __init__(self, *args): + MysqlPacket.__init__(self, *args) + self.__parse_field_descriptor() + + def __parse_field_descriptor(self): + """Parse the 'Field Descriptor' (Metadata) packet. + + This is compatible with MySQL 4.1+ (not compatible with MySQL 4.0). + """ + self.catalog = self.read_length_coded_string() + self.db = self.read_length_coded_string() + self.table_name = self.read_length_coded_string() + self.org_table = self.read_length_coded_string() + self.name = self.read_length_coded_string() + self.org_name = self.read_length_coded_string() + self.advance(1) # non-null filler + self.charsetnr = struct.unpack('<H', self.read(2))[0] + self.length = struct.unpack('<I', self.read(4))[0] + self.type_code = byte2int(self.read(1)) + self.flags = struct.unpack('<H', self.read(2))[0] + self.scale = byte2int(self.read(1)) # "decimals" + self.advance(2) # filler (always 0x00) + + # 'default' is a length coded binary and is still in the buffer? + # not used for normal result sets... + + def description(self): + """Provides a 7-item tuple compatible with the Python PEP249 DB Spec.""" + desc = [] + desc.append(self.name) + desc.append(self.type_code) + desc.append(None) # TODO: display_length; should this be self.length? + desc.append(self.get_column_length()) # 'internal_size' + desc.append(self.get_column_length()) # 'precision' # TODO: why!?!? + desc.append(self.scale) + + # 'null_ok' -- can this be True/False rather than 1/0? + # if so just do: desc.append(bool(self.flags % 2 == 0)) + if self.flags % 2 == 0: + desc.append(1) + else: + desc.append(0) + return tuple(desc) + + def get_column_length(self): + if self.type_code == FIELD_TYPE.VAR_STRING: + mblen = MBLENGTH.get(self.charsetnr, 1) + return self.length // mblen + return self.length + + def __str__(self): + return ('%s %s.%s.%s, type=%s' + % (self.__class__, self.db, self.table_name, self.name, + self.type_code)) + + +class Connection(object): + """ + Representation of a socket with a mysql server. + + The proper way to get an instance of this class is to call + connect().""" + errorhandler = defaulterrorhandler + + def __init__(self, host="localhost", user=None, passwd="", + db=None, port=3306, unix_socket=None, + charset='', sql_mode=None, + read_default_file=None, conv=decoders, use_unicode=None, + client_flag=0, cursorclass=Cursor, init_command=None, + connect_timeout=None, ssl=None, read_default_group=None, + compress=None, named_pipe=None): + """ + Establish a connection to the MySQL database. Accepts several + arguments: + + host: Host where the database server is located + user: Username to log in as + passwd: Password to use. + db: Database to use, None to not use a particular one. + port: MySQL port to use, default is usually OK. + unix_socket: Optionally, you can use a unix socket rather than TCP/IP. + charset: Charset you want to use. + sql_mode: Default SQL_MODE to use. + read_default_file: Specifies my.cnf file to read these parameters from under the [client] section. + conv: Decoders dictionary to use instead of the default one. This is used to provide custom marshalling of types. See converters. + use_unicode: Whether or not to default to unicode strings. This option defaults to true for Py3k. + client_flag: Custom flags to send to MySQL. Find potential values in constants.CLIENT. + cursorclass: Custom cursor class to use. + init_command: Initial SQL statement to run when connection is established. + connect_timeout: Timeout before throwing an exception when connecting. + ssl: A dict of arguments similar to mysql_ssl_set()'s parameters. For now the capath and cipher arguments are not supported. + read_default_group: Group to read from in the configuration file. + compress; Not supported + named_pipe: Not supported + """ + + if use_unicode is None and sys.version_info[0] > 2: + use_unicode = True + + if compress or named_pipe: + raise NotImplementedError, "compress and named_pipe arguments are not supported" + + if ssl and (ssl.has_key('capath') or ssl.has_key('cipher')): + raise NotImplementedError, 'ssl options capath and cipher are not supported' + + self.ssl = False + if ssl: + if not SSL_ENABLED: + raise NotImplementedError, "ssl module not found" + self.ssl = True + client_flag |= SSL + for k in ('key', 'cert', 'ca'): + v = None + if ssl.has_key(k): + v = ssl[k] + setattr(self, k, v) + + if read_default_group and not read_default_file: + if sys.platform.startswith("win"): + read_default_file = "c:\\my.ini" + else: + read_default_file = "/etc/my.cnf" + + if read_default_file: + if not read_default_group: + read_default_group = "client" + + cfg = ConfigParser.RawConfigParser() + cfg.read(os.path.expanduser(read_default_file)) + + def _config(key, default): + try: + return cfg.get(read_default_group,key) + except: + return default + + user = _config("user",user) + passwd = _config("password",passwd) + host = _config("host", host) + db = _config("db",db) + unix_socket = _config("socket",unix_socket) + port = _config("port", port) + charset = _config("default-character-set", charset) + + self.host = host + self.port = port + self.user = user + self.password = passwd + self.db = db + self.unix_socket = unix_socket + if charset: + self.charset = charset + self.use_unicode = True + else: + self.charset = DEFAULT_CHARSET + self.use_unicode = False + + if use_unicode: + self.use_unicode = use_unicode + + client_flag |= CAPABILITIES + client_flag |= MULTI_STATEMENTS + if self.db: + client_flag |= CONNECT_WITH_DB + self.client_flag = client_flag + + self.cursorclass = cursorclass + self.connect_timeout = connect_timeout + + self._connect() + + self.messages = [] + self.set_charset(charset) + self.encoders = encoders + self.decoders = conv + + self._affected_rows = 0 + self.host_info = "Not connected" + + self.autocommit(False) + + if sql_mode is not None: + c = self.cursor() + c.execute("SET sql_mode=%s", (sql_mode,)) + + self.commit() + + if init_command is not None: + c = self.cursor() + c.execute(init_command) + + self.commit() + + + def close(self): + ''' Send the quit message and close the socket ''' + send_data = struct.pack('<i',1) + int2byte(COM_QUIT) + self.socket.send(send_data) + self.socket.close() + self.socket = None + + def autocommit(self, value): + ''' Set whether or not to commit after every execute() ''' + try: + self._execute_command(COM_QUERY, "SET AUTOCOMMIT = %s" % \ + self.escape(value)) + self.read_packet() + except: + exc,value,tb = sys.exc_info() + self.errorhandler(None, exc, value) + + def commit(self): + ''' Commit changes to stable storage ''' + try: + self._execute_command(COM_QUERY, "COMMIT") + self.read_packet() + except: + exc,value,tb = sys.exc_info() + self.errorhandler(None, exc, value) + + def rollback(self): + ''' Roll back the current transaction ''' + try: + self._execute_command(COM_QUERY, "ROLLBACK") + self.read_packet() + except: + exc,value,tb = sys.exc_info() + self.errorhandler(None, exc, value) + + def escape(self, obj): + ''' Escape whatever value you pass to it ''' + return escape_item(obj, self.charset) + + def literal(self, obj): + ''' Alias for escape() ''' + return escape_item(obj, self.charset) + + def cursor(self): + ''' Create a new cursor to execute queries with ''' + return self.cursorclass(self) + + def __enter__(self): + ''' Context manager that returns a Cursor ''' + return self.cursor() + + def __exit__(self, exc, value, traceback): + ''' On successful exit, commit. On exception, rollback. ''' + if exc: + self.rollback() + else: + self.commit() + + # The following methods are INTERNAL USE ONLY (called from Cursor) + def query(self, sql): + self._execute_command(COM_QUERY, sql) + self._affected_rows = self._read_query_result() + return self._affected_rows + + def next_result(self): + self._affected_rows = self._read_query_result() + return self._affected_rows + + def affected_rows(self): + return self._affected_rows + + def kill(self, thread_id): + arg = struct.pack('<I', thread_id) + try: + self._execute_command(COM_PROCESS_KILL, arg) + except: + exc,value,tb = sys.exc_info() + self.errorhandler(None, exc, value) + return + pkt = self.read_packet() + return pkt.is_ok_packet() + + def ping(self, reconnect=True): + ''' Check if the server is alive ''' + try: + self._execute_command(COM_PING, "") + except: + if reconnect: + self._connect() + return self.ping(False) + else: + exc,value,tb = sys.exc_info() + self.errorhandler(None, exc, value) + return + + pkt = self.read_packet() + return pkt.is_ok_packet() + + def set_charset(self, charset): + try: + if charset: + self._execute_command(COM_QUERY, "SET NAMES %s" % + self.escape(charset)) + self.read_packet() + self.charset = charset + except: + exc,value,tb = sys.exc_info() + self.errorhandler(None, exc, value) + + def _connect(self): + try: + if self.unix_socket and (self.host == 'localhost' or self.host == '127.0.0.1'): + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + t = sock.gettimeout() + sock.settimeout(self.connect_timeout) + sock.connect(self.unix_socket) + sock.settimeout(t) + self.host_info = "Localhost via UNIX socket" + if DEBUG: print 'connected using unix_socket' + else: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + t = sock.gettimeout() + sock.settimeout(self.connect_timeout) + sock.connect((self.host, self.port)) + sock.settimeout(t) + self.host_info = "socket %s:%d" % (self.host, self.port) + if DEBUG: print 'connected using socket' + self.socket = sock + self._get_server_information() + self._request_authentication() + except socket.error, e: + raise OperationalError(2003, "Can't connect to MySQL server on %r (%d)" % (self.host, e.args[0])) + + def read_packet(self, packet_type=MysqlPacket): + """Read an entire "mysql packet" in its entirety from the network + and return a MysqlPacket type that represents the results.""" + + # TODO: is socket.recv(small_number) significantly slower than + # socket.recv(large_number)? if so, maybe we should buffer + # the socket.recv() (though that obviously makes memory management + # more complicated. + packet = packet_type(self.socket) + packet.check_error() + return packet + + def _read_query_result(self): + result = MySQLResult(self) + result.read() + self._result = result + return result.affected_rows + + def _send_command(self, command, sql): + #send_data = struct.pack('<i', len(sql) + 1) + command + sql + # could probably be more efficient, at least it's correct + if not self.socket: + self.errorhandler(None, InterfaceError, "(0, '')") + + if isinstance(sql, unicode): + sql = sql.encode(self.charset) + + buf = int2byte(command) + sql + pckt_no = 0 + while len(buf) >= MAX_PACKET_LENGTH: + header = struct.pack('<i', MAX_PACKET_LENGTH)[:-1]+int2byte(pckt_no) + send_data = header + buf[:MAX_PACKET_LENGTH] + self.socket.send(send_data) + if DEBUG: dump_packet(send_data) + buf = buf[MAX_PACKET_LENGTH:] + pckt_no += 1 + header = struct.pack('<i', len(buf))[:-1]+int2byte(pckt_no) + self.socket.send(header+buf) + + + #sock = self.socket + #sock.send(send_data) + + # + + def _execute_command(self, command, sql): + self._send_command(command, sql) + + def _request_authentication(self): + self._send_authentication() + + def _send_authentication(self): + sock = self.socket + self.client_flag |= CAPABILITIES + if self.server_version.startswith('5'): + self.client_flag |= MULTI_RESULTS + + if self.user is None: + raise ValueError, "Did not specify a username" + + charset_id = charset_by_name(self.charset).id + self.user = self.user.encode(self.charset) + + data_init = struct.pack('<i', self.client_flag) + struct.pack("<I", 1) + \ + int2byte(charset_id) + int2byte(0)*23 + + next_packet = 1 + + if self.ssl: + data = pack_int24(len(data_init)) + int2byte(next_packet) + data_init + next_packet += 1 + + if DEBUG: dump_packet(data) + + sock.send(data) + sock = self.socket = ssl.wrap_socket(sock, keyfile=self.key, + certfile=self.cert, + ssl_version=ssl.PROTOCOL_TLSv1, + cert_reqs=ssl.CERT_REQUIRED, + ca_certs=self.ca) + + data = data_init + self.user+int2byte(0) + _scramble(self.password.encode(self.charset), self.salt) + + if self.db: + self.db = self.db.encode(self.charset) + data += self.db + int2byte(0) + + data = pack_int24(len(data)) + int2byte(next_packet) + data + next_packet += 2 + + if DEBUG: dump_packet(data) + + sock.send(data) + + auth_packet = MysqlPacket(sock) + auth_packet.check_error() + if DEBUG: auth_packet.dump() + + # if old_passwords is enabled the packet will be 1 byte long and + # have the octet 254 + + if auth_packet.is_eof_packet(): + # send legacy handshake + #raise NotImplementedError, "old_passwords are not supported. Check to see if mysqld was started with --old-passwords, if old-passwords=1 in a my.cnf file, or if there are some short hashes in your mysql.user table." + # TODO: is this the correct charset? + data = _scramble_323(self.password.encode(self.charset), self.salt.encode(self.charset)) + int2byte(0) + data = pack_int24(len(data)) + int2byte(next_packet) + data + + sock.send(data) + auth_packet = MysqlPacket(sock) + auth_packet.check_error() + if DEBUG: auth_packet.dump() + + + # _mysql support + def thread_id(self): + return self.server_thread_id[0] + + def character_set_name(self): + return self.charset + + def get_host_info(self): + return self.host_info + + def get_proto_info(self): + return self.protocol_version + + def _get_server_information(self): + sock = self.socket + i = 0 + packet = MysqlPacket(sock) + data = packet.get_all_data() + + if DEBUG: dump_packet(data) + #packet_len = byte2int(data[i:i+1]) + #i += 4 + self.protocol_version = byte2int(data[i:i+1]) + + i += 1 + server_end = data.find(int2byte(0), i) + # TODO: is this the correct charset? should it be default_charset? + self.server_version = data[i:server_end].decode(self.charset) + + i = server_end + 1 + self.server_thread_id = struct.unpack('<h', data[i:i+2]) + + i += 4 + self.salt = data[i:i+8] + + i += 9 + if len(data) >= i + 1: + i += 1 + + self.server_capabilities = struct.unpack('<h', data[i:i+2])[0] + + i += 1 + self.server_language = byte2int(data[i:i+1]) + self.server_charset = charset_by_id(self.server_language).name + + i += 16 + if len(data) >= i+12-1: + rest_salt = data[i:i+12] + self.salt += rest_salt + + def get_server_info(self): + return self.server_version + + Warning = Warning + Error = Error + InterfaceError = InterfaceError + DatabaseError = DatabaseError + DataError = DataError + OperationalError = OperationalError + IntegrityError = IntegrityError + InternalError = InternalError + ProgrammingError = ProgrammingError + NotSupportedError = NotSupportedError + +# TODO: move OK and EOF packet parsing/logic into a proper subclass +# of MysqlPacket like has been done with FieldDescriptorPacket. +class MySQLResult(object): + + def __init__(self, connection): + from weakref import proxy + self.connection = proxy(connection) + self.affected_rows = None + self.insert_id = None + self.server_status = 0 + self.warning_count = 0 + self.message = None + self.field_count = 0 + self.description = None + self.rows = None + self.has_next = None + + def read(self): + self.first_packet = self.connection.read_packet() + + # TODO: use classes for different packet types? + if self.first_packet.is_ok_packet(): + self._read_ok_packet() + else: + self._read_result_packet() + + def _read_ok_packet(self): + self.first_packet.advance(1) # field_count (always '0') + self.affected_rows = self.first_packet.read_length_coded_binary() + self.insert_id = self.first_packet.read_length_coded_binary() + self.server_status = struct.unpack('<H', self.first_packet.read(2))[0] + self.warning_count = struct.unpack('<H', self.first_packet.read(2))[0] + self.message = self.first_packet.read_all() + + def _read_result_packet(self): + self.field_count = byte2int(self.first_packet.read(1)) + self._get_descriptions() + self._read_rowdata_packet() + + # TODO: implement this as an iteratable so that it is more + # memory efficient and lower-latency to client... + def _read_rowdata_packet(self): + """Read a rowdata packet for each data row in the result set.""" + rows = [] + while True: + packet = self.connection.read_packet() + if packet.is_eof_packet(): + self.warning_count = packet.read(2) + server_status = struct.unpack('<h', packet.read(2))[0] + self.has_next = (server_status + & SERVER_STATUS.SERVER_MORE_RESULTS_EXISTS) + break + + row = [] + for field in self.fields: + if field.type_code in self.connection.decoders: + converter = self.connection.decoders[field.type_code] + + if DEBUG: print "DEBUG: field=%s, converter=%s" % (field, converter) + data = packet.read_length_coded_string() + converted = None + if data != None: + converted = converter(self.connection, field, data) + + row.append(converted) + + rows.append(tuple(row)) + + self.affected_rows = len(rows) + self.rows = tuple(rows) + if DEBUG: self.rows + + def _get_descriptions(self): + """Read a column descriptor packet for each column in the result.""" + self.fields = [] + description = [] + for i in xrange(self.field_count): + field = self.connection.read_packet(FieldDescriptorPacket) + self.fields.append(field) + description.append(field.description()) + + eof_packet = self.connection.read_packet() + assert eof_packet.is_eof_packet(), 'Protocol error, expecting EOF' + self.description = tuple(description) ADDED gluon/contrib/pymysql/constants/CLIENT.py Index: gluon/contrib/pymysql/constants/CLIENT.py ================================================================== --- gluon/contrib/pymysql/constants/CLIENT.py +++ gluon/contrib/pymysql/constants/CLIENT.py @@ -0,0 +1,20 @@ + +LONG_PASSWORD = 1 +FOUND_ROWS = 1 << 1 +LONG_FLAG = 1 << 2 +CONNECT_WITH_DB = 1 << 3 +NO_SCHEMA = 1 << 4 +COMPRESS = 1 << 5 +ODBC = 1 << 6 +LOCAL_FILES = 1 << 7 +IGNORE_SPACE = 1 << 8 +PROTOCOL_41 = 1 << 9 +INTERACTIVE = 1 << 10 +SSL = 1 << 11 +IGNORE_SIGPIPE = 1 << 12 +TRANSACTIONS = 1 << 13 +SECURE_CONNECTION = 1 << 15 +MULTI_STATEMENTS = 1 << 16 +MULTI_RESULTS = 1 << 17 +CAPABILITIES = LONG_PASSWORD|LONG_FLAG|TRANSACTIONS| \ + PROTOCOL_41|SECURE_CONNECTION ADDED gluon/contrib/pymysql/constants/COMMAND.py Index: gluon/contrib/pymysql/constants/COMMAND.py ================================================================== --- gluon/contrib/pymysql/constants/COMMAND.py +++ gluon/contrib/pymysql/constants/COMMAND.py @@ -0,0 +1,23 @@ + +COM_SLEEP = 0x00 +COM_QUIT = 0x01 +COM_INIT_DB = 0x02 +COM_QUERY = 0x03 +COM_FIELD_LIST = 0x04 +COM_CREATE_DB = 0x05 +COM_DROP_DB = 0x06 +COM_REFRESH = 0x07 +COM_SHUTDOWN = 0x08 +COM_STATISTICS = 0x09 +COM_PROCESS_INFO = 0x0a +COM_CONNECT = 0x0b +COM_PROCESS_KILL = 0x0c +COM_DEBUG = 0x0d +COM_PING = 0x0e +COM_TIME = 0x0f +COM_DELAYED_INSERT = 0x10 +COM_CHANGE_USER = 0x11 +COM_BINLOG_DUMP = 0x12 +COM_TABLE_DUMP = 0x13 +COM_CONNECT_OUT = 0x14 +COM_REGISTER_SLAVE = 0x15 ADDED gluon/contrib/pymysql/constants/ER.py Index: gluon/contrib/pymysql/constants/ER.py ================================================================== --- gluon/contrib/pymysql/constants/ER.py +++ gluon/contrib/pymysql/constants/ER.py @@ -0,0 +1,472 @@ + +ERROR_FIRST = 1000 +HASHCHK = 1000 +NISAMCHK = 1001 +NO = 1002 +YES = 1003 +CANT_CREATE_FILE = 1004 +CANT_CREATE_TABLE = 1005 +CANT_CREATE_DB = 1006 +DB_CREATE_EXISTS = 1007 +DB_DROP_EXISTS = 1008 +DB_DROP_DELETE = 1009 +DB_DROP_RMDIR = 1010 +CANT_DELETE_FILE = 1011 +CANT_FIND_SYSTEM_REC = 1012 +CANT_GET_STAT = 1013 +CANT_GET_WD = 1014 +CANT_LOCK = 1015 +CANT_OPEN_FILE = 1016 +FILE_NOT_FOUND = 1017 +CANT_READ_DIR = 1018 +CANT_SET_WD = 1019 +CHECKREAD = 1020 +DISK_FULL = 1021 +DUP_KEY = 1022 +ERROR_ON_CLOSE = 1023 +ERROR_ON_READ = 1024 +ERROR_ON_RENAME = 1025 +ERROR_ON_WRITE = 1026 +FILE_USED = 1027 +FILSORT_ABORT = 1028 +FORM_NOT_FOUND = 1029 +GET_ERRNO = 1030 +ILLEGAL_HA = 1031 +KEY_NOT_FOUND = 1032 +NOT_FORM_FILE = 1033 +NOT_KEYFILE = 1034 +OLD_KEYFILE = 1035 +OPEN_AS_READONLY = 1036 +OUTOFMEMORY = 1037 +OUT_OF_SORTMEMORY = 1038 +UNEXPECTED_EOF = 1039 +CON_COUNT_ERROR = 1040 +OUT_OF_RESOURCES = 1041 +BAD_HOST_ERROR = 1042 +HANDSHAKE_ERROR = 1043 +DBACCESS_DENIED_ERROR = 1044 +ACCESS_DENIED_ERROR = 1045 +NO_DB_ERROR = 1046 +UNKNOWN_COM_ERROR = 1047 +BAD_NULL_ERROR = 1048 +BAD_DB_ERROR = 1049 +TABLE_EXISTS_ERROR = 1050 +BAD_TABLE_ERROR = 1051 +NON_UNIQ_ERROR = 1052 +SERVER_SHUTDOWN = 1053 +BAD_FIELD_ERROR = 1054 +WRONG_FIELD_WITH_GROUP = 1055 +WRONG_GROUP_FIELD = 1056 +WRONG_SUM_SELECT = 1057 +WRONG_VALUE_COUNT = 1058 +TOO_LONG_IDENT = 1059 +DUP_FIELDNAME = 1060 +DUP_KEYNAME = 1061 +DUP_ENTRY = 1062 +WRONG_FIELD_SPEC = 1063 +PARSE_ERROR = 1064 +EMPTY_QUERY = 1065 +NONUNIQ_TABLE = 1066 +INVALID_DEFAULT = 1067 +MULTIPLE_PRI_KEY = 1068 +TOO_MANY_KEYS = 1069 +TOO_MANY_KEY_PARTS = 1070 +TOO_LONG_KEY = 1071 +KEY_COLUMN_DOES_NOT_EXITS = 1072 +BLOB_USED_AS_KEY = 1073 +TOO_BIG_FIELDLENGTH = 1074 +WRONG_AUTO_KEY = 1075 +READY = 1076 +NORMAL_SHUTDOWN = 1077 +GOT_SIGNAL = 1078 +SHUTDOWN_COMPLETE = 1079 +FORCING_CLOSE = 1080 +IPSOCK_ERROR = 1081 +NO_SUCH_INDEX = 1082 +WRONG_FIELD_TERMINATORS = 1083 +BLOBS_AND_NO_TERMINATED = 1084 +TEXTFILE_NOT_READABLE = 1085 +FILE_EXISTS_ERROR = 1086 +LOAD_INFO = 1087 +ALTER_INFO = 1088 +WRONG_SUB_KEY = 1089 +CANT_REMOVE_ALL_FIELDS = 1090 +CANT_DROP_FIELD_OR_KEY = 1091 +INSERT_INFO = 1092 +UPDATE_TABLE_USED = 1093 +NO_SUCH_THREAD = 1094 +KILL_DENIED_ERROR = 1095 +NO_TABLES_USED = 1096 +TOO_BIG_SET = 1097 +NO_UNIQUE_LOGFILE = 1098 +TABLE_NOT_LOCKED_FOR_WRITE = 1099 +TABLE_NOT_LOCKED = 1100 +BLOB_CANT_HAVE_DEFAULT = 1101 +WRONG_DB_NAME = 1102 +WRONG_TABLE_NAME = 1103 +TOO_BIG_SELECT = 1104 +UNKNOWN_ERROR = 1105 +UNKNOWN_PROCEDURE = 1106 +WRONG_PARAMCOUNT_TO_PROCEDURE = 1107 +WRONG_PARAMETERS_TO_PROCEDURE = 1108 +UNKNOWN_TABLE = 1109 +FIELD_SPECIFIED_TWICE = 1110 +INVALID_GROUP_FUNC_USE = 1111 +UNSUPPORTED_EXTENSION = 1112 +TABLE_MUST_HAVE_COLUMNS = 1113 +RECORD_FILE_FULL = 1114 +UNKNOWN_CHARACTER_SET = 1115 +TOO_MANY_TABLES = 1116 +TOO_MANY_FIELDS = 1117 +TOO_BIG_ROWSIZE = 1118 +STACK_OVERRUN = 1119 +WRONG_OUTER_JOIN = 1120 +NULL_COLUMN_IN_INDEX = 1121 +CANT_FIND_UDF = 1122 +CANT_INITIALIZE_UDF = 1123 +UDF_NO_PATHS = 1124 +UDF_EXISTS = 1125 +CANT_OPEN_LIBRARY = 1126 +CANT_FIND_DL_ENTRY = 1127 +FUNCTION_NOT_DEFINED = 1128 +HOST_IS_BLOCKED = 1129 +HOST_NOT_PRIVILEGED = 1130 +PASSWORD_ANONYMOUS_USER = 1131 +PASSWORD_NOT_ALLOWED = 1132 +PASSWORD_NO_MATCH = 1133 +UPDATE_INFO = 1134 +CANT_CREATE_THREAD = 1135 +WRONG_VALUE_COUNT_ON_ROW = 1136 +CANT_REOPEN_TABLE = 1137 +INVALID_USE_OF_NULL = 1138 +REGEXP_ERROR = 1139 +MIX_OF_GROUP_FUNC_AND_FIELDS = 1140 +NONEXISTING_GRANT = 1141 +TABLEACCESS_DENIED_ERROR = 1142 +COLUMNACCESS_DENIED_ERROR = 1143 +ILLEGAL_GRANT_FOR_TABLE = 1144 +GRANT_WRONG_HOST_OR_USER = 1145 +NO_SUCH_TABLE = 1146 +NONEXISTING_TABLE_GRANT = 1147 +NOT_ALLOWED_COMMAND = 1148 +SYNTAX_ERROR = 1149 +DELAYED_CANT_CHANGE_LOCK = 1150 +TOO_MANY_DELAYED_THREADS = 1151 +ABORTING_CONNECTION = 1152 +NET_PACKET_TOO_LARGE = 1153 +NET_READ_ERROR_FROM_PIPE = 1154 +NET_FCNTL_ERROR = 1155 +NET_PACKETS_OUT_OF_ORDER = 1156 +NET_UNCOMPRESS_ERROR = 1157 +NET_READ_ERROR = 1158 +NET_READ_INTERRUPTED = 1159 +NET_ERROR_ON_WRITE = 1160 +NET_WRITE_INTERRUPTED = 1161 +TOO_LONG_STRING = 1162 +TABLE_CANT_HANDLE_BLOB = 1163 +TABLE_CANT_HANDLE_AUTO_INCREMENT = 1164 +DELAYED_INSERT_TABLE_LOCKED = 1165 +WRONG_COLUMN_NAME = 1166 +WRONG_KEY_COLUMN = 1167 +WRONG_MRG_TABLE = 1168 +DUP_UNIQUE = 1169 +BLOB_KEY_WITHOUT_LENGTH = 1170 +PRIMARY_CANT_HAVE_NULL = 1171 +TOO_MANY_ROWS = 1172 +REQUIRES_PRIMARY_KEY = 1173 +NO_RAID_COMPILED = 1174 +UPDATE_WITHOUT_KEY_IN_SAFE_MODE = 1175 +KEY_DOES_NOT_EXITS = 1176 +CHECK_NO_SUCH_TABLE = 1177 +CHECK_NOT_IMPLEMENTED = 1178 +CANT_DO_THIS_DURING_AN_TRANSACTION = 1179 +ERROR_DURING_COMMIT = 1180 +ERROR_DURING_ROLLBACK = 1181 +ERROR_DURING_FLUSH_LOGS = 1182 +ERROR_DURING_CHECKPOINT = 1183 +NEW_ABORTING_CONNECTION = 1184 +DUMP_NOT_IMPLEMENTED = 1185 +FLUSH_MASTER_BINLOG_CLOSED = 1186 +INDEX_REBUILD = 1187 +MASTER = 1188 +MASTER_NET_READ = 1189 +MASTER_NET_WRITE = 1190 +FT_MATCHING_KEY_NOT_FOUND = 1191 +LOCK_OR_ACTIVE_TRANSACTION = 1192 +UNKNOWN_SYSTEM_VARIABLE = 1193 +CRASHED_ON_USAGE = 1194 +CRASHED_ON_REPAIR = 1195 +WARNING_NOT_COMPLETE_ROLLBACK = 1196 +TRANS_CACHE_FULL = 1197 +SLAVE_MUST_STOP = 1198 +SLAVE_NOT_RUNNING = 1199 +BAD_SLAVE = 1200 +MASTER_INFO = 1201 +SLAVE_THREAD = 1202 +TOO_MANY_USER_CONNECTIONS = 1203 +SET_CONSTANTS_ONLY = 1204 +LOCK_WAIT_TIMEOUT = 1205 +LOCK_TABLE_FULL = 1206 +READ_ONLY_TRANSACTION = 1207 +DROP_DB_WITH_READ_LOCK = 1208 +CREATE_DB_WITH_READ_LOCK = 1209 +WRONG_ARGUMENTS = 1210 +NO_PERMISSION_TO_CREATE_USER = 1211 +UNION_TABLES_IN_DIFFERENT_DIR = 1212 +LOCK_DEADLOCK = 1213 +TABLE_CANT_HANDLE_FT = 1214 +CANNOT_ADD_FOREIGN = 1215 +NO_REFERENCED_ROW = 1216 +ROW_IS_REFERENCED = 1217 +CONNECT_TO_MASTER = 1218 +QUERY_ON_MASTER = 1219 +ERROR_WHEN_EXECUTING_COMMAND = 1220 +WRONG_USAGE = 1221 +WRONG_NUMBER_OF_COLUMNS_IN_SELECT = 1222 +CANT_UPDATE_WITH_READLOCK = 1223 +MIXING_NOT_ALLOWED = 1224 +DUP_ARGUMENT = 1225 +USER_LIMIT_REACHED = 1226 +SPECIFIC_ACCESS_DENIED_ERROR = 1227 +LOCAL_VARIABLE = 1228 +GLOBAL_VARIABLE = 1229 +NO_DEFAULT = 1230 +WRONG_VALUE_FOR_VAR = 1231 +WRONG_TYPE_FOR_VAR = 1232 +VAR_CANT_BE_READ = 1233 +CANT_USE_OPTION_HERE = 1234 +NOT_SUPPORTED_YET = 1235 +MASTER_FATAL_ERROR_READING_BINLOG = 1236 +SLAVE_IGNORED_TABLE = 1237 +INCORRECT_GLOBAL_LOCAL_VAR = 1238 +WRONG_FK_DEF = 1239 +KEY_REF_DO_NOT_MATCH_TABLE_REF = 1240 +OPERAND_COLUMNS = 1241 +SUBQUERY_NO_1_ROW = 1242 +UNKNOWN_STMT_HANDLER = 1243 +CORRUPT_HELP_DB = 1244 +CYCLIC_REFERENCE = 1245 +AUTO_CONVERT = 1246 +ILLEGAL_REFERENCE = 1247 +DERIVED_MUST_HAVE_ALIAS = 1248 +SELECT_REDUCED = 1249 +TABLENAME_NOT_ALLOWED_HERE = 1250 +NOT_SUPPORTED_AUTH_MODE = 1251 +SPATIAL_CANT_HAVE_NULL = 1252 +COLLATION_CHARSET_MISMATCH = 1253 +SLAVE_WAS_RUNNING = 1254 +SLAVE_WAS_NOT_RUNNING = 1255 +TOO_BIG_FOR_UNCOMPRESS = 1256 +ZLIB_Z_MEM_ERROR = 1257 +ZLIB_Z_BUF_ERROR = 1258 +ZLIB_Z_DATA_ERROR = 1259 +CUT_VALUE_GROUP_CONCAT = 1260 +WARN_TOO_FEW_RECORDS = 1261 +WARN_TOO_MANY_RECORDS = 1262 +WARN_NULL_TO_NOTNULL = 1263 +WARN_DATA_OUT_OF_RANGE = 1264 +WARN_DATA_TRUNCATED = 1265 +WARN_USING_OTHER_HANDLER = 1266 +CANT_AGGREGATE_2COLLATIONS = 1267 +DROP_USER = 1268 +REVOKE_GRANTS = 1269 +CANT_AGGREGATE_3COLLATIONS = 1270 +CANT_AGGREGATE_NCOLLATIONS = 1271 +VARIABLE_IS_NOT_STRUCT = 1272 +UNKNOWN_COLLATION = 1273 +SLAVE_IGNORED_SSL_PARAMS = 1274 +SERVER_IS_IN_SECURE_AUTH_MODE = 1275 +WARN_FIELD_RESOLVED = 1276 +BAD_SLAVE_UNTIL_COND = 1277 +MISSING_SKIP_SLAVE = 1278 +UNTIL_COND_IGNORED = 1279 +WRONG_NAME_FOR_INDEX = 1280 +WRONG_NAME_FOR_CATALOG = 1281 +WARN_QC_RESIZE = 1282 +BAD_FT_COLUMN = 1283 +UNKNOWN_KEY_CACHE = 1284 +WARN_HOSTNAME_WONT_WORK = 1285 +UNKNOWN_STORAGE_ENGINE = 1286 +WARN_DEPRECATED_SYNTAX = 1287 +NON_UPDATABLE_TABLE = 1288 +FEATURE_DISABLED = 1289 +OPTION_PREVENTS_STATEMENT = 1290 +DUPLICATED_VALUE_IN_TYPE = 1291 +TRUNCATED_WRONG_VALUE = 1292 +TOO_MUCH_AUTO_TIMESTAMP_COLS = 1293 +INVALID_ON_UPDATE = 1294 +UNSUPPORTED_PS = 1295 +GET_ERRMSG = 1296 +GET_TEMPORARY_ERRMSG = 1297 +UNKNOWN_TIME_ZONE = 1298 +WARN_INVALID_TIMESTAMP = 1299 +INVALID_CHARACTER_STRING = 1300 +WARN_ALLOWED_PACKET_OVERFLOWED = 1301 +CONFLICTING_DECLARATIONS = 1302 +SP_NO_RECURSIVE_CREATE = 1303 +SP_ALREADY_EXISTS = 1304 +SP_DOES_NOT_EXIST = 1305 +SP_DROP_FAILED = 1306 +SP_STORE_FAILED = 1307 +SP_LILABEL_MISMATCH = 1308 +SP_LABEL_REDEFINE = 1309 +SP_LABEL_MISMATCH = 1310 +SP_UNINIT_VAR = 1311 +SP_BADSELECT = 1312 +SP_BADRETURN = 1313 +SP_BADSTATEMENT = 1314 +UPDATE_LOG_DEPRECATED_IGNORED = 1315 +UPDATE_LOG_DEPRECATED_TRANSLATED = 1316 +QUERY_INTERRUPTED = 1317 +SP_WRONG_NO_OF_ARGS = 1318 +SP_COND_MISMATCH = 1319 +SP_NORETURN = 1320 +SP_NORETURNEND = 1321 +SP_BAD_CURSOR_QUERY = 1322 +SP_BAD_CURSOR_SELECT = 1323 +SP_CURSOR_MISMATCH = 1324 +SP_CURSOR_ALREADY_OPEN = 1325 +SP_CURSOR_NOT_OPEN = 1326 +SP_UNDECLARED_VAR = 1327 +SP_WRONG_NO_OF_FETCH_ARGS = 1328 +SP_FETCH_NO_DATA = 1329 +SP_DUP_PARAM = 1330 +SP_DUP_VAR = 1331 +SP_DUP_COND = 1332 +SP_DUP_CURS = 1333 +SP_CANT_ALTER = 1334 +SP_SUBSELECT_NYI = 1335 +STMT_NOT_ALLOWED_IN_SF_OR_TRG = 1336 +SP_VARCOND_AFTER_CURSHNDLR = 1337 +SP_CURSOR_AFTER_HANDLER = 1338 +SP_CASE_NOT_FOUND = 1339 +FPARSER_TOO_BIG_FILE = 1340 +FPARSER_BAD_HEADER = 1341 +FPARSER_EOF_IN_COMMENT = 1342 +FPARSER_ERROR_IN_PARAMETER = 1343 +FPARSER_EOF_IN_UNKNOWN_PARAMETER = 1344 +VIEW_NO_EXPLAIN = 1345 +FRM_UNKNOWN_TYPE = 1346 +WRONG_OBJECT = 1347 +NONUPDATEABLE_COLUMN = 1348 +VIEW_SELECT_DERIVED = 1349 +VIEW_SELECT_CLAUSE = 1350 +VIEW_SELECT_VARIABLE = 1351 +VIEW_SELECT_TMPTABLE = 1352 +VIEW_WRONG_LIST = 1353 +WARN_VIEW_MERGE = 1354 +WARN_VIEW_WITHOUT_KEY = 1355 +VIEW_INVALID = 1356 +SP_NO_DROP_SP = 1357 +SP_GOTO_IN_HNDLR = 1358 +TRG_ALREADY_EXISTS = 1359 +TRG_DOES_NOT_EXIST = 1360 +TRG_ON_VIEW_OR_TEMP_TABLE = 1361 +TRG_CANT_CHANGE_ROW = 1362 +TRG_NO_SUCH_ROW_IN_TRG = 1363 +NO_DEFAULT_FOR_FIELD = 1364 +DIVISION_BY_ZERO = 1365 +TRUNCATED_WRONG_VALUE_FOR_FIELD = 1366 +ILLEGAL_VALUE_FOR_TYPE = 1367 +VIEW_NONUPD_CHECK = 1368 +VIEW_CHECK_FAILED = 1369 +PROCACCESS_DENIED_ERROR = 1370 +RELAY_LOG_FAIL = 1371 +PASSWD_LENGTH = 1372 +UNKNOWN_TARGET_BINLOG = 1373 +IO_ERR_LOG_INDEX_READ = 1374 +BINLOG_PURGE_PROHIBITED = 1375 +FSEEK_FAIL = 1376 +BINLOG_PURGE_FATAL_ERR = 1377 +LOG_IN_USE = 1378 +LOG_PURGE_UNKNOWN_ERR = 1379 +RELAY_LOG_INIT = 1380 +NO_BINARY_LOGGING = 1381 +RESERVED_SYNTAX = 1382 +WSAS_FAILED = 1383 +DIFF_GROUPS_PROC = 1384 +NO_GROUP_FOR_PROC = 1385 +ORDER_WITH_PROC = 1386 +LOGGING_PROHIBIT_CHANGING_OF = 1387 +NO_FILE_MAPPING = 1388 +WRONG_MAGIC = 1389 +PS_MANY_PARAM = 1390 +KEY_PART_0 = 1391 +VIEW_CHECKSUM = 1392 +VIEW_MULTIUPDATE = 1393 +VIEW_NO_INSERT_FIELD_LIST = 1394 +VIEW_DELETE_MERGE_VIEW = 1395 +CANNOT_USER = 1396 +XAER_NOTA = 1397 +XAER_INVAL = 1398 +XAER_RMFAIL = 1399 +XAER_OUTSIDE = 1400 +XAER_RMERR = 1401 +XA_RBROLLBACK = 1402 +NONEXISTING_PROC_GRANT = 1403 +PROC_AUTO_GRANT_FAIL = 1404 +PROC_AUTO_REVOKE_FAIL = 1405 +DATA_TOO_LONG = 1406 +SP_BAD_SQLSTATE = 1407 +STARTUP = 1408 +LOAD_FROM_FIXED_SIZE_ROWS_TO_VAR = 1409 +CANT_CREATE_USER_WITH_GRANT = 1410 +WRONG_VALUE_FOR_TYPE = 1411 +TABLE_DEF_CHANGED = 1412 +SP_DUP_HANDLER = 1413 +SP_NOT_VAR_ARG = 1414 +SP_NO_RETSET = 1415 +CANT_CREATE_GEOMETRY_OBJECT = 1416 +FAILED_ROUTINE_BREAK_BINLOG = 1417 +BINLOG_UNSAFE_ROUTINE = 1418 +BINLOG_CREATE_ROUTINE_NEED_SUPER = 1419 +EXEC_STMT_WITH_OPEN_CURSOR = 1420 +STMT_HAS_NO_OPEN_CURSOR = 1421 +COMMIT_NOT_ALLOWED_IN_SF_OR_TRG = 1422 +NO_DEFAULT_FOR_VIEW_FIELD = 1423 +SP_NO_RECURSION = 1424 +TOO_BIG_SCALE = 1425 +TOO_BIG_PRECISION = 1426 +M_BIGGER_THAN_D = 1427 +WRONG_LOCK_OF_SYSTEM_TABLE = 1428 +CONNECT_TO_FOREIGN_DATA_SOURCE = 1429 +QUERY_ON_FOREIGN_DATA_SOURCE = 1430 +FOREIGN_DATA_SOURCE_DOESNT_EXIST = 1431 +FOREIGN_DATA_STRING_INVALID_CANT_CREATE = 1432 +FOREIGN_DATA_STRING_INVALID = 1433 +CANT_CREATE_FEDERATED_TABLE = 1434 +TRG_IN_WRONG_SCHEMA = 1435 +STACK_OVERRUN_NEED_MORE = 1436 +TOO_LONG_BODY = 1437 +WARN_CANT_DROP_DEFAULT_KEYCACHE = 1438 +TOO_BIG_DISPLAYWIDTH = 1439 +XAER_DUPID = 1440 +DATETIME_FUNCTION_OVERFLOW = 1441 +CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG = 1442 +VIEW_PREVENT_UPDATE = 1443 +PS_NO_RECURSION = 1444 +SP_CANT_SET_AUTOCOMMIT = 1445 +MALFORMED_DEFINER = 1446 +VIEW_FRM_NO_USER = 1447 +VIEW_OTHER_USER = 1448 +NO_SUCH_USER = 1449 +FORBID_SCHEMA_CHANGE = 1450 +ROW_IS_REFERENCED_2 = 1451 +NO_REFERENCED_ROW_2 = 1452 +SP_BAD_VAR_SHADOW = 1453 +TRG_NO_DEFINER = 1454 +OLD_FILE_FORMAT = 1455 +SP_RECURSION_LIMIT = 1456 +SP_PROC_TABLE_CORRUPT = 1457 +SP_WRONG_NAME = 1458 +TABLE_NEEDS_UPGRADE = 1459 +SP_NO_AGGREGATE = 1460 +MAX_PREPARED_STMT_COUNT_REACHED = 1461 +VIEW_RECURSIVE = 1462 +NON_GROUPING_FIELD_USED = 1463 +TABLE_CANT_HANDLE_SPKEYS = 1464 +NO_TRIGGERS_ON_SYSTEM_SCHEMA = 1465 +USERNAME = 1466 +HOSTNAME = 1467 +WRONG_STRING_LENGTH = 1468 +ERROR_LAST = 1468 ADDED gluon/contrib/pymysql/constants/FIELD_TYPE.py Index: gluon/contrib/pymysql/constants/FIELD_TYPE.py ================================================================== --- gluon/contrib/pymysql/constants/FIELD_TYPE.py +++ gluon/contrib/pymysql/constants/FIELD_TYPE.py @@ -0,0 +1,32 @@ + + +DECIMAL = 0 +TINY = 1 +SHORT = 2 +LONG = 3 +FLOAT = 4 +DOUBLE = 5 +NULL = 6 +TIMESTAMP = 7 +LONGLONG = 8 +INT24 = 9 +DATE = 10 +TIME = 11 +DATETIME = 12 +YEAR = 13 +NEWDATE = 14 +VARCHAR = 15 +BIT = 16 +NEWDECIMAL = 246 +ENUM = 247 +SET = 248 +TINY_BLOB = 249 +MEDIUM_BLOB = 250 +LONG_BLOB = 251 +BLOB = 252 +VAR_STRING = 253 +STRING = 254 +GEOMETRY = 255 + +CHAR = TINY +INTERVAL = ENUM ADDED gluon/contrib/pymysql/constants/FLAG.py Index: gluon/contrib/pymysql/constants/FLAG.py ================================================================== --- gluon/contrib/pymysql/constants/FLAG.py +++ gluon/contrib/pymysql/constants/FLAG.py @@ -0,0 +1,15 @@ +NOT_NULL = 1 +PRI_KEY = 2 +UNIQUE_KEY = 4 +MULTIPLE_KEY = 8 +BLOB = 16 +UNSIGNED = 32 +ZEROFILL = 64 +BINARY = 128 +ENUM = 256 +AUTO_INCREMENT = 512 +TIMESTAMP = 1024 +SET = 2048 +PART_KEY = 16384 +GROUP = 32767 +UNIQUE = 65536 ADDED gluon/contrib/pymysql/constants/SERVER_STATUS.py Index: gluon/contrib/pymysql/constants/SERVER_STATUS.py ================================================================== --- gluon/contrib/pymysql/constants/SERVER_STATUS.py +++ gluon/contrib/pymysql/constants/SERVER_STATUS.py @@ -0,0 +1,12 @@ + +SERVER_STATUS_IN_TRANS = 1 +SERVER_STATUS_AUTOCOMMIT = 2 +SERVER_MORE_RESULTS_EXISTS = 8 +SERVER_QUERY_NO_GOOD_INDEX_USED = 16 +SERVER_QUERY_NO_INDEX_USED = 32 +SERVER_STATUS_CURSOR_EXISTS = 64 +SERVER_STATUS_LAST_ROW_SENT = 128 +SERVER_STATUS_DB_DROPPED = 256 +SERVER_STATUS_NO_BACKSLASH_ESCAPES = 512 +SERVER_STATUS_METADATA_CHANGED = 1024 + ADDED gluon/contrib/pymysql/constants/__init__.py Index: gluon/contrib/pymysql/constants/__init__.py ================================================================== --- gluon/contrib/pymysql/constants/__init__.py +++ gluon/contrib/pymysql/constants/__init__.py ADDED gluon/contrib/pymysql/converters.py Index: gluon/contrib/pymysql/converters.py ================================================================== --- gluon/contrib/pymysql/converters.py +++ gluon/contrib/pymysql/converters.py @@ -0,0 +1,346 @@ +import re +import datetime +import time + +from constants import FIELD_TYPE, FLAG +from charset import charset_by_id + +try: + set +except NameError: + try: + from sets import BaseSet as set + except ImportError: + from sets import Set as set + +ESCAPE_REGEX = re.compile(r"[\0\n\r\032\'\"\\]") +ESCAPE_MAP = {'\0': '\\0', '\n': '\\n', '\r': '\\r', '\032': '\\Z', + '\'': '\\\'', '"': '\\"', '\\': '\\\\'} + +def escape_item(val, charset): + if type(val) in [tuple, list, set]: + return escape_sequence(val, charset) + if type(val) is dict: + return escape_dict(val, charset) + if hasattr(val, "decode") and not isinstance(val, unicode): + # deal with py3k bytes + val = val.decode(charset) + encoder = encoders[type(val)] + val = encoder(val) + if type(val) is str: + return val + val = val.encode(charset) + return val + +def escape_dict(val, charset): + n = {} + for k, v in val.items(): + quoted = escape_item(v, charset) + n[k] = quoted + return n + +def escape_sequence(val, charset): + n = [] + for item in val: + quoted = escape_item(item, charset) + n.append(quoted) + return tuple(n) + +def escape_set(val, charset): + val = map(lambda x: escape_item(x, charset), val) + return ','.join(val) + +def escape_bool(value): + return str(int(value)) + +def escape_object(value): + return str(value) + +escape_int = escape_long = escape_object + +def escape_float(value): + return ('%.15g' % value) + +def escape_string(value): + return ("'%s'" % ESCAPE_REGEX.sub( + lambda match: ESCAPE_MAP.get(match.group(0)), value)) + +def escape_unicode(value): + return escape_string(value) + +def escape_None(value): + return 'NULL' + +def escape_timedelta(obj): + seconds = int(obj.seconds) % 60 + minutes = int(obj.seconds // 60) % 60 + hours = int(obj.seconds // 3600) % 24 + int(obj.days) * 24 + return escape_string('%02d:%02d:%02d' % (hours, minutes, seconds)) + +def escape_time(obj): + s = "%02d:%02d:%02d" % (int(obj.hour), int(obj.minute), + int(obj.second)) + if obj.microsecond: + s += ".%f" % obj.microsecond + + return escape_string(s) + +def escape_datetime(obj): + return escape_string(obj.strftime("%Y-%m-%d %H:%M:%S")) + +def escape_date(obj): + return escape_string(obj.strftime("%Y-%m-%d")) + +def escape_struct_time(obj): + return escape_datetime(datetime.datetime(*obj[:6])) + +def convert_datetime(connection, field, obj): + """Returns a DATETIME or TIMESTAMP column value as a datetime object: + + >>> datetime_or_None('2007-02-25 23:06:20') + datetime.datetime(2007, 2, 25, 23, 6, 20) + >>> datetime_or_None('2007-02-25T23:06:20') + datetime.datetime(2007, 2, 25, 23, 6, 20) + + Illegal values are returned as None: + + >>> datetime_or_None('2007-02-31T23:06:20') is None + True + >>> datetime_or_None('0000-00-00 00:00:00') is None + True + + """ + if not isinstance(obj, unicode): + obj = obj.decode(connection.charset) + if ' ' in obj: + sep = ' ' + elif 'T' in obj: + sep = 'T' + else: + return convert_date(connection, field, obj) + + try: + ymd, hms = obj.split(sep, 1) + return datetime.datetime(*[ int(x) for x in ymd.split('-')+hms.split(':') ]) + except ValueError: + return convert_date(connection, field, obj) + +def convert_timedelta(connection, field, obj): + """Returns a TIME column as a timedelta object: + + >>> timedelta_or_None('25:06:17') + datetime.timedelta(1, 3977) + >>> timedelta_or_None('-25:06:17') + datetime.timedelta(-2, 83177) + + Illegal values are returned as None: + + >>> timedelta_or_None('random crap') is None + True + + Note that MySQL always returns TIME columns as (+|-)HH:MM:SS, but + can accept values as (+|-)DD HH:MM:SS. The latter format will not + be parsed correctly by this function. + """ + from math import modf + try: + if not isinstance(obj, unicode): + obj = obj.decode(connection.charset) + hours, minutes, seconds = tuple([int(x) for x in obj.split(':')]) + tdelta = datetime.timedelta( + hours = int(hours), + minutes = int(minutes), + seconds = int(seconds), + microseconds = int(modf(float(seconds))[0]*1000000), + ) + return tdelta + except ValueError: + return None + +def convert_time(connection, field, obj): + """Returns a TIME column as a time object: + + >>> time_or_None('15:06:17') + datetime.time(15, 6, 17) + + Illegal values are returned as None: + + >>> time_or_None('-25:06:17') is None + True + >>> time_or_None('random crap') is None + True + + Note that MySQL always returns TIME columns as (+|-)HH:MM:SS, but + can accept values as (+|-)DD HH:MM:SS. The latter format will not + be parsed correctly by this function. + + Also note that MySQL's TIME column corresponds more closely to + Python's timedelta and not time. However if you want TIME columns + to be treated as time-of-day and not a time offset, then you can + use set this function as the converter for FIELD_TYPE.TIME. + """ + from math import modf + try: + hour, minute, second = obj.split(':') + return datetime.time(hour=int(hour), minute=int(minute), + second=int(second), + microsecond=int(modf(float(second))[0]*1000000)) + except ValueError: + return None + +def convert_date(connection, field, obj): + """Returns a DATE column as a date object: + + >>> date_or_None('2007-02-26') + datetime.date(2007, 2, 26) + + Illegal values are returned as None: + + >>> date_or_None('2007-02-31') is None + True + >>> date_or_None('0000-00-00') is None + True + + """ + try: + if not isinstance(obj, unicode): + obj = obj.decode(connection.charset) + return datetime.date(*[ int(x) for x in obj.split('-', 2) ]) + except ValueError: + return None + +def convert_mysql_timestamp(connection, field, timestamp): + """Convert a MySQL TIMESTAMP to a Timestamp object. + + MySQL >= 4.1 returns TIMESTAMP in the same format as DATETIME: + + >>> mysql_timestamp_converter('2007-02-25 22:32:17') + datetime.datetime(2007, 2, 25, 22, 32, 17) + + MySQL < 4.1 uses a big string of numbers: + + >>> mysql_timestamp_converter('20070225223217') + datetime.datetime(2007, 2, 25, 22, 32, 17) + + Illegal values are returned as None: + + >>> mysql_timestamp_converter('2007-02-31 22:32:17') is None + True + >>> mysql_timestamp_converter('00000000000000') is None + True + + """ + if not isinstance(timestamp, unicode): + timestamp = timestamp.decode(connection.charset) + + if timestamp[4] == '-': + return convert_datetime(connection, field, timestamp) + timestamp += "0"*(14-len(timestamp)) # padding + year, month, day, hour, minute, second = \ + int(timestamp[:4]), int(timestamp[4:6]), int(timestamp[6:8]), \ + int(timestamp[8:10]), int(timestamp[10:12]), int(timestamp[12:14]) + try: + return datetime.datetime(year, month, day, hour, minute, second) + except ValueError: + return None + +def convert_set(s): + return set(s.split(",")) + +def convert_bit(connection, field, b): + #b = "\x00" * (8 - len(b)) + b # pad w/ zeroes + #return struct.unpack(">Q", b)[0] + # + # the snippet above is right, but MySQLdb doesn't process bits, + # so we shouldn't either + return b + +def convert_characters(connection, field, data): + field_charset = charset_by_id(field.charsetnr).name + if field.flags & FLAG.SET: + return convert_set(data.decode(field_charset)) + if field.flags & FLAG.BINARY: + return data + + if connection.use_unicode: + data = data.decode(field_charset) + elif connection.charset != field_charset: + data = data.decode(field_charset) + data = data.encode(connection.charset) + else: + data = data.decode(connection.charset) + return data + +def convert_int(connection, field, data): + return int(data) + +def convert_long(connection, field, data): + return long(data) + +def convert_float(connection, field, data): + return float(data) + +encoders = { + bool: escape_bool, + int: escape_int, + long: escape_long, + float: escape_float, + str: escape_string, + unicode: escape_unicode, + tuple: escape_sequence, + list:escape_sequence, + set:escape_sequence, + dict:escape_dict, + type(None):escape_None, + datetime.date: escape_date, + datetime.datetime : escape_datetime, + datetime.timedelta : escape_timedelta, + datetime.time : escape_time, + time.struct_time : escape_struct_time, + } + +decoders = { + FIELD_TYPE.BIT: convert_bit, + FIELD_TYPE.TINY: convert_int, + FIELD_TYPE.SHORT: convert_int, + FIELD_TYPE.LONG: convert_long, + FIELD_TYPE.FLOAT: convert_float, + FIELD_TYPE.DOUBLE: convert_float, + FIELD_TYPE.DECIMAL: convert_float, + FIELD_TYPE.NEWDECIMAL: convert_float, + FIELD_TYPE.LONGLONG: convert_long, + FIELD_TYPE.INT24: convert_int, + FIELD_TYPE.YEAR: convert_int, + FIELD_TYPE.TIMESTAMP: convert_mysql_timestamp, + FIELD_TYPE.DATETIME: convert_datetime, + FIELD_TYPE.TIME: convert_timedelta, + FIELD_TYPE.DATE: convert_date, + FIELD_TYPE.SET: convert_set, + FIELD_TYPE.BLOB: convert_characters, + FIELD_TYPE.TINY_BLOB: convert_characters, + FIELD_TYPE.MEDIUM_BLOB: convert_characters, + FIELD_TYPE.LONG_BLOB: convert_characters, + FIELD_TYPE.STRING: convert_characters, + FIELD_TYPE.VAR_STRING: convert_characters, + FIELD_TYPE.VARCHAR: convert_characters, + #FIELD_TYPE.BLOB: str, + #FIELD_TYPE.STRING: str, + #FIELD_TYPE.VAR_STRING: str, + #FIELD_TYPE.VARCHAR: str + } +conversions = decoders # for MySQLdb compatibility + +try: + # python version > 2.3 + from decimal import Decimal + def convert_decimal(connection, field, data): + return Decimal(data) + decoders[FIELD_TYPE.DECIMAL] = convert_decimal + decoders[FIELD_TYPE.NEWDECIMAL] = convert_decimal + + def escape_decimal(obj): + return unicode(obj) + encoders[Decimal] = escape_decimal + +except ImportError: + pass ADDED gluon/contrib/pymysql/cursors.py Index: gluon/contrib/pymysql/cursors.py ================================================================== --- gluon/contrib/pymysql/cursors.py +++ gluon/contrib/pymysql/cursors.py @@ -0,0 +1,250 @@ +# -*- coding: utf-8 -*- +import struct +import re + +try: + import cStringIO as StringIO +except ImportError: + import StringIO + +from err import Warning, Error, InterfaceError, DataError, \ + DatabaseError, OperationalError, IntegrityError, InternalError, \ + NotSupportedError, ProgrammingError + +insert_values = re.compile(r'\svalues\s*(\(.+\))', re.IGNORECASE) + +class Cursor(object): + ''' + This is the object you use to interact with the database. + ''' + def __init__(self, connection): + ''' + Do not create an instance of a Cursor yourself. Call + connections.Connection.cursor(). + ''' + from weakref import proxy + self.connection = proxy(connection) + self.description = None + self.rownumber = 0 + self.rowcount = -1 + self.arraysize = 1 + self._executed = None + self.messages = [] + self.errorhandler = connection.errorhandler + self._has_next = None + self._rows = () + + def __del__(self): + ''' + When this gets GC'd close it. + ''' + self.close() + + def close(self): + ''' + Closing a cursor just exhausts all remaining data. + ''' + if not self.connection: + return + try: + while self.nextset(): + pass + except: + pass + + self.connection = None + + def _get_db(self): + if not self.connection: + self.errorhandler(self, ProgrammingError, "cursor closed") + return self.connection + + def _check_executed(self): + if not self._executed: + self.errorhandler(self, ProgrammingError, "execute() first") + + def setinputsizes(self, *args): + """Does nothing, required by DB API.""" + + def setoutputsizes(self, *args): + """Does nothing, required by DB API.""" + + def nextset(self): + ''' Get the next query set ''' + if self._executed: + self.fetchall() + del self.messages[:] + + if not self._has_next: + return None + connection = self._get_db() + connection.next_result() + self._do_get_result() + return True + + def execute(self, query, args=None): + ''' Execute a query ''' + from sys import exc_info + + conn = self._get_db() + charset = conn.charset + del self.messages[:] + + # TODO: make sure that conn.escape is correct + + if args is not None: + query = query % conn.escape(args) + + if isinstance(query, unicode): + query = query.encode(charset) + + result = 0 + try: + result = self._query(query) + except: + exc, value, tb = exc_info() + del tb + self.messages.append((exc,value)) + self.errorhandler(self, exc, value) + + self._executed = query + return result + + def executemany(self, query, args): + ''' Run several data against one query ''' + del self.messages[:] + conn = self._get_db() + if not args: + return + charset = conn.charset + if isinstance(query, unicode): + query = query.encode(charset) + + self.rowcount = sum([ self.execute(query, arg) for arg in args ]) + return self.rowcount + + + def callproc(self, procname, args=()): + """Execute stored procedure procname with args + + procname -- string, name of procedure to execute on server + + args -- Sequence of parameters to use with procedure + + Returns the original args. + + Compatibility warning: PEP-249 specifies that any modified + parameters must be returned. This is currently impossible + as they are only available by storing them in a server + variable and then retrieved by a query. Since stored + procedures return zero or more result sets, there is no + reliable way to get at OUT or INOUT parameters via callproc. + The server variables are named @_procname_n, where procname + is the parameter above and n is the position of the parameter + (from zero). Once all result sets generated by the procedure + have been fetched, you can issue a SELECT @_procname_0, ... + query using .execute() to get any OUT or INOUT values. + + Compatibility warning: The act of calling a stored procedure + itself creates an empty result set. This appears after any + result sets generated by the procedure. This is non-standard + behavior with respect to the DB-API. Be sure to use nextset() + to advance through all result sets; otherwise you may get + disconnected. + """ + conn = self._get_db() + for index, arg in enumerate(args): + q = "SET @_%s_%d=%s" % (procname, index, conn.escape(arg)) + if isinstance(q, unicode): + q = q.encode(conn.charset) + self._query(q) + self.nextset() + + q = "CALL %s(%s)" % (procname, + ','.join(['@_%s_%d' % (procname, i) + for i in range(len(args))])) + if isinstance(q, unicode): + q = q.encode(conn.charset) + self._query(q) + self._executed = q + + return args + + def fetchone(self): + ''' Fetch the next row ''' + self._check_executed() + if self._rows is None or self.rownumber >= len(self._rows): + return None + result = self._rows[self.rownumber] + self.rownumber += 1 + return result + + def fetchmany(self, size=None): + ''' Fetch several rows ''' + self._check_executed() + end = self.rownumber + (size or self.arraysize) + result = self._rows[self.rownumber:end] + if self._rows is None: + return None + self.rownumber = min(end, len(self._rows)) + return result + + def fetchall(self): + ''' Fetch all the rows ''' + self._check_executed() + if self._rows is None: + return None + if self.rownumber: + result = self._rows[self.rownumber:] + else: + result = self._rows + self.rownumber = len(self._rows) + return result + + def scroll(self, value, mode='relative'): + self._check_executed() + if mode == 'relative': + r = self.rownumber + value + elif mode == 'absolute': + r = value + else: + self.errorhandler(self, ProgrammingError, + "unknown scroll mode %s" % mode) + + if r < 0 or r >= len(self._rows): + self.errorhandler(self, IndexError, "out of range") + self.rownumber = r + + def _query(self, q): + conn = self._get_db() + self._last_executed = q + conn.query(q) + self._do_get_result() + return self.rowcount + + def _do_get_result(self): + conn = self._get_db() + self.rowcount = conn._result.affected_rows + + self.rownumber = 0 + self.description = conn._result.description + self.lastrowid = conn._result.insert_id + self._rows = conn._result.rows + self._has_next = conn._result.has_next + conn._result = None + + def __iter__(self): + self._check_executed() + result = self.rownumber and self._rows[self.rownumber:] or self._rows + return iter(result) + + Warning = Warning + Error = Error + InterfaceError = InterfaceError + DatabaseError = DatabaseError + DataError = DataError + OperationalError = OperationalError + IntegrityError = IntegrityError + InternalError = InternalError + ProgrammingError = ProgrammingError + NotSupportedError = NotSupportedError ADDED gluon/contrib/pymysql/err.py Index: gluon/contrib/pymysql/err.py ================================================================== --- gluon/contrib/pymysql/err.py +++ gluon/contrib/pymysql/err.py @@ -0,0 +1,140 @@ +import struct + + +try: + Exception, Warning +except ImportError: + try: + from exceptions import Exception, Warning + except ImportError: + import sys + e = sys.modules['exceptions'] + Exception = e.Exception + Warning = e.Warning + +from constants import ER + +class MySQLError(Exception): + + """Exception related to operation with MySQL.""" + + +class Warning(Warning, MySQLError): + + """Exception raised for important warnings like data truncations + while inserting, etc.""" + +class Error(MySQLError): + + """Exception that is the base class of all other error exceptions + (not Warning).""" + + +class InterfaceError(Error): + + """Exception raised for errors that are related to the database + interface rather than the database itself.""" + + +class DatabaseError(Error): + + """Exception raised for errors that are related to the + database.""" + + +class DataError(DatabaseError): + + """Exception raised for errors that are due to problems with the + processed data like division by zero, numeric value out of range, + etc.""" + + +class OperationalError(DatabaseError): + + """Exception raised for errors that are related to the database's + operation and not necessarily under the control of the programmer, + e.g. an unexpected disconnect occurs, the data source name is not + found, a transaction could not be processed, a memory allocation + error occurred during processing, etc.""" + + +class IntegrityError(DatabaseError): + + """Exception raised when the relational integrity of the database + is affected, e.g. a foreign key check fails, duplicate key, + etc.""" + + +class InternalError(DatabaseError): + + """Exception raised when the database encounters an internal + error, e.g. the cursor is not valid anymore, the transaction is + out of sync, etc.""" + + +class ProgrammingError(DatabaseError): + + """Exception raised for programming errors, e.g. table not found + or already exists, syntax error in the SQL statement, wrong number + of parameters specified, etc.""" + + +class NotSupportedError(DatabaseError): + + """Exception raised in case a method or database API was used + which is not supported by the database, e.g. requesting a + .rollback() on a connection that does not support transaction or + has transactions turned off.""" + + +error_map = {} + +def _map_error(exc, *errors): + for error in errors: + error_map[error] = exc + +_map_error(ProgrammingError, ER.DB_CREATE_EXISTS, ER.SYNTAX_ERROR, + ER.PARSE_ERROR, ER.NO_SUCH_TABLE, ER.WRONG_DB_NAME, + ER.WRONG_TABLE_NAME, ER.FIELD_SPECIFIED_TWICE, + ER.INVALID_GROUP_FUNC_USE, ER.UNSUPPORTED_EXTENSION, + ER.TABLE_MUST_HAVE_COLUMNS, ER.CANT_DO_THIS_DURING_AN_TRANSACTION) +_map_error(DataError, ER.WARN_DATA_TRUNCATED, ER.WARN_NULL_TO_NOTNULL, + ER.WARN_DATA_OUT_OF_RANGE, ER.NO_DEFAULT, ER.PRIMARY_CANT_HAVE_NULL, + ER.DATA_TOO_LONG, ER.DATETIME_FUNCTION_OVERFLOW) +_map_error(IntegrityError, ER.DUP_ENTRY, ER.NO_REFERENCED_ROW, + ER.NO_REFERENCED_ROW_2, ER.ROW_IS_REFERENCED, ER.ROW_IS_REFERENCED_2, + ER.CANNOT_ADD_FOREIGN) +_map_error(NotSupportedError, ER.WARNING_NOT_COMPLETE_ROLLBACK, + ER.NOT_SUPPORTED_YET, ER.FEATURE_DISABLED, ER.UNKNOWN_STORAGE_ENGINE) + +del _map_error, ER + + +def _get_error_info(data): + errno = struct.unpack('<h', data[1:3])[0] + if data[3] == "#": + # version 4.1 + sqlstate = data[4:9].decode("utf8") + errorvalue = data[9:].decode("utf8") + return (errno, sqlstate, errorvalue) + else: + # version 4.0 + return (errno, None, data[3:].decode("utf8")) + +def _check_mysql_exception(errinfo): + errno, sqlstate, errorvalue = errinfo + errorclass = error_map.get(errno, None) + if errorclass: + raise errorclass, (errno,errorvalue) + + # couldn't find the right error number + raise InternalError, (errno, errorvalue) + +def raise_mysql_exception(data): + errinfo = _get_error_info(data) + _check_mysql_exception(errinfo) + + + + + ADDED gluon/contrib/pymysql/tests/__init__.py Index: gluon/contrib/pymysql/tests/__init__.py ================================================================== --- gluon/contrib/pymysql/tests/__init__.py +++ gluon/contrib/pymysql/tests/__init__.py @@ -0,0 +1,7 @@ +from pymysql.tests.test_issues import * +from pymysql.tests.test_example import * +from pymysql.tests.test_basic import * + +if __name__ == "__main__": + import unittest + unittest.main() ADDED gluon/contrib/pymysql/tests/base.py Index: gluon/contrib/pymysql/tests/base.py ================================================================== --- gluon/contrib/pymysql/tests/base.py +++ gluon/contrib/pymysql/tests/base.py @@ -0,0 +1,19 @@ +import pymysql +import unittest + +class PyMySQLTestCase(unittest.TestCase): + databases = [ + {"host":"localhost","user":"root", + "passwd":"","db":"test_pymysql", "use_unicode": True}, + {"host":"localhost","user":"root","passwd":"","db":"test_pymysql2"}] + + def setUp(self): + self.connections = [] + + for params in self.databases: + self.connections.append(pymysql.connect(**params)) + + def tearDown(self): + for connection in self.connections: + connection.close() + ADDED gluon/contrib/pymysql/tests/test_basic.py Index: gluon/contrib/pymysql/tests/test_basic.py ================================================================== --- gluon/contrib/pymysql/tests/test_basic.py +++ gluon/contrib/pymysql/tests/test_basic.py @@ -0,0 +1,141 @@ +from pymysql.tests import base +from pymysql import util + +import time +import datetime + +class TestConversion(base.PyMySQLTestCase): + def test_datatypes(self): + """ test every data type """ + conn = self.connections[0] + c = conn.cursor() + c.execute("create table test_datatypes (b bit, i int, l bigint, f real, s varchar(32), u varchar(32), bb blob, d date, dt datetime, ts timestamp, td time, t time, st datetime)") + try: + # insert values + v = (True, -3, 123456789012, 5.7, "hello'\" world", u"Espa\xc3\xb1ol", "binary\x00data".encode(conn.charset), datetime.date(1988,2,2), datetime.datetime.now(), datetime.timedelta(5,6), datetime.time(16,32), time.localtime()) + c.execute("insert into test_datatypes (b,i,l,f,s,u,bb,d,dt,td,t,st) values (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)", v) + c.execute("select b,i,l,f,s,u,bb,d,dt,td,t,st from test_datatypes") + r = c.fetchone() + self.assertEqual(util.int2byte(1), r[0]) + self.assertEqual(v[1:8], r[1:8]) + # mysql throws away microseconds so we need to check datetimes + # specially. additionally times are turned into timedeltas. + self.assertEqual(datetime.datetime(*v[8].timetuple()[:6]), r[8]) + self.assertEqual(v[9], r[9]) # just timedeltas + self.assertEqual(datetime.timedelta(0, 60 * (v[10].hour * 60 + v[10].minute)), r[10]) + self.assertEqual(datetime.datetime(*v[-1][:6]), r[-1]) + + c.execute("delete from test_datatypes") + + # check nulls + c.execute("insert into test_datatypes (b,i,l,f,s,u,bb,d,dt,td,t,st) values (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)", [None] * 12) + c.execute("select b,i,l,f,s,u,bb,d,dt,td,t,st from test_datatypes") + r = c.fetchone() + self.assertEqual(tuple([None] * 12), r) + + c.execute("delete from test_datatypes") + + # check sequence type + c.execute("insert into test_datatypes (i, l) values (2,4), (6,8), (10,12)") + c.execute("select l from test_datatypes where i in %s order by i", ((2,6),)) + r = c.fetchall() + self.assertEqual(((4,),(8,)), r) + finally: + c.execute("drop table test_datatypes") + + def test_dict(self): + """ test dict escaping """ + conn = self.connections[0] + c = conn.cursor() + c.execute("create table test_dict (a integer, b integer, c integer)") + try: + c.execute("insert into test_dict (a,b,c) values (%(a)s, %(b)s, %(c)s)", {"a":1,"b":2,"c":3}) + c.execute("select a,b,c from test_dict") + self.assertEqual((1,2,3), c.fetchone()) + finally: + c.execute("drop table test_dict") + + def test_big_blob(self): + """ test tons of data """ + conn = self.connections[0] + c = conn.cursor() + c.execute("create table test_big_blob (b blob)") + try: + data = "pymysql" * 1024 + c.execute("insert into test_big_blob (b) values (%s)", (data,)) + c.execute("select b from test_big_blob") + self.assertEqual(data.encode(conn.charset), c.fetchone()[0]) + finally: + c.execute("drop table test_big_blob") + +class TestCursor(base.PyMySQLTestCase): + # this test case does not work quite right yet, however, + # we substitute in None for the erroneous field which is + # compatible with the DB-API 2.0 spec and has not broken + # any unit tests for anything we've tried. + + #def test_description(self): + # """ test description attribute """ + # # result is from MySQLdb module + # r = (('Host', 254, 11, 60, 60, 0, 0), + # ('User', 254, 16, 16, 16, 0, 0), + # ('Password', 254, 41, 41, 41, 0, 0), + # ('Select_priv', 254, 1, 1, 1, 0, 0), + # ('Insert_priv', 254, 1, 1, 1, 0, 0), + # ('Update_priv', 254, 1, 1, 1, 0, 0), + # ('Delete_priv', 254, 1, 1, 1, 0, 0), + # ('Create_priv', 254, 1, 1, 1, 0, 0), + # ('Drop_priv', 254, 1, 1, 1, 0, 0), + # ('Reload_priv', 254, 1, 1, 1, 0, 0), + # ('Shutdown_priv', 254, 1, 1, 1, 0, 0), + # ('Process_priv', 254, 1, 1, 1, 0, 0), + # ('File_priv', 254, 1, 1, 1, 0, 0), + # ('Grant_priv', 254, 1, 1, 1, 0, 0), + # ('References_priv', 254, 1, 1, 1, 0, 0), + # ('Index_priv', 254, 1, 1, 1, 0, 0), + # ('Alter_priv', 254, 1, 1, 1, 0, 0), + # ('Show_db_priv', 254, 1, 1, 1, 0, 0), + # ('Super_priv', 254, 1, 1, 1, 0, 0), + # ('Create_tmp_table_priv', 254, 1, 1, 1, 0, 0), + # ('Lock_tables_priv', 254, 1, 1, 1, 0, 0), + # ('Execute_priv', 254, 1, 1, 1, 0, 0), + # ('Repl_slave_priv', 254, 1, 1, 1, 0, 0), + # ('Repl_client_priv', 254, 1, 1, 1, 0, 0), + # ('Create_view_priv', 254, 1, 1, 1, 0, 0), + # ('Show_view_priv', 254, 1, 1, 1, 0, 0), + # ('Create_routine_priv', 254, 1, 1, 1, 0, 0), + # ('Alter_routine_priv', 254, 1, 1, 1, 0, 0), + # ('Create_user_priv', 254, 1, 1, 1, 0, 0), + # ('Event_priv', 254, 1, 1, 1, 0, 0), + # ('Trigger_priv', 254, 1, 1, 1, 0, 0), + # ('ssl_type', 254, 0, 9, 9, 0, 0), + # ('ssl_cipher', 252, 0, 65535, 65535, 0, 0), + # ('x509_issuer', 252, 0, 65535, 65535, 0, 0), + # ('x509_subject', 252, 0, 65535, 65535, 0, 0), + # ('max_questions', 3, 1, 11, 11, 0, 0), + # ('max_updates', 3, 1, 11, 11, 0, 0), + # ('max_connections', 3, 1, 11, 11, 0, 0), + # ('max_user_connections', 3, 1, 11, 11, 0, 0)) + # conn = self.connections[0] + # c = conn.cursor() + # c.execute("select * from mysql.user") + # + # self.assertEqual(r, c.description) + + def test_fetch_no_result(self): + """ test a fetchone() with no rows """ + conn = self.connections[0] + c = conn.cursor() + c.execute("create table test_nr (b varchar(32))") + try: + data = "pymysql" + c.execute("insert into test_nr (b) values (%s)", (data,)) + self.assertEqual(None, c.fetchone()) + finally: + c.execute("drop table test_nr") + +__all__ = ["TestConversion","TestCursor"] + +if __name__ == "__main__": + import unittest + unittest.main() ADDED gluon/contrib/pymysql/tests/test_example.py Index: gluon/contrib/pymysql/tests/test_example.py ================================================================== --- gluon/contrib/pymysql/tests/test_example.py +++ gluon/contrib/pymysql/tests/test_example.py @@ -0,0 +1,32 @@ +import pymysql +from pymysql.tests import base + +class TestExample(base.PyMySQLTestCase): + def test_example(self): + conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='', db='mysql') + + + cur = conn.cursor() + + cur.execute("SELECT Host,User FROM user") + + # print cur.description + + # r = cur.fetchall() + # print r + # ...or... + u = False + + for r in cur.fetchall(): + u = u or conn.user in r + + self.assertTrue(u) + + cur.close() + conn.close() + +__all__ = ["TestExample"] + +if __name__ == "__main__": + import unittest + unittest.main() ADDED gluon/contrib/pymysql/tests/test_issues.py Index: gluon/contrib/pymysql/tests/test_issues.py ================================================================== --- gluon/contrib/pymysql/tests/test_issues.py +++ gluon/contrib/pymysql/tests/test_issues.py @@ -0,0 +1,240 @@ +import pymysql +from pymysql.tests import base + +import sys + +try: + import imp + reload = imp.reload +except AttributeError: + pass + +import datetime + +class TestOldIssues(base.PyMySQLTestCase): + def test_issue_3(self): + """ undefined methods datetime_or_None, date_or_None """ + conn = self.connections[0] + c = conn.cursor() + c.execute("create table issue3 (d date, t time, dt datetime, ts timestamp)") + try: + c.execute("insert into issue3 (d, t, dt, ts) values (%s,%s,%s,%s)", (None, None, None, None)) + c.execute("select d from issue3") + self.assertEqual(None, c.fetchone()[0]) + c.execute("select t from issue3") + self.assertEqual(None, c.fetchone()[0]) + c.execute("select dt from issue3") + self.assertEqual(None, c.fetchone()[0]) + c.execute("select ts from issue3") + self.assertTrue(isinstance(c.fetchone()[0], datetime.datetime)) + finally: + c.execute("drop table issue3") + + def test_issue_4(self): + """ can't retrieve TIMESTAMP fields """ + conn = self.connections[0] + c = conn.cursor() + c.execute("create table issue4 (ts timestamp)") + try: + c.execute("insert into issue4 (ts) values (now())") + c.execute("select ts from issue4") + self.assertTrue(isinstance(c.fetchone()[0], datetime.datetime)) + finally: + c.execute("drop table issue4") + + def test_issue_5(self): + """ query on information_schema.tables fails """ + con = self.connections[0] + cur = con.cursor() + cur.execute("select * from information_schema.tables") + + def test_issue_6(self): + """ exception: TypeError: ord() expected a character, but string of length 0 found """ + conn = pymysql.connect(host="localhost",user="root",passwd="",db="mysql") + c = conn.cursor() + c.execute("select * from user") + conn.close() + + def test_issue_8(self): + """ Primary Key and Index error when selecting data """ + conn = self.connections[0] + c = conn.cursor() + c.execute("""CREATE TABLE `test` (`station` int(10) NOT NULL DEFAULT '0', `dh` +datetime NOT NULL DEFAULT '0000-00-00 00:00:00', `echeance` int(1) NOT NULL +DEFAULT '0', `me` double DEFAULT NULL, `mo` double DEFAULT NULL, PRIMARY +KEY (`station`,`dh`,`echeance`)) ENGINE=MyISAM DEFAULT CHARSET=latin1;""") + try: + self.assertEqual(0, c.execute("SELECT * FROM test")) + c.execute("ALTER TABLE `test` ADD INDEX `idx_station` (`station`)") + self.assertEqual(0, c.execute("SELECT * FROM test")) + finally: + c.execute("drop table test") + + def test_issue_9(self): + """ sets DeprecationWarning in Python 2.6 """ + try: + reload(pymysql) + except DeprecationWarning: + self.fail() + + def test_issue_10(self): + """ Allocate a variable to return when the exception handler is permissive """ + conn = self.connections[0] + conn.errorhandler = lambda cursor, errorclass, errorvalue: None + cur = conn.cursor() + cur.execute( "create table t( n int )" ) + cur.execute( "create table t( n int )" ) + + def test_issue_13(self): + """ can't handle large result fields """ + conn = self.connections[0] + cur = conn.cursor() + cur.execute("create table issue13 (t text)") + try: + # ticket says 18k + size = 18*1024 + cur.execute("insert into issue13 (t) values (%s)", ("x" * size,)) + cur.execute("select t from issue13") + # use assert_ so that obscenely huge error messages don't print + r = cur.fetchone()[0] + self.assert_("x" * size == r) + finally: + cur.execute("drop table issue13") + + def test_issue_14(self): + """ typo in converters.py """ + self.assertEqual('1', pymysql.converters.escape_item(1, "utf8")) + self.assertEqual('1', pymysql.converters.escape_item(1L, "utf8")) + + self.assertEqual('1', pymysql.converters.escape_object(1)) + self.assertEqual('1', pymysql.converters.escape_object(1L)) + + def test_issue_15(self): + """ query should be expanded before perform character encoding """ + conn = self.connections[0] + c = conn.cursor() + c.execute("create table issue15 (t varchar(32))") + try: + c.execute("insert into issue15 (t) values (%s)", (u'\xe4\xf6\xfc')) + c.execute("select t from issue15") + self.assertEqual(u'\xe4\xf6\xfc', c.fetchone()[0]) + finally: + c.execute("drop table issue15") + + def test_issue_16(self): + """ Patch for string and tuple escaping """ + conn = self.connections[0] + c = conn.cursor() + c.execute("create table issue16 (name varchar(32) primary key, email varchar(32))") + try: + c.execute("insert into issue16 (name, email) values ('pete', 'floydophone')") + c.execute("select email from issue16 where name=%s", ("pete",)) + self.assertEqual("floydophone", c.fetchone()[0]) + finally: + c.execute("drop table issue16") + + def test_issue_17(self): + """ could not connect mysql use passwod """ + conn = self.connections[0] + host = self.databases[0]["host"] + db = self.databases[0]["db"] + c = conn.cursor() + # grant access to a table to a user with a password + try: + c.execute("create table issue17 (x varchar(32) primary key)") + c.execute("insert into issue17 (x) values ('hello, world!')") + c.execute("grant all privileges on %s.issue17 to 'issue17user'@'%%' identified by '1234'" % db) + conn.commit() + + conn2 = pymysql.connect(host=host, user="issue17user", passwd="1234", db=db) + c2 = conn2.cursor() + c2.execute("select x from issue17") + self.assertEqual("hello, world!", c2.fetchone()[0]) + finally: + c.execute("drop table issue17") + +def _uni(s, e): + # hack for py3 + if sys.version_info[0] > 2: + return unicode(bytes(s, sys.getdefaultencoding()), e) + else: + return unicode(s, e) + +class TestNewIssues(base.PyMySQLTestCase): + def test_issue_34(self): + try: + pymysql.connect(host="localhost", port=1237, user="root") + self.fail() + except pymysql.OperationalError, e: + self.assertEqual(2003, e.args[0]) + except: + self.fail() + + def test_issue_33(self): + conn = pymysql.connect(host="localhost", user="root", db=self.databases[0]["db"], charset="utf8") + c = conn.cursor() + try: + c.execute(_uni("create table hei\xc3\x9fe (name varchar(32))", "utf8")) + c.execute(_uni("insert into hei\xc3\x9fe (name) values ('Pi\xc3\xb1ata')", "utf8")) + c.execute(_uni("select name from hei\xc3\x9fe", "utf8")) + self.assertEqual(_uni("Pi\xc3\xb1ata","utf8"), c.fetchone()[0]) + finally: + c.execute(_uni("drop table hei\xc3\x9fe", "utf8")) + + # Will fail without manual intervention: + #def test_issue_35(self): + # + # conn = self.connections[0] + # c = conn.cursor() + # print "sudo killall -9 mysqld within the next 10 seconds" + # try: + # c.execute("select sleep(10)") + # self.fail() + # except pymysql.OperationalError, e: + # self.assertEqual(2013, e.args[0]) + + def test_issue_36(self): + conn = self.connections[0] + c = conn.cursor() + # kill connections[0] + original_count = c.execute("show processlist") + kill_id = None + for id,user,host,db,command,time,state,info in c.fetchall(): + if info == "show processlist": + kill_id = id + break + # now nuke the connection + conn.kill(kill_id) + # make sure this connection has broken + try: + c.execute("show tables") + self.fail() + except: + pass + # check the process list from the other connection + self.assertEqual(original_count - 1, self.connections[1].cursor().execute("show processlist")) + del self.connections[0] + + def test_issue_37(self): + conn = self.connections[0] + c = conn.cursor() + self.assertEqual(1, c.execute("SELECT @foo")) + self.assertEqual((None,), c.fetchone()) + self.assertEqual(0, c.execute("SET @foo = 'bar'")) + c.execute("set @foo = 'bar'") + + def test_issue_38(self): + conn = self.connections[0] + c = conn.cursor() + datum = "a" * 1024 * 1023 # reduced size for most default mysql installs + + try: + c.execute("create table issue38 (id integer, data mediumblob)") + c.execute("insert into issue38 values (1, %s)", datum) + finally: + c.execute("drop table issue38") +__all__ = ["TestOldIssues", "TestNewIssues"] + +if __name__ == "__main__": + import unittest + unittest.main() ADDED gluon/contrib/pymysql/times.py Index: gluon/contrib/pymysql/times.py ================================================================== --- gluon/contrib/pymysql/times.py +++ gluon/contrib/pymysql/times.py @@ -0,0 +1,16 @@ +from time import localtime +from datetime import date, datetime, time, timedelta + +Date = date +Time = time +TimeDelta = timedelta +Timestamp = datetime + +def DateFromTicks(ticks): + return date(*localtime(ticks)[:3]) + +def TimeFromTicks(ticks): + return time(*localtime(ticks)[3:6]) + +def TimestampFromTicks(ticks): + return datetime(*localtime(ticks)[:6]) ADDED gluon/contrib/pymysql/util.py Index: gluon/contrib/pymysql/util.py ================================================================== --- gluon/contrib/pymysql/util.py +++ gluon/contrib/pymysql/util.py @@ -0,0 +1,19 @@ +import struct + +def byte2int(b): + if isinstance(b, int): + return b + else: + return struct.unpack("!B", b)[0] + +def int2byte(i): + return struct.pack("!B", i) + +def join_bytes(bs): + if len(bs) == 0: + return "" + else: + rv = bs[0] + for b in bs[1:]: + rv += b + return rv ADDED gluon/contrib/pyrtf/Constants.py Index: gluon/contrib/pyrtf/Constants.py ================================================================== --- gluon/contrib/pyrtf/Constants.py +++ gluon/contrib/pyrtf/Constants.py @@ -0,0 +1,157 @@ +class ViewKind : + """An integer (0-5) that represents the view mode of the document.""" + + NONE = 0 + PageLayout = 1 + Outline = 2 + MasterDocument = 3 + Normal = 4 + OnlineLayout = 5 + + DEFAULT = PageLayout + + def _IsValid( cls, value ) : + return value in [ 0, 1, 2, 3, 4, 5 ] + IsValid = classmethod( _IsValid ) + +class ViewScale : + """Zoom level of the document; the N argument is a value representing a percentage (the default is 100).""" + + def _IsValid( cls, value ) : + return value is None or (0 < value < 101) + IsValid = classmethod( _IsValid ) + +class ViewZoomKind : + """An integer (0 to 2) that represents the zoom kind of the document.""" + + NONE = 0 + FullPage = 1 + BestFit = 2 + + def _IsValid( cls, value ) : + return value in [ None, 0, 1, 2 ] + IsValid = classmethod( _IsValid ) + + +class Languages : + NoLanguage = 1024 + Albanian = 1052 + Arabic = 1025 + Bahasa = 1057 + BelgianDutch = 2067 + BelgianFrench = 2060 + BrazilianPortuguese = 1046 + Bulgarian = 1026 + Catalan = 1027 + CroatoSerbianLatin = 1050 + Czech = 1029 + Danish = 1030 + Dutch = 1043 + EnglishAustralian = 3081 + EnglishUK = 2057 + EnglishUS = 1033 + Finnish = 1035 + French = 1036 + FrenchCanadian = 3084 + German = 1031 + Greek = 1032 + Hebrew = 1037 + Hungarian = 1038 + Icelandic = 1039 + Italian = 1040 + Japanese = 1041 + Korean = 1042 + NorwegianBokmal = 1044 + NorwegianNynorsk = 2068 + Polish = 1045 + Portuguese = 2070 + RhaetoRomanic = 1047 + Romanian = 1048 + Russian = 1049 + SerboCroatianCyrillic = 2074 + SimplifiedChinese = 2052 + Slovak = 1051 + SpanishCastilian = 1034 + SpanishMexican = 2058 + Swedish = 1053 + SwissFrench = 4108 + SwissGerman = 2055 + SwissItalian = 2064 + Thai = 1054 + TraditionalChinese = 1028 + Turkish = 1055 + Urdu = 1056 + SesothoSotho = 1072 + Afrikaans = 1078 + Zulu = 1077 + Xhosa = 1076 + Venda = 1075 + Tswana = 1074 + Tsonga = 1073 + FarsiPersian = 1065 + + Codes = [ 1024, + 1052, + 1025, + 1057, + 2067, + 2060, + 1046, + 1026, + 1027, + 1050, + 1029, + 1030, + 1043, + 3081, + 2057, + 1033, + 1035, + 1036, + 3084, + 1031, + 1032, + 1037, + 1038, + 1039, + 1040, + 1041, + 1042, + 1044, + 2068, + 1045, + 2070, + 1047, + 1048, + 1049, + 2074, + 2052, + 1051, + 1034, + 2058, + 1053, + 4108, + 2055, + 2064, + 1054, + 1028, + 1055, + 1056, + 1072, + 1078, + 1077, + 1076, + 1075, + 1074, + 1073, + 1065 ] + + # make it Australian as that is what I use most of the time + DEFAULT = EnglishAustralian + + def _IsValid( cls, value ) : + return value in cls.Codes + IsValid = classmethod( _IsValid ) + +if __name__ == '__main__' : + PrintHexTable() ADDED gluon/contrib/pyrtf/Elements.py Index: gluon/contrib/pyrtf/Elements.py ================================================================== --- gluon/contrib/pyrtf/Elements.py +++ gluon/contrib/pyrtf/Elements.py @@ -0,0 +1,756 @@ +from types import IntType, FloatType, LongType, StringTypes +from copy import deepcopy +from binascii import hexlify + +from Constants import * +from Styles import * + +class UnhandledParamError( Exception ) : + def __init__( self, param ) : + Exception.__init__( self, "Don't know what to do with param %s" % param ) + +# red green blue +StandardColours = Colours() +StandardColours.append( Colour( 'Black', 0, 0, 0 ) ) +StandardColours.append( Colour( 'Blue', 0, 0, 255 ) ) +StandardColours.append( Colour( 'Turquoise', 0, 255, 255 ) ) +StandardColours.append( Colour( 'Green', 0, 255, 0 ) ) +StandardColours.append( Colour( 'Pink', 255, 0, 255 ) ) +StandardColours.append( Colour( 'Red', 255, 0, 0 ) ) +StandardColours.append( Colour( 'Yellow', 255, 255, 0 ) ) +StandardColours.append( Colour( 'White', 255, 255, 255 ) ) +StandardColours.append( Colour( 'Blue Dark', 0, 0, 128 ) ) +StandardColours.append( Colour( 'Teal', 0, 128, 128 ) ) +StandardColours.append( Colour( 'Green Dark', 0, 128, 0 ) ) +StandardColours.append( Colour( 'Violet', 128, 0, 128 ) ) +StandardColours.append( Colour( 'Red Dark', 128, 0, 0 ) ) +StandardColours.append( Colour( 'Yellow Dark', 128, 128, 0 ) ) +StandardColours.append( Colour( 'Grey Dark', 128, 128, 128 ) ) +StandardColours.append( Colour( 'Grey', 192, 192, 192 ) ) + +StandardFonts = Fonts() +StandardFonts.append( Font( 'Arial' , 'swiss' , 0, 2, '020b0604020202020204' ) ) +StandardFonts.append( Font( 'Arial Black' , 'swiss' , 0, 2, '020b0a04020102020204' ) ) +StandardFonts.append( Font( 'Arial Narrow' , 'swiss' , 0, 2, '020b0506020202030204' ) ) +StandardFonts.append( Font( 'Bitstream Vera Sans Mono', 'modern', 0, 1, '020b0609030804020204' ) ) +StandardFonts.append( Font( 'Bitstream Vera Sans' , 'swiss' , 0, 2, '020b0603030804020204' ) ) +StandardFonts.append( Font( 'Bitstream Vera Serif' , 'roman' , 0, 2, '02060603050605020204' ) ) +StandardFonts.append( Font( 'Book Antiqua' , 'roman' , 0, 2, '02040602050305030304' ) ) +StandardFonts.append( Font( 'Bookman Old Style' , 'roman' , 0, 2, '02050604050505020204' ) ) +StandardFonts.append( Font( 'Castellar' , 'roman' , 0, 2, '020a0402060406010301' ) ) +StandardFonts.append( Font( 'Century Gothic' , 'swiss' , 0, 2, '020b0502020202020204' ) ) +StandardFonts.append( Font( 'Comic Sans MS' , 'script', 0, 2, '030f0702030302020204' ) ) +StandardFonts.append( Font( 'Courier New' , 'modern', 0, 1, '02070309020205020404' ) ) +StandardFonts.append( Font( 'Franklin Gothic Medium' , 'swiss' , 0, 2, '020b0603020102020204' ) ) +StandardFonts.append( Font( 'Garamond' , 'roman' , 0, 2, '02020404030301010803' ) ) +StandardFonts.append( Font( 'Georgia' , 'roman' , 0, 2, '02040502050405020303' ) ) +StandardFonts.append( Font( 'Haettenschweiler' , 'swiss' , 0, 2, '020b0706040902060204' ) ) +StandardFonts.append( Font( 'Impact' , 'swiss' , 0, 2, '020b0806030902050204' ) ) +StandardFonts.append( Font( 'Lucida Console' , 'modern', 0, 1, '020b0609040504020204' ) ) +StandardFonts.append( Font( 'Lucida Sans Unicode' , 'swiss' , 0, 2, '020b0602030504020204' ) ) +StandardFonts.append( Font( 'Microsoft Sans Serif' , 'swiss' , 0, 2, '020b0604020202020204' ) ) +StandardFonts.append( Font( 'Monotype Corsiva' , 'script', 0, 2, '03010101010201010101' ) ) +StandardFonts.append( Font( 'Palatino Linotype' , 'roman' , 0, 2, '02040502050505030304' ) ) +StandardFonts.append( Font( 'Papyrus' , 'script', 0, 2, '03070502060502030205' ) ) +StandardFonts.append( Font( 'Sylfaen' , 'roman' , 0, 2, '010a0502050306030303' ) ) +StandardFonts.append( Font( 'Symbol' , 'roman' , 2, 2, '05050102010706020507' ) ) +StandardFonts.append( Font( 'Tahoma' , 'swiss' , 0, 2, '020b0604030504040204' ) ) +StandardFonts.append( Font( 'Times New Roman' , 'roman' , 0, 2, '02020603050405020304' ) ) +StandardFonts.append( Font( 'Trebuchet MS' , 'swiss' , 0, 2, '020b0603020202020204' ) ) +StandardFonts.append( Font( 'Verdana' , 'swiss' , 0, 2, '020b0604030504040204' ) ) + +StandardFonts.Castellar.SetAlternate( StandardFonts.Georgia ) + +""" +Found the following definition at http://www.pbdr.com/vbtips/gen/convtwip.htm + +Twips are screen-independent units used to ensure that the placement and +proportion of screen elements in your screen application are the same on all +display systems. A twip is a unit of screen measurement equal to 1/20 of a +printer's point. The conversion between twips and +inches/centimeters/millimeters is as follows: + +There are approximately 1440 twips to a inch (the length of a screen item +measuring one inch when printed). + +As there are 2.54 centimeters to 1 inch, then there are approximately 567 +twips to a centimeter (the length of a screen item measuring one centimeter +when printed). + +Or in millimeters, as there are 25.4 millimeters to 1 inch, therefore there +are approximately 56.7 twips to a millimeter (the length of a screen item +measuring one millimeter when printed).""" + +# Width default is 12240, Height default is 15840 +StandardPaper = Papers() +StandardPaper.append( Paper( 'LETTER' , 1, 'Letter 8 1/2 x 11 in' , 12240, 15840 ) ) +StandardPaper.append( Paper( 'LETTERSMALL' , 2, 'Letter Small 8 1/2 x 11 in' , 12240, 15840 ) ) +StandardPaper.append( Paper( 'TABLOID' , 3, 'Tabloid 11 x 17 in' , 15840, 24480 ) ) +StandardPaper.append( Paper( 'LEDGER' , 4, 'Ledger 17 x 11 in' , 24480, 15840 ) ) +StandardPaper.append( Paper( 'LEGAL' , 5, 'Legal 8 1/2 x 14 in' , 12240, 20160 ) ) +StandardPaper.append( Paper( 'STATEMENT' , 6, 'Statement 5 1/2 x 8 1/2 in' , 7920, 12240 ) ) +StandardPaper.append( Paper( 'EXECUTIVE' , 7, 'Executive 7 1/4 x 10 1/2 in' , 10440, 15120 ) ) +StandardPaper.append( Paper( 'A3' , 8, 'A3 297 x 420 mm' , 16838, 23811 ) ) +StandardPaper.append( Paper( 'A4' , 9, 'A4 210 x 297 mm' , 11907, 16838 ) ) +StandardPaper.append( Paper( 'A4SMALL' , 10, 'A4 Small 210 x 297 mm' , 11907, 16838 ) ) +StandardPaper.append( Paper( 'A5' , 11, 'A5 148 x 210 mm' , 8391, 11907 ) ) +StandardPaper.append( Paper( 'B4' , 12, 'B4 (JIS) 250 x 354' , 14175, 20072 ) ) +StandardPaper.append( Paper( 'B5' , 13, 'B5 (JIS) 182 x 257 mm' , 10319, 14572 ) ) +StandardPaper.append( Paper( 'FOLIO' , 14, 'Folio 8 1/2 x 13 in' , 12240, 18720 ) ) +StandardPaper.append( Paper( 'QUARTO' , 15, 'Quarto 215 x 275 mm' , 12191, 15593 ) ) +StandardPaper.append( Paper( '10X14' , 16, '10x14 in' , 14400, 20160 ) ) +StandardPaper.append( Paper( '11X17' , 17, '11x17 in' , 15840, 24480 ) ) +StandardPaper.append( Paper( 'NOTE' , 18, 'Note 8 1/2 x 11 in' , 12240, 15840 ) ) +StandardPaper.append( Paper( 'ENV_9' , 19, 'Envelope #9 3 7/8 x 8 7/8' , 5580, 12780 ) ) +StandardPaper.append( Paper( 'ENV_10' , 20, 'Envelope #10 4 1/8 x 9 1/2' , 5940, 13680 ) ) +StandardPaper.append( Paper( 'ENV_11' , 21, 'Envelope #11 4 1/2 x 10 3/8' , 6480, 14940 ) ) +StandardPaper.append( Paper( 'ENV_12' , 22, 'Envelope #12 4 3/4 x 11' , 6840, 15840 ) ) +StandardPaper.append( Paper( 'ENV_14' , 23, 'Envelope #14 5 x 11 1/2' , 7200, 16560 ) ) +StandardPaper.append( Paper( 'CSHEET' , 24, 'C size sheet 18 x 24 in' , 29520, 34560 ) ) +StandardPaper.append( Paper( 'DSHEET' , 25, 'D size sheet 22 x 34 in' , 31680, 48960 ) ) +StandardPaper.append( Paper( 'ESHEET' , 26, 'E size sheet 34 x 44 in' , 48960, 63360 ) ) +StandardPaper.append( Paper( 'ENV_DL' , 27, 'Envelope DL 110 x 220mm' , 6237, 12474 ) ) +StandardPaper.append( Paper( 'ENV_C5' , 28, 'Envelope C5 162 x 229 mm' , 9185, 12984 ) ) +StandardPaper.append( Paper( 'ENV_C3' , 29, 'Envelope C3 324 x 458 mm' , 18371, 25969 ) ) +StandardPaper.append( Paper( 'ENV_C4' , 30, 'Envelope C4 229 x 324 mm' , 12984, 18371 ) ) +StandardPaper.append( Paper( 'ENV_C6' , 31, 'Envelope C6 114 x 162 mm' , 6464, 9185 ) ) +StandardPaper.append( Paper( 'ENV_C65' , 32, 'Envelope C65 114 x 229 mm' , 6464, 12984 ) ) +StandardPaper.append( Paper( 'ENV_B4' , 33, 'Envelope B4 250 x 353 mm' , 14175, 20015 ) ) +StandardPaper.append( Paper( 'ENV_B5' , 34, 'Envelope B5 176 x 250 mm' , 9979, 14175 ) ) +StandardPaper.append( Paper( 'ENV_B6' , 35, 'Envelope B6 176 x 125 mm' , 9979, 7088 ) ) +StandardPaper.append( Paper( 'ENV_ITALY' , 36, 'Envelope 110 x 230 mm' , 6237, 13041 ) ) +StandardPaper.append( Paper( 'ENV_MONARCH' , 37, 'Envelope Monarch 3.875 x 7.5 in' , 5580, 10800 ) ) +StandardPaper.append( Paper( 'ENV_PERSONAL' , 38, '6 3/4 Envelope 3 5/8 x 6 1/2 in' , 5220, 9360 ) ) +StandardPaper.append( Paper( 'FANFOLD_US' , 39, 'US Std Fanfold 14 7/8 x 11 in' , 21420, 15840 ) ) +StandardPaper.append( Paper( 'FANFOLD_STD_GERMAN' , 40, 'German Std Fanfold 8 1/2 x 12 in' , 12240, 17280 ) ) +StandardPaper.append( Paper( 'FANFOLD_LGL_GERMAN' , 41, 'German Legal Fanfold 8 1/2 x 13 in' , 12240, 18720 ) ) + +# +# Finally a StyleSheet in which all of this stuff is put together +# +class StyleSheet : + def __init__( self, colours=None, fonts=None ) : + + self.Colours = colours or deepcopy( StandardColours ) + self.Fonts = fonts or deepcopy( StandardFonts ) + + self.TextStyles = AttributedList() + self.ParagraphStyles = AttributedList() + +class Section( list ) : + NONE = 1 + COLUMN = 2 + PAGE = 3 + EVEN = 4 + ODD = 5 + BREAK_TYPES = [ NONE, COLUMN, PAGE, EVEN, ODD ] + + def __init__( self, paper=None, margins=None, break_type=None, headery=None, footery=None, landscape=None, first_page_number=None ) : + super( Section, self ).__init__() + + self.Paper = paper or StandardPaper.A4 + self.SetMargins( margins ) + + self.Header = [] + self.Footer = [] + self.FirstHeader = [] + self.FirstFooter = [] + + self.SetBreakType( break_type or self.NONE ) + self.SetHeaderY( headery ) + self.SetFooterY( footery ) + self.SetLandscape( landscape ) + self.SetFirstPageNumber( first_page_number ) + + def TwipsToRightMargin( self ) : + return self.Paper.Width - ( self.Margins.Left + self.Margins.Right ) + + def SetMargins( self, value ) : + self.Margins = value or MarginsPropertySet( top=1000, left=1200, bottom=1000, right=1200 ) + self.Width = self.Paper.Width - ( self.Margins.Left + self.Margins.Right ) + + def SetBreakType( self, value ) : + assert value in self.BREAK_TYPES + self.BreakType = value + return self + + def SetHeaderY( self, value ) : + self.HeaderY = value + return self + + def SetFooterY( self, value ) : + self.FooterY = value + return self + + def SetLandscape( self, value ) : + self.Landscape = False + if value : self.Landscape = True + return self + + def SetFirstPageNumber( self, value ) : + self.FirstPageNumber = value + return self + +def MakeDefaultStyleSheet( ) : + result = StyleSheet() + + NormalText = TextStyle( TextPropertySet( result.Fonts.Arial, 22 ) ) + + ps = ParagraphStyle( 'Normal', + NormalText.Copy(), + ParagraphPropertySet( space_before = 60, + space_after = 60 ) ) + result.ParagraphStyles.append( ps ) + + ps = ParagraphStyle( 'Normal Short', + NormalText.Copy() ) + result.ParagraphStyles.append( ps ) + + NormalText.TextPropertySet.SetSize( 32 ) + ps = ParagraphStyle( 'Heading 1', + NormalText.Copy(), + ParagraphPropertySet( space_before = 240, + space_after = 60 ) ) + result.ParagraphStyles.append( ps ) + + NormalText.TextPropertySet.SetSize( 24 ).SetBold( True ) + ps = ParagraphStyle( 'Heading 2', + NormalText.Copy(), + ParagraphPropertySet( space_before = 240, + space_after = 60 ) ) + result.ParagraphStyles.append( ps ) + + # Add some more in that are based on the normal template but that + # have some indenting set that makes them suitable for doing numbered + normal_numbered = result.ParagraphStyles.Normal.Copy() + normal_numbered.SetName( 'Normal Numbered' ) + normal_numbered.ParagraphPropertySet.SetFirstLineIndent( TabPropertySet.DEFAULT_WIDTH * -1 ) + normal_numbered.ParagraphPropertySet.SetLeftIndent ( TabPropertySet.DEFAULT_WIDTH ) + + result.ParagraphStyles.append( normal_numbered ) + + normal_numbered2 = result.ParagraphStyles.Normal.Copy() + normal_numbered2.SetName( 'Normal Numbered 2' ) + normal_numbered2.ParagraphPropertySet.SetFirstLineIndent( TabPropertySet.DEFAULT_WIDTH * -1 ) + normal_numbered2.ParagraphPropertySet.SetLeftIndent ( TabPropertySet.DEFAULT_WIDTH * 2 ) + + result.ParagraphStyles.append( normal_numbered2 ) + + ## LIST STYLES + for idx, indent in [ (1, TabPS.DEFAULT_WIDTH ), + (2, TabPS.DEFAULT_WIDTH * 2), + (3, TabPS.DEFAULT_WIDTH * 3) ] : + indent = TabPropertySet.DEFAULT_WIDTH + ps = ParagraphStyle( 'List %s' % idx, + TextStyle( TextPropertySet( result.Fonts.Arial, 22 ) ), + ParagraphPropertySet( space_before = 60, + space_after = 60, + first_line_indent = -indent, + left_indent = indent) ) + result.ParagraphStyles.append( ps ) + + return result + +class TAB : pass +class LINE : pass + +class RawCode : + def __init__( self, data ) : + self.Data = data + +PAGE_NUMBER = RawCode( r'{\field{\fldinst page}}' ) +TOTAL_PAGES = RawCode( r'{\field{\fldinst numpages}}' ) +SECTION_PAGES = RawCode( r'{\field{\fldinst sectionpages}}' ) +ARIAL_BULLET = RawCode( r'{\f2\'95}' ) + +def _get_jpg_dimensions( fin ): + """ + converted from: http://dev.w3.org/cvsweb/Amaya/libjpeg/rdjpgcom.c?rev=1.2 + """ + + M_SOF0 = chr( 0xC0 ) # /* Start Of Frame N */ + M_SOF1 = chr( 0xC1 ) # /* N indicates which compression process */ + M_SOF2 = chr( 0xC2 ) # /* Only SOF0-SOF2 are now in common use */ + M_SOF3 = chr( 0xC3 ) # + M_SOF5 = chr( 0xC5 ) # /* NB: codes C4 and CC are NOT SOF markers */ + M_SOF6 = chr( 0xC6 ) # + M_SOF7 = chr( 0xC7 ) # + M_SOF9 = chr( 0xC9 ) # + M_SOF10 = chr( 0xCA ) # + M_SOF11 = chr( 0xCB ) # + M_SOF13 = chr( 0xCD ) # + M_SOF14 = chr( 0xCE ) # + M_SOF15 = chr( 0xCF ) # + M_SOI = chr( 0xD8 ) # /* Start Of Image (beginning of datastream) */ + M_EOI = chr( 0xD9 ) # /* End Of Image (end of datastream) */ + + M_FF = chr( 0xFF ) + + MARKERS = [ M_SOF0, M_SOF1, M_SOF2, M_SOF3, + M_SOF5, M_SOF6, M_SOF7, M_SOF9, + M_SOF10,M_SOF11, M_SOF13, M_SOF14, + M_SOF15 ] + + def get_length() : + b1 = fin.read( 1 ) + b2 = fin.read( 1 ) + return (ord(b1) << 8) + ord(b2) + + def next_marker() : + # markers come straight after an 0xFF so skip everything + # up to the first 0xFF that we find + while fin.read(1) != M_FF : + pass + + # there can be more than one 0xFF as they can be used + # for padding so we are now looking for the first byte + # that isn't an 0xFF, this will be the marker + while True : + result = fin.read(1) + if result != M_FF : + return result + + raise Exception( 'Invalid JPEG' ) + + # BODY OF THE FUNCTION + if not ((fin.read(1) == M_FF) and (fin.read(1) == M_SOI)) : + raise Exception( 'Invalid Jpeg' ) + + while True : + marker = next_marker() + + # the marker is always followed by two bytes representing the length of the data field + length = get_length () + if length < 2 : raise Exception( "Erroneous JPEG marker length" ) + + # if it is a compression process marker then it will contain the dimension of the image + if marker in MARKERS : + # the next byte is the data precision, just skip it + fin.read(1) + + # bingo + image_height = get_length() + image_width = get_length() + return image_width, image_height + + # just skip whatever data it contains + fin.read( length - 2 ) + + raise Exception( 'Invalid JPEG, end of stream reached' ) + + +_PNG_HEADER = '\x89\x50\x4e' +def _get_png_dimensions( data ) : + if data[0:3] != _PNG_HEADER : + raise Exception( 'Invalid PNG image' ) + + width = (ord(data[18]) * 256) + (ord(data[19])) + height = (ord(data[22]) * 256) + (ord(data[23])) + return width, height + +def _get_emf_dimensions( fin ): + import struct + def get_DWORD(): + return struct.unpack("<L",fin.read(4))[0] + def get_LONG(): + return struct.unpack("<l",fin.read(4))[0] + def get_WORD(): + return struct.unpack("<H",fin.read(2))[0] + class Empty: + pass + header = Empty() + header.RecordType = get_DWORD() # Record type + header.RecordSize = get_DWORD() # Size of the record in bytes + header.BoundsLeft = get_LONG() # Left inclusive bounds + header.BoundsTop = get_LONG() # Top inclusive bounds + header.BoundsRight = get_LONG() # Right inclusive bounds + header.BoundsBottom = get_LONG() # Bottom inclusive bounds + header.FrameLeft = get_LONG() # Left side of inclusive picture frame + header.FrameTop = get_LONG() # Top side of inclusive picture frame + header.FrameRight = get_LONG() # Right side of inclusive picture frame + header.FrameBottom = get_LONG() # Bottom side of inclusive picture frame + header.Signature = get_DWORD() # Signature ID (always 0x464D4520) + header.Version = get_DWORD() # Version of the metafile + header.Size = get_DWORD() # Size of the metafile in bytes + header.NumOfRecords = get_DWORD() # Number of records in the metafile + header.NumOfHandles = get_WORD() # Number of handles in the handle table + header.Reserved = get_WORD() # Not used (always 0) + header.SizeOfDescrip = get_DWORD() # Size of description string in WORDs + header.OffsOfDescrip = get_DWORD() # Offset of description string in metafile + header.NumPalEntries = get_DWORD() # Number of color palette entries + header.WidthDevPixels = get_LONG() # Width of reference device in pixels + header.HeightDevPixels = get_LONG() # Height of reference device in pixels + header.WidthDevMM = get_LONG() # Width of reference device in millimeters + header.HeightDevMM = get_LONG() # Height of reference device in millimeters + + if 0: + klist = header.__dict__.keys() + klist.sort() + for k in klist: + print "%20s:%s" % (k,header.__dict__[k]) + + dw = header.FrameRight-header.FrameLeft + dh = header.FrameBottom-header.FrameTop + + # convert from 0.01mm units to 1/72in units + return int(dw * 72.0/2540.0), int(dh * 72.0/2540.0) + +class Image( RawCode ) : + + # Need to add in the width and height in twips as it crashes + # word xp with these values. Still working out the most + # efficient way of getting these values. + # \picscalex100\picscaley100\piccropl0\piccropr0\piccropt0\piccropb0 + # picwgoal900\pichgoal281 + + PNG_LIB = 'pngblip' + JPG_LIB = 'jpegblip' + EMF_LIB = 'emfblip' + PICT_TYPES = { 'png' : PNG_LIB, + 'jpg' : JPG_LIB, + 'emf' : EMF_LIB} + + def __init__( self, infile, **kwargs ) : + + if hasattr( infile, 'read' ): + fin = infile + if 'datatype' not in kwargs.keys(): + msg = "If passing in a file object, you must also specify type='xxx' where xxx is one of %s" % self.PICT_TYPES.keys() + raise ValueError,msg + file_name = kwargs.pop('datatype') + else: + fin = file( infile, 'rb' ) + file_name = infile + + pict_type = self.PICT_TYPES[ file_name[ -3 : ].lower() ] + if pict_type == self.PNG_LIB : + width, height = _get_png_dimensions( fin.read( 100 ) ) + elif pict_type == self.JPG_LIB : + width, height = _get_jpg_dimensions( fin ) + elif pict_type == self.EMF_LIB : + width, height = _get_emf_dimensions( fin ) + + + # if user specified height or width but not both, then + # scale unspecified dimension to maintain aspect ratio + + if ('width' in kwargs) and ('height' not in kwargs): + height = int(height * float(kwargs['width'])/width) + elif ('height' in kwargs) and ('width' not in kwargs): + width = int(width * float(kwargs['height'])/height) + + width = kwargs.pop('width',width) + height = kwargs.pop('height', height) + + codes = [ pict_type, + 'picwgoal%s' % (width * 20), + 'pichgoal%s' % (height * 20) ] + # let user specify global scaling + scale = kwargs.pop('scale',100) + + for kwarg, code, default in [ ( 'scale_x', 'scalex', scale ), + ( 'scale_y', 'scaley', scale ), + ( 'crop_left', 'cropl', '0' ), + ( 'crop_right', 'cropr', '0' ), + ( 'crop_top', 'cropt', '0' ), + ( 'crop_bottom', 'cropb', '0' ) ] : + codes.append( 'pic%s%s' % ( code, kwargs.pop( kwarg, default ) ) ) + + + # reset back to the start of the file to get all of it and now + # turn it into hex. + fin.seek( 0, 0 ) + image = hexlify( fin.read() ) + fin.close() + data = [] + for i in range( 0, len( image ), 128 ) : + data.append( image[ i : i + 128 ] ) + + data = r'{\pict{\%s}%s}' % ( '\\'.join( codes ), '\n'.join( data ) ) + RawCode.__init__( self, data ) + + def ToRawCode( self, var_name ) : + return '%s = RawCode( """%s""" )' % ( var_name, self.Data ) + +class Text : + def __init__( self, *params ) : + self.Data = None + self.Style = None + self.Properties = None + self.Shading = None + + for param in params : + if isinstance( param, TextStyle ) : self.Style = param + elif isinstance( param, TextPS ) : self.Properties = param + elif isinstance( param, ShadingPS ) : self.Shading = param + else : + # otherwise let the rendering custom handler sort it out itself + self.Data = param + + def SetData( self, value ) : + self.Data = value + +class Inline( list ) : + def __init__( self, *params ) : + super( Inline, self ).__init__() + + self.Style = None + self.Properties = None + self.Shading = None + + self._append = super( Inline, self ).append + + for param in params : + if isinstance( param, TextStyle ) : self.Style = param + elif isinstance( param, TextPS ) : self.Properties = param + elif isinstance( param, ShadingPS ) : self.Shading = param + else : + # otherwise we add to it to our list of elements and let + # the rendering custom handler sort it out itself. + self.append( param ) + + def append( self, *params ) : + # filter out any that are explicitly None + [ self._append( param ) for param in params if param is not None ] + +class Paragraph( list ) : + def __init__( self, *params ) : + super( Paragraph, self ).__init__() + + self.Style = None + self.Properties = None + self.Frame = None + self.Shading = None + + self._append = super( Paragraph, self ).append + + for param in params : + if isinstance( param, ParagraphStyle ) : self.Style = param + elif isinstance( param, ParagraphPS ) : self.Properties = param + elif isinstance( param, FramePS ) : self.Frame = param + elif isinstance( param, ShadingPS ) : self.Shading = param + else : + # otherwise we add to it to our list of elements and let + # the rendering custom handler sort it out itself. + self.append( param ) + + def append( self, *params ) : + # filter out any that are explicitly None + [ self._append( param ) for param in params if param is not None ] + + def insert( self, index, value ) : + if value is not None : + super( Paragraph, self ).insert( index, value ) + +class Table : + LEFT = 1 + RIGHT = 2 + CENTER = 3 + ALIGNMENT = [ LEFT, RIGHT, CENTER ] + + NO_WRAPPING = 1 + WRAP_AROUND = 2 + WRAPPING = [ NO_WRAPPING, WRAP_AROUND ] + + # trrh height of row, 0 means automatically adjust, use negative for an absolute + # trgaph is half of the space between a table cell in width, reduce this one + # to get a really tiny column + + def __init__( self, *column_widths, **kwargs ) : + + self.Rows = [] + + self.SetAlignment ( kwargs.pop( 'alignment', self.LEFT ) ) + self.SetLeftOffset ( kwargs.pop( 'left_offset', None ) ) + self.SetGapBetweenCells( kwargs.pop( 'gap_between_cells', None ) ) + self.SetColumnWidths ( *column_widths ) + + assert not kwargs, 'invalid keyword args %s' % kwargs + + def SetAlignment( self, value ) : + assert value is None or value in self.ALIGNMENT + self.Alignment = value or self.LEFT + return self + + def SetLeftOffset( self, value ) : + self.LeftOffset = value + return self + + def SetGapBetweenCells( self, value ) : + self.GapBetweenCells = value + return self + + def SetColumnWidths( self, *column_widths ) : + self.ColumnWidths = column_widths + self.ColumnCount = len( column_widths ) + return self + + def AddRow( self, *cells ) : + height = None + if isinstance( cells[ 0 ], (IntType, FloatType, LongType) ): + height = int( cells[ 0 ] ) + cells = cells[ 1 : ] + + # make sure all of the spans add up to the number of columns + # otherwise the table will get corrupted + if self.ColumnCount != sum( [ cell.Span for cell in cells ] ) : + raise Exception( 'ColumnCount != the total of this row\'s cell.Spans.' ) + + self.Rows.append( ( height, cells ) ) + + append = AddRow + +class Cell( list ) : + + """ + \clvertalt Text is top-aligned in cell (the default). + \clvertalc Text is centered vertically in cell. + \clvertalb Text is bottom-aligned in cell. + \cltxlrtb Vertical text aligned left (direction bottom up). + \cltxtbrl Vertical text aligned right (direction top down). + """ + + ALIGN_TOP = 1 + ALIGN_CENTER = 2 + ALIGN_BOTTOM = 3 + + FLOW_LR_TB = 1 + FLOW_RL_TB = 2 + FLOW_LR_BT = 3 + FLOW_VERTICAL_LR_TB = 4 + FLOW_VERTICAL_TB_RL = 5 + + def __init__( self, *params, **kwargs ) : + super( Cell, self ).__init__() + + self.SetFrame ( None ) + self.SetMargins( None ) + + self.SetAlignment( kwargs.get( 'alignment', self.ALIGN_TOP ) ) + self.SetFlow ( kwargs.get( 'flow' , self.FLOW_LR_TB ) ) + self.SetSpan ( kwargs.get( 'span', 1 ) ) + + self.SetStartVerticalMerge( kwargs.get( 'start_vertical_merge', False ) ) + self.SetVerticalMerge ( kwargs.get( 'vertical_merge', False ) ) + + self._append = super( Cell, self ).append + + for param in params : + if isinstance( param, StringType ) : self.append ( param ) + elif isinstance( param, Paragraph ) : self.append ( param ) + elif isinstance( param, FramePS ) : self.SetFrame ( param ) + elif isinstance( param, MarginsPS ) : self.SetMargins( param ) + + def SetFrame( self, value ) : + self.Frame = value + return self + + def SetMargins( self, value ) : + self.Margins = value + return self + + def SetAlignment( self, value ) : + assert value in [ self.ALIGN_TOP, self.ALIGN_CENTER, self.ALIGN_BOTTOM ] #, self.ALIGN_TEXT_TOP_DOWN, self.ALIGN_TEXT_BOTTOM_UP ] + self.Alignment = value + + def SetFlow( self, value ) : + assert value in [ self.FLOW_LR_TB, self.FLOW_RL_TB, self.FLOW_LR_BT, self.FLOW_VERTICAL_LR_TB, self.FLOW_VERTICAL_TB_RL ] + self.Flow = value + + def SetSpan( self, value ) : + # must be a positive integer + self.Span = int( max( value, 1 ) ) + return self + + def SetStartVerticalMerge( self, value ) : + self.StartVerticalMerge = False + if value : + self.StartVerticalMerge = True + return self + + def SetVerticalMerge( self, value ) : + self.VerticalMerge = False + if value : + self.VerticalMerge = True + return self + + def append( self, *params ) : + [ self._append( param ) for param in params ] + +class Document : + def __init__( self, style_sheet=None, default_language=None, view_kind=None, view_zoom_kind=None, view_scale=None ) : + self.StyleSheet = style_sheet or MakeDefaultStyleSheet() + self.Sections = AttributedList( Section ) + + self.SetTitle( None ) + + self.DefaultLanguage = default_language or Languages.DEFAULT + self.ViewKind = view_kind or ViewKind.DEFAULT + self.ViewZoomKind = view_zoom_kind + self.ViewScale = view_scale + + def NewSection( self, *params, **kwargs ) : + result = Section( *params, **kwargs ) + self.Sections.append( result ) + return result + + def SetTitle( self, value ) : + self.Title = value + return self + + def Copy( self ) : + result = Document( style_sheet = self.StyleSheet.Copy(), + default_language = self.DefaultLanguage, + view_kind = self.ViewKind, + view_zoom_kind = self.ViewZoomKind, + view_scale = self.ViewScale ) + result.SetTitle( self.Title ) + result.Sections = self.Sections.Copy() + + return result + +def TEXT( *params, **kwargs ) : + text_props = TextPropertySet() + text_props.SetFont ( kwargs.get( 'font', None ) ) + text_props.SetSize ( kwargs.get( 'size', None ) ) + text_props.SetBold ( kwargs.get( 'bold', False ) ) + text_props.SetItalic ( kwargs.get( 'italic', False ) ) + text_props.SetUnderline( kwargs.get( 'underline', False ) ) + text_props.SetColour ( kwargs.get( 'colour', None ) ) + + if len( params ) == 1 : + return Text( params[ 0 ], text_props ) + + result = Inline( text_props ) + apply( result.append, params ) + return result + +def B( *params ) : + text_props = TextPropertySet( bold=True ) + + if len( params ) == 1 : + return Text( params[ 0 ], text_props ) + + result = Inline( text_props ) + apply( result.append, params ) + return result + +def I( *params ) : + text_props = TextPropertySet( italic=True ) + + if len( params ) == 1 : + return Text( params[ 0 ], text_props ) + + result = Inline( text_props ) + apply( result.append, params ) + return result + +def U( *params ) : + text_props = TextPropertySet( underline=True ) + + if len( params ) == 1 : + return Text( params[ 0 ], text_props ) + + result = Inline( text_props ) + apply( result.append, params ) + return result ADDED gluon/contrib/pyrtf/PropertySets.py Index: gluon/contrib/pyrtf/PropertySets.py ================================================================== --- gluon/contrib/pyrtf/PropertySets.py +++ gluon/contrib/pyrtf/PropertySets.py @@ -0,0 +1,488 @@ +""" +PropertySets group common attributes together, each property set is used to control a specific part of the rendering. + +PropertySets can be used in different elements of the document. + +For example the FramePropertySet is used in paragraphs, tables, cells, etc. + +The TextPropertySet can be used for text or in a Paragraph Style. + +""" + +from types import StringType +from copy import deepcopy + + +# +# We need some basic Type like fonts, colours and paper definitions +# +def MakeAttributeName( value ) : + assert value and type( value ) is StringType + value = value.replace( ' ', '' ) + return value + +class AttributedList( list ) : + def __init__( self, accepted_type=None ) : + super( AttributedList, self ).__init__() + self.AcceptedType = accepted_type + self._append = super( AttributedList, self ).append + + def append( self, *values ) : + for value in values : + if self.AcceptedType : assert isinstance( value, self.AcceptedType ) + + self._append( value ) + + name = getattr( value, 'Name', None ) + if name : + name = MakeAttributeName( value.Name ) + setattr( self, name, value ) + + def __deepcopy__( self, memo ) : + result = self.__class__() + result.append( *self[:] ) + return result + +class Colour : + def __init__( self, name, red, green, blue ) : + self.SetName ( name ) + self.SetRed ( red ) + self.SetGreen( green ) + self.SetBlue ( blue ) + + def SetName( self, value ) : + self.Name = value + return self + + def SetRed( self, value ) : + self.Red = value + return self + + def SetGreen( self, value ) : + self.Green = value + return self + + def SetBlue( self, value ) : + self.Blue = value + return self + +class Colours( AttributedList ) : + def __init__( self ) : + super( Colours, self ).__init__( Colour ) + +class Font : + def __init__( self, name, family, character_set = 0, pitch = None, panose = None, alternate = None ) : + self.SetName ( name ) + self.SetFamily ( family ) + self.SetCharacterSet( character_set ) + self.SetPitch ( pitch ) + self.SetPanose ( panose ) + self.SetAlternate ( alternate ) + + def SetName( self, value ) : + self.Name = value + return self + + def SetFamily( self, value ) : + self.Family = value + return self + + def SetCharacterSet( self, value ) : + self.CharacterSet = value + return self + + def SetPitch( self, value ) : + self.Pitch = value + return self + + def SetPanose( self, value ) : + self.Panose = value + return self + + def SetAlternate( self, value ) : + self.Alternate = value + return self + +class Fonts( AttributedList ) : + def __init__( self ) : + super( Fonts, self ).__init__( Font ) + +class Paper : + def __init__( self, name, code, description, width, height ) : + self.SetName ( name ) + self.SetCode ( code ) + self.SetDescription( description ) + self.SetWidth ( width ) + self.SetHeight ( height ) + + def SetName( self, value ) : + self.Name = value + return self + + def SetCode( self, value ) : + self.Code = value + return self + + def SetDescription( self, value ) : + self.Description = value + return self + + def SetWidth( self, value ) : + self.Width = value + return self + + def SetHeight( self, value ) : + self.Height = value + return self + +class Papers( AttributedList ) : + def __init__( self ) : + super( Papers, self ).__init__( Paper ) + +# +# Then we have property sets which represent different aspects of Styles +# +class MarginsPropertySet : + def __init__( self, top=None, left=None, bottom=None, right=None ) : + self.SetTop ( top ) + self.SetLeft ( left ) + self.SetBottom( bottom ) + self.SetRight ( right ) + + def SetTop( self, value ) : + self.Top = value + return self + + def SetLeft( self, value ) : + self.Left = value + return self + + def SetBottom( self, value ) : + self.Bottom = value + return self + + def SetRight( self, value ) : + self.Right = value + return self + +class ShadingPropertySet : + HORIZONTAL = 1 + VERTICAL = 2 + FORWARD_DIAGONAL = 3 + BACKWARD_DIAGONAL = 4 + VERTICAL_CROSS = 5 + DIAGONAL_CROSS = 6 + DARK_HORIZONTAL = 7 + DARK_VERTICAL = 8 + DARK_FORWARD_DIAGONAL = 9 + DARK_BACKWARD_DIAGONAL = 10 + DARK_VERTICAL_CROSS = 11 + DARK_DIAGONAL_CROSS = 12 + PATTERNS = [ HORIZONTAL, + VERTICAL, + FORWARD_DIAGONAL, + BACKWARD_DIAGONAL, + VERTICAL_CROSS, + DIAGONAL_CROSS, + DARK_HORIZONTAL, + DARK_VERTICAL, + DARK_FORWARD_DIAGONAL, + DARK_BACKWARD_DIAGONAL, + DARK_VERTICAL_CROSS, + DARK_DIAGONAL_CROSS ] + + def __init__( self, shading=None, pattern=None, foreground=None, background=None ) : + self.SetShading ( shading ) + self.SetForeground( foreground ) + self.SetBackground( background ) + self.SetPattern ( pattern ) + + def __deepcopy__( self, memo ) : + return ShadingPropertySet( self.Shading, + self.Foreground, + self.Background, + self.Pattern ) + + def SetShading( self, value ) : + self.Shading = value + return self + + def SetPattern( self, value ) : + assert value is None or value in self.PATTERNS + self.Pattern = value + return self + + def SetForeground( self, value ) : + assert not value or isinstance( value, Colour ) + self.Foreground = value + return self + + def SetBackground( self, value ) : + assert not value or isinstance( value, Colour ) + self.Background = value + return self + + +class BorderPropertySet : + SINGLE = 1 + DOUBLE = 2 + SHADOWED = 3 + DOUBLED = 4 + DOTTED = 5 + DASHED = 6 + HAIRLINE = 7 + STYLES = [ SINGLE, DOUBLE, SHADOWED, DOUBLED, DOTTED, DASHED, HAIRLINE ] + + def __init__( self, width=None, style=None, colour=None, spacing=None ) : + self.SetWidth ( width ) + self.SetStyle ( style or self.SINGLE ) + self.SetColour ( colour ) + self.SetSpacing( spacing ) + + def SetWidth( self, value ) : + self.Width = value + return self + + def SetStyle( self, value ) : + assert value is None or value in self.STYLES + self.Style = value + return self + + def SetColour( self, value ) : + assert value is None or isinstance( value, Colour ) + self.Colour = value + return self + + def SetSpacing( self, value ) : + self.Spacing = value + return self + +class FramePropertySet : + def __init__( self, top=None, left=None, bottom=None, right=None ) : + self.SetTop ( top ) + self.SetLeft ( left ) + self.SetBottom( bottom ) + self.SetRight ( right ) + + def SetTop( self, value ) : + assert value is None or isinstance( value, BorderPropertySet ) + self.Top = value + return self + + def SetLeft( self, value ) : + assert value is None or isinstance( value, BorderPropertySet ) + self.Left = value + return self + + def SetBottom( self, value ) : + assert value is None or isinstance( value, BorderPropertySet ) + self.Bottom = value + return self + + def SetRight( self, value ) : + assert value is None or isinstance( value, BorderPropertySet ) + self.Right = value + return self + +class TabPropertySet : + DEFAULT_WIDTH = 720 + + LEFT = 1 + RIGHT = 2 + CENTER = 3 + DECIMAL = 4 + ALIGNMENT = [ LEFT, RIGHT, CENTER, DECIMAL ] + + DOTS = 1 + HYPHENS = 2 + UNDERLINE = 3 + THICK_LINE = 4 + EQUAL_SIGN = 5 + LEADERS = [ DOTS, HYPHENS, UNDERLINE, THICK_LINE, EQUAL_SIGN ] + + def __init__( self, width=None, alignment=None, leader=None ) : + self.SetWidth ( width ) + self.SetAlignment( alignment or self.LEFT ) + self.SetLeader ( leader ) + + def SetWidth( self, value ) : + self.Width = value + return self + + def SetAlignment( self, value ) : + assert value in self.ALIGNMENT + self.Alignment = value + return self + + def SetLeader( self, value ) : + assert not value or value in self.LEADERS + self.Leader = value + return self + +class TextPropertySet : + + def __init__( self, font=None, size=None, bold=None, italic=None, underline=None, colour=None, frame=None, expansion=None ) : + self.SetFont ( font ) + self.SetSize ( size ) + + self.SetBold ( bold or False ) + self.SetItalic ( italic or False ) + self.SetUnderline ( underline or False ) + + self.SetColour( colour ) + self.SetFrame ( frame ) + + self.SetStrikeThrough ( False ) + self.SetDottedUnderline( False ) + self.SetDoubleUnderline( False ) + self.SetWordUnderline ( False ) + self.SetExpansion ( expansion ) + + def Copy( self ) : + return deepcopy( self ) + + def __deepcopy__( self, memo ) : + # the font must remain a reference to the same font that we are looking at + # so we want to stop the recursiveness at this point and return an object + # with the right references. + result = TextPropertySet( self.Font, + self.Size, + self.Bold, + self.Italic, + self.Underline, + self.Colour, + deepcopy( self.Frame, memo ) ) + result.SetStrikeThrough( self.StrikeThrough ) + return result + + def SetFont( self, value ) : + assert not value or isinstance( value, Font ) + self.Font = value + return self + + def SetSize( self, value ) : + self.Size = value + return self + + def SetBold( self, value ) : + self.Bold = False + if value : self.Bold = True + return self + + def SetItalic( self, value ) : + self.Italic = False + if value : self.Italic = True + return self + + def SetUnderline( self, value ) : + self.Underline = False + if value : self.Underline = True + return self + + def SetColour( self, value ) : + assert value is None or isinstance( value, Colour ) + self.Colour = value + return self + + def SetFrame( self, value ) : + assert value is None or isinstance( value, BorderPropertySet ) + self.Frame = value + return self + + def SetStrikeThrough( self, value ) : + self.StrikeThrough = False + if value : self.StrikeThrough = True + return self + + def SetDottedUnderline( self, value ) : + self.DottedUnderline = False + if value : self.DottedUnderline = True + return self + + def SetDoubleUnderline( self, value ) : + self.DoubleUnderline = False + if value : self.DoubleUnderline = True + return self + + def SetWordUnderline( self, value ) : + self.WordUnderline = False + if value : self.WordUnderline = True + return self + + def SetExpansion( self, value ) : + self.Expansion = value + return self + +class ParagraphPropertySet : + LEFT = 1 + RIGHT = 2 + CENTER = 3 + JUSTIFY = 4 + DISTRIBUTE = 5 + ALIGNMENT = [ LEFT, RIGHT, CENTER, JUSTIFY, DISTRIBUTE ] + + def __init__( self, alignment=None, space_before=None, space_after=None, tabs=None, first_line_indent=None, left_indent=None, right_indent=None, page_break_before=None ) : + self.SetAlignment ( alignment or self.LEFT ) + self.SetSpaceBefore( space_before ) + self.SetSpaceAfter ( space_after ) + + self.Tabs = [] + if tabs : apply( self.SetTabs, tabs ) + + self.SetFirstLineIndent( first_line_indent or None ) + self.SetLeftIndent ( left_indent or None ) + self.SetRightIndent ( right_indent or None ) + + self.SetPageBreakBefore( page_break_before ) + + self.SetSpaceBetweenLines( None ) + + def Copy( self ) : + return deepcopy( self ) + + def SetAlignment( self, value ) : + assert not value or value in self.ALIGNMENT + self.Alignment = value or self.LEFT + return self + + def SetSpaceBefore( self, value ) : + self.SpaceBefore = value + return self + + def SetSpaceAfter( self, value ) : + self.SpaceAfter = value + return self + + def SetTabs( self, *params ) : + self.Tabs = params + return self + + def SetFirstLineIndent( self, value ) : + self.FirstLineIndent = value + return self + + def SetLeftIndent( self, value ) : + self.LeftIndent = value + return self + + def SetRightIndent( self, value ) : + self.RightIndent = value + return self + + def SetSpaceBetweenLines( self, value ) : + self.SpaceBetweenLines = value + return self + + def SetPageBreakBefore( self, value ) : + self.PageBreakBefore = False + if value : self.PageBreakBefore = True + return self + +# Some short cuts to make the code a bit easier to read +MarginsPS = MarginsPropertySet +ShadingPS = ShadingPropertySet +BorderPS = BorderPropertySet +FramePS = FramePropertySet +TabPS = TabPropertySet +TextPS = TextPropertySet +ParagraphPS = ParagraphPropertySet ADDED gluon/contrib/pyrtf/README Index: gluon/contrib/pyrtf/README ================================================================== --- gluon/contrib/pyrtf/README +++ gluon/contrib/pyrtf/README @@ -0,0 +1,19 @@ +Version 0.46 + +Added EMF support. + +Added more sophisticated scaling options. + +See examples2.py for both. + +Grant Edwards, grante@users.sourceforge.net + + + +Version 0.45 + +Finally, image support!!! Handles PNGs and JPGs. + +See examples2.py for the gory details. + +Simon Cusack, scusack@sourceforge.net ADDED gluon/contrib/pyrtf/Renderer.py Index: gluon/contrib/pyrtf/Renderer.py ================================================================== --- gluon/contrib/pyrtf/Renderer.py +++ gluon/contrib/pyrtf/Renderer.py @@ -0,0 +1,638 @@ +from types import StringType, ListType, TupleType +from copy import deepcopy +from Elements import * + +DEFAULT_TAB_WIDTH = 720 + +ParagraphAlignmentMap = { ParagraphPropertySet.LEFT : 'ql', + ParagraphPropertySet.RIGHT : 'qr', + ParagraphPropertySet.CENTER : 'qc', + ParagraphPropertySet.JUSTIFY : 'qj', + ParagraphPropertySet.DISTRIBUTE : 'qd' } + +TabAlignmentMap = { TabPropertySet.LEFT : '', + TabPropertySet.RIGHT : 'tqr', + TabPropertySet.CENTER : 'tqc', + TabPropertySet.DECIMAL : 'tqdec' } + +TableAlignmentMap = { Table.LEFT : 'trql', + Table.RIGHT : 'trqr', + Table.CENTER : 'trqc' } + +CellAlignmentMap = { Cell.ALIGN_TOP : '', # clvertalt + Cell.ALIGN_CENTER : 'clvertalc', + Cell.ALIGN_BOTTOM : 'clvertalb' } + +CellFlowMap = { Cell.FLOW_LR_TB : '', # cltxlrtb, Text in a cell flows from left to right and top to bottom (default) + Cell.FLOW_RL_TB : 'cltxtbrl', # Text in a cell flows right to left and top to bottom + Cell.FLOW_LR_BT : 'cltxbtlr', # Text in a cell flows left to right and bottom to top + Cell.FLOW_VERTICAL_LR_TB : 'cltxlrtbv', # Text in a cell flows left to right and top to bottom, vertical + Cell.FLOW_VERTICAL_TB_RL : 'cltxtbrlv' } # Text in a cell flows top to bottom and right to left, vertical + +ShadingPatternMap = { ShadingPropertySet.HORIZONTAL : 'bghoriz', + ShadingPropertySet.VERTICAL : 'bgvert', + ShadingPropertySet.FORWARD_DIAGONAL : 'bgfdiag', + ShadingPropertySet.BACKWARD_DIAGONAL : 'bgbdiag', + ShadingPropertySet.VERTICAL_CROSS : 'bgcross', + ShadingPropertySet.DIAGONAL_CROSS : 'bgdcross', + ShadingPropertySet.DARK_HORIZONTAL : 'bgdkhoriz', + ShadingPropertySet.DARK_VERTICAL : 'bgdkvert', + ShadingPropertySet.DARK_FORWARD_DIAGONAL : 'bgdkfdiag', + ShadingPropertySet.DARK_BACKWARD_DIAGONAL : 'bgdkbdiag', + ShadingPropertySet.DARK_VERTICAL_CROSS : 'bgdkcross', + ShadingPropertySet.DARK_DIAGONAL_CROSS : 'bgdkdcross' } + +TabLeaderMap = { TabPropertySet.DOTS : 'tldot', + TabPropertySet.HYPHENS : 'tlhyph', + TabPropertySet.UNDERLINE : 'tlul', + TabPropertySet.THICK_LINE : 'tlth', + TabPropertySet.EQUAL_SIGN : 'tleq' } + +BorderStyleMap = { BorderPropertySet.SINGLE : 'brdrs', + BorderPropertySet.DOUBLE : 'brdrth', + BorderPropertySet.SHADOWED : 'brdrsh', + BorderPropertySet.DOUBLED : 'brdrdb', + BorderPropertySet.DOTTED : 'brdrdot', + BorderPropertySet.DASHED : 'brdrdash', + BorderPropertySet.HAIRLINE : 'brdrhair' } + +SectionBreakTypeMap = { Section.NONE : 'sbknone', + Section.COLUMN : 'sbkcol', + Section.PAGE : 'sbkpage', + Section.EVEN : 'sbkeven', + Section.ODD : 'sbkodd' } + +class Settings( list ) : + def __init__( self ) : + super( Settings, self ).__init__() + self._append = super( Settings, self ).append + + def append( self, value, mask=None, fallback=None ) : + if (value is not 0) and value in [ False, None, '' ] : + if fallback : self._append( self, fallback ) + + else : + if mask : + if value is True : + value = mask + else : + value = mask % value + self._append( value ) + + def Join( self ) : + if self : return r'\%s' % '\\'.join( self ) + return '' + + def __repr__( self ) : + return self.Join() + +class Renderer : + def __init__( self, write_custom_element_callback=None ) : + self.character_style_map = {} + self.paragraph_style_map = {} + self.WriteCustomElement = write_custom_element_callback + + # + # All of the Rend* Functions populate a Settings object with values + # + def _RendPageProperties( self, section, settings, in_section ) : + # this one is different from the others as it takes the settings from a + if in_section : + #paper_size_code = 'psz%s' + paper_width_code = 'pgwsxn%s' + paper_height_code = 'pghsxn%s' + landscape = 'lndscpsxn' + margin_suffix = 'sxn' + + else : + #paper_size_code = 'psz%s' + paper_width_code = 'paperw%s' + paper_height_code = 'paperh%s' + landscape = 'landscape' + margin_suffix = '' + + #settings.append( section.Paper.Code, paper_size_code ) + settings.append( section.Paper.Width, paper_width_code ) + settings.append( section.Paper.Height, paper_height_code ) + + if section.Landscape : + settings.append( landscape ) + + if section.FirstPageNumber : + settings.append( section.FirstPageNumber, 'pgnstarts%s' ) + settings.append( 'pgnrestart' ) + + self._RendMarginsPropertySet( section.Margins, settings, margin_suffix ) + + def _RendShadingPropertySet( self, shading_props, settings, prefix='' ) : + if not shading_props : return + + settings.append( shading_props.Shading, prefix + 'shading%s' ) + settings.append( ShadingPatternMap.get( shading_props.Pattern, False ) ) + + settings.append( self._colour_map.get( shading_props.Foreground, False ), prefix + 'cfpat%s' ) + settings.append( self._colour_map.get( shading_props.Background, False ), prefix + 'cbpat%s' ) + + def _RendBorderPropertySet( self, edge_props, settings ) : + settings.append( BorderStyleMap[ edge_props.Style ] ) + settings.append( edge_props.Width , 'brdrw%s' ) + settings.append( self._colour_map.get( edge_props.Colour, False ), 'brdrcf%s' ) + settings.append( edge_props.Spacing or False , 'brsp%s' ) + + def _RendFramePropertySet( self, frame_props, settings, tag_prefix='' ) : + if not frame_props : return + + if frame_props.Top : + settings.append( tag_prefix + 'brdrt' ) + self._RendBorderPropertySet( frame_props.Top, settings ) + + if frame_props.Left : + settings.append( tag_prefix + 'brdrl' ) + self._RendBorderPropertySet( frame_props.Left, settings ) + + if frame_props.Bottom : + settings.append( tag_prefix + 'brdrb' ) + self._RendBorderPropertySet( frame_props.Bottom, settings ) + + if frame_props.Right : + settings.append( tag_prefix + 'brdrr' ) + self._RendBorderPropertySet( frame_props.Right, settings ) + + def _RendMarginsPropertySet( self, margin_props, settings, suffix='' ) : + if not margin_props : return + + settings.append( margin_props.Top, 'margt' + suffix + '%s' ) + settings.append( margin_props.Left, 'margl' + suffix + '%s' ) + settings.append( margin_props.Bottom, 'margb' + suffix + '%s' ) + settings.append( margin_props.Right, 'margr' + suffix + '%s' ) + + def _RendParagraphPropertySet( self, paragraph_props, settings ) : + if not paragraph_props : return + settings.append( ParagraphAlignmentMap[ paragraph_props.Alignment ] ) + + settings.append( paragraph_props.SpaceBefore, 'sb%s' ) + settings.append( paragraph_props.SpaceAfter, 'sa%s' ) + + # then we have to find out all of the tabs + width = 0 + for tab in paragraph_props.Tabs : + settings.append( TabAlignmentMap[ tab.Alignment ] ) + settings.append( TabLeaderMap.get( tab.Leader, '' ) ) + + width += tab.Width or DEFAULT_TAB_WIDTH + settings.append( 'tx%s' % width ) + + settings.append( paragraph_props.PageBreakBefore, 'pagebb' ) + + settings.append( paragraph_props.FirstLineIndent, 'fi%s' ) + settings.append( paragraph_props.LeftIndent, 'li%s' ) + settings.append( paragraph_props.RightIndent, 'ri%s' ) + + if paragraph_props.SpaceBetweenLines : + if paragraph_props.SpaceBetweenLines < 0 : + settings.append( paragraph_props.SpaceBetweenLines, r'sl%s\slmult0' ) + else : + settings.append( paragraph_props.SpaceBetweenLines, r'sl%s\slmult1' ) + + def _RendTextPropertySet( self, text_props, settings ) : + if not text_props : return + + if text_props.Expansion : + settings.append( text_props.Expansion, 'expndtw%s' ) + + settings.append( text_props.Bold, 'b' ) + settings.append( text_props.Italic, 'i' ) + settings.append( text_props.Underline, 'ul' ) + settings.append( text_props.DottedUnderline, 'uld' ) + settings.append( text_props.DoubleUnderline, 'uldb' ) + settings.append( text_props.WordUnderline, 'ulw' ) + + settings.append( self._font_map.get( text_props.Font, False ), 'f%s' ) + settings.append( text_props.Size, 'fs%s' ) + settings.append( self._colour_map.get( text_props.Colour, False ), 'cf%s' ) + + if text_props.Frame : + frame = text_props.Frame + settings.append( 'chbrdr' ) + settings.append( BorderStyleMap[ frame.Style ] ) + settings.append( frame.Width , 'brdrw%s' ) + settings.append( self._colour_map.get( frame.Colour, False ), 'brdrcf%s' ) + + # + # All of the Write* functions will write to the internal file object + # + # the _ ones probably don't need to be used by anybody outside + # but the other ones like WriteTextElement could be used in the Custom + # callback. + def Write( self, document, fout ) : + # write all of the standard stuff based upon the first document + self._doc = document + self._fout = fout + self._WriteDocument () + self._WriteColours () + self._WriteFonts () + self._WriteStyleSheet() + + settings = Settings() + self._RendPageProperties( self._doc.Sections[ 0 ], settings, in_section=False ) + self._write( repr( settings ) ) + + # handle the simplest case first, we don't need to do anymore mucking around + # with section headers, etc we can just rip the document out + if len( document.Sections ) == 1 : + self._WriteSection( document.Sections[ 0 ], + is_first = True, + add_header = False ) + + else : + for section_idx, section in enumerate( document.Sections ) : + is_first = section_idx == 0 + add_header = True + self._WriteSection( section, is_first, add_header ) + + self._write( '}' ) + + del self._fout, self._doc, self._CurrentStyle + + def _write( self, data, *params ) : + #---------------------------------- + # begin modification + # by Herbert Weinhandl + # to convert accented characters + # to their rtf-compatible form + #for c in range( 128, 256 ) : + # data = data.replace( chr(c), "\'%x" % c) + # end modification + # + # This isn't the right place for this as it is going to do + # this loop for all sorts of writes, including settings, control codes, etc. + # + # I will create a def _WriteText (or something) method that is used when the + # actual string that is to be viewed in the document is written, this can then + # do the final accented character check. + # + # I left it here so that I remember to do the right thing when I have time + #---------------------------------- + + if params : data = data % params + self._fout.write( data ) + + def _WriteDocument( self ) : + settings = Settings() + + assert Languages.IsValid ( self._doc.DefaultLanguage ) + assert ViewKind.IsValid ( self._doc.ViewKind ) + assert ViewZoomKind.IsValid( self._doc.ViewZoomKind ) + assert ViewScale.IsValid ( self._doc.ViewScale ) + + settings.append( self._doc.DefaultLanguage, 'deflang%s' ) + settings.append( self._doc.ViewKind , 'viewkind%s' ) + settings.append( self._doc.ViewZoomKind , 'viewzk%s' ) + settings.append( self._doc.ViewScale , 'viewscale%s' ) + + self._write( "{\\rtf1\\ansi\\ansicpg1252\\deff0%s\n" % settings ) + + def _WriteColours( self ) : + self._write( r"{\colortbl ;" ) + + self._colour_map = {} + offset = 0 + for colour in self._doc.StyleSheet.Colours : + self._write( r'\red%s\green%s\blue%s;', colour.Red, colour.Green, colour.Blue ) + self._colour_map[ colour ] = offset + 1 + offset += 1 + self._write( "}\n" ) + + def _WriteFonts( self ) : + self._write( r'{\fonttbl' ) + + self._font_map = {} + offset = 0 + for font in self._doc.StyleSheet.Fonts : + pitch = '' + panose = '' + alternate = '' + if font.Pitch : pitch = r'\fprq%s' % font.Pitch + if font.Panose : panose = r'{\*\panose %s}' % font.Panose + if font.Alternate : alternate = r'{\*\falt %s}' % font.Alternate.Name + + self._write( r'{\f%s\f%s%s\fcharset%s%s %s%s;}', + offset, + font.Family, + pitch, + font.CharacterSet, + panose, + font.Name, + alternate ) + + self._font_map[ font ] = offset + offset += 1 + + self._write( "}\n" ) + + def _WriteStyleSheet( self ) : + self._write( r"{\stylesheet" ) + + # TO DO: character styles, does anybody actually use them? + + offset_map = {} + for idx, style in enumerate( self._doc.StyleSheet.ParagraphStyles ) : + offset_map[ style ] = idx + + # paragraph styles + self.paragraph_style_map = {} + for idx, style in enumerate( self._doc.StyleSheet.ParagraphStyles ) : + + if idx == 0 : + default = style + else : + self._write( '\n' ) + + settings = Settings() + + # paragraph properties + self._RendParagraphPropertySet( style.ParagraphPropertySet, settings ) + self._RendFramePropertySet ( style.FramePropertySet, settings ) + self._RendShadingPropertySet ( style.ShadingPropertySet, settings ) + + # text properties + self._RendTextPropertySet ( style.TextStyle.TextPropertySet, settings ) + self._RendShadingPropertySet( style.TextStyle.ShadingPropertySet, settings ) + + # have to take + based_on = '\\sbasedon%s' % offset_map.get( style.BasedOn, 0 ) + next = '\\snext%s' % offset_map.get( style.Next, 0 ) + + inln = '\\s%s%s' % ( idx, settings ) + self._write( "{%s%s%s %s;}", inln, based_on, next, style.Name ) + + self.paragraph_style_map[ style ] = inln + + # if now style is specified for the first paragraph to be written, this one + # will be used + self._CurrentStyle = self.paragraph_style_map[ default ] + + self._write( "}\n" ) + + def _WriteSection( self, section, is_first, add_header ) : + + def WriteHF( hf, rtfword ) : + #if not hf : return + + # if we don't have anything in the header/footer then include + # a blank paragraph, this stops it from picking up the header/footer + # from the previous section + # if not hf : hf = [ Paragraph( '' ) ] + if not hf : hf = [] + + self._write( '{\\%s' % rtfword ) + self._WriteElements( hf ) + self._write( '}\n' ) + + settings = Settings() + + if not is_first : + # we need to finish off the preceding section + # and reset all of our defaults back to standard + settings.append( 'sect' ) + + # reset to our defaults + settings.append( 'sectd' ) + + if add_header : + settings.append( SectionBreakTypeMap[ section.BreakType ] ) + self._RendPageProperties( section, settings, in_section=True ) + + settings.append( section.HeaderY, 'headery%s' ) + settings.append( section.FooterY, 'footery%s' ) + + # write all of these out now as we need to do a write elements in the + # next section + self._write( repr( settings ) ) + + # finally after all that has settled down we can do the + # headers and footers + if section.FirstHeader or section.FirstFooter : + # include the titlepg flag if the first page has a special format + self._write( r'\titlepg' ) + WriteHF( section.FirstHeader, 'headerf' ) + WriteHF( section.FirstFooter, 'footerf' ) + + WriteHF( section.Header, 'header' ) + WriteHF( section.Footer, 'footer' ) + + # and at last the contents of the section that actually appear on the page + self._WriteElements( section ) + + def _WriteElements( self, elements ) : + new_line = '' + for element in elements : + self._write( new_line ) + new_line = '\n' + + clss = element.__class__ + + if clss == Paragraph : + self.WriteParagraphElement( element ) + + elif clss == Table : + self.WriteTableElement( element ) + + elif clss == StringType : + self.WriteParagraphElement( Paragraph( element ) ) + + elif clss in [ RawCode, Image ] : + self.WriteRawCode( element ) + + #elif clss == List : + # self._HandleListElement( element ) + + elif self.WriteCustomElement : + self.WriteCustomElement( self, element ) + + else : + raise Exception( "Don't know how to handle elements of type %s" % clss ) + + def WriteParagraphElement( self, paragraph_elem, tag_prefix='', tag_suffix=r'\par', opening='{', closing='}' ) : + + # the tag_prefix and the tag_suffix take care of paragraphs in tables. A + # paragraph in a table requires and extra tag at the front (intbl) and we + # don't want the ending tag everytime. We want it for all paragraphs but + # the last. + + overrides = Settings() + self._RendParagraphPropertySet( paragraph_elem.Properties, overrides ) + self._RendFramePropertySet ( paragraph_elem.Frame, overrides ) + self._RendShadingPropertySet ( paragraph_elem.Shading, overrides ) + + # when writing the RTF the style is carried from the previous paragraph to the next, + # so if the currently written paragraph has a style then make it the current one, + # otherwise leave it as it was + self._CurrentStyle = self.paragraph_style_map.get( paragraph_elem.Style, self._CurrentStyle ) + + self._write( r'%s\pard\plain%s %s%s ' % ( opening, tag_prefix, self._CurrentStyle, overrides ) ) + + for element in paragraph_elem : + + if isinstance( element, StringType ) : + self._write( element ) + + elif isinstance( element, RawCode ) : + self._write( element.Data ) + + elif isinstance( element, Text ) : + self.WriteTextElement( element ) + + elif isinstance( element, Inline ) : + self.WriteInlineElement( element ) + + elif element == TAB : + self._write( r'\tab ' ) + + elif element == LINE : + self._write( r'\line ' ) + + elif self.WriteCustomElement : + self.WriteCustomElement( self, element ) + + else : + raise Exception( 'Don\'t know how to handle %s' % element ) + + self._write( tag_suffix + closing ) + + def WriteRawCode( self, raw_elem ) : + self._write( raw_elem.Data ) + + def WriteTextElement( self, text_elem ) : + overrides = Settings() + + self._RendTextPropertySet ( text_elem.Properties, overrides ) + self._RendShadingPropertySet( text_elem.Shading, overrides, 'ch' ) + + # write the wrapper and then let the custom handler have a go + if overrides : self._write( '{%s ' % repr( overrides ) ) + + # if the data is just a string then we can now write it + if isinstance( text_elem.Data, StringType ) : + self._write( text_elem.Data or '' ) + + elif text_elem.Data == TAB : + self._write( r'\tab ' ) + + else : + self.WriteCustomElement( self, text_elem.Data ) + + if overrides : self._write( '}' ) + + def WriteInlineElement( self, inline_elem ) : + overrides = Settings() + + self._RendTextPropertySet ( inline_elem.Properties, overrides ) + self._RendShadingPropertySet( inline_elem.Shading, overrides, 'ch' ) + + # write the wrapper and then let the custom handler have a go + if overrides : self._write( '{%s ' % repr( overrides ) ) + + for element in inline_elem : + # if the data is just a string then we can now write it + if isinstance( element, StringType ) : + self._write( element ) + + elif isinstance( element, RawCode ) : + self._write( element.Data ) + + elif element == TAB : + self._write( r'\tab ' ) + + elif element == LINE : + self._write( r'\line ' ) + + else : + self.WriteCustomElement( self, element ) + + if overrides : self._write( '}' ) + + def WriteText( self, text ) : + self._write( text or '' ) + + def WriteTableElement( self, table_elem ) : + + vmerge = [ False ] * table_elem.ColumnCount + for height, cells in table_elem.Rows : + + # calculate the right hand edge of the cells taking into account the spans + offset = table_elem.LeftOffset or 0 + cellx = [] + cell_idx = 0 + for cell in cells : + cellx.append( offset + sum( table_elem.ColumnWidths[ : cell_idx + cell.Span ] ) ) + cell_idx += cell.Span + + self._write( r'{\trowd' ) + + settings = Settings() + + # the spec says that this value is mandatory and I think that 108 is the default value + # so I'll take care of it here + settings.append( table_elem.GapBetweenCells or 108, 'trgaph%s' ) + settings.append( TableAlignmentMap[ table_elem.Alignment ] ) + settings.append( height, 'trrh%s' ) + settings.append( table_elem.LeftOffset, 'trleft%s' ) + + width = table_elem.LeftOffset or 0 + for idx, cell in enumerate( cells ) : + self._RendFramePropertySet ( cell.Frame, settings, 'cl' ) + + # cells don't have margins so I don't know why I was doing this + # I think it might have an affect in some versions of some WPs. + #self._RendMarginsPropertySet( cell.Margins, settings, 'cl' ) + + # if we are starting to merge or if this one is the first in what is + # probably a series of merges then start the vertical merging + if cell.StartVerticalMerge or (cell.VerticalMerge and not vmerge[ idx ]) : + settings.append( 'clvmgf' ) + vmerge[ idx ] = True + + elif cell.VerticalMerge : + #..continuing a merge + settings.append( 'clvmrg' ) + + else : + #..no merging going on so make sure that it is off + vmerge[ idx ] = False + + # for any cell in the next row that is covered by this span we + # need to run off the vertical merging as we don't want them + # merging up into this spanned cell + for vmerge_idx in range( idx + 1, idx + cell.Span - 1 ) : + vmerge[ vmerge_idx ] = False + + settings.append( CellAlignmentMap[ cell.Alignment ] ) + settings.append( CellFlowMap[ cell.Flow ] ) + + # this terminates the definition of a cell and represents the right most edge of the cell from the left margin + settings.append( cellx[ idx ], 'cellx%s' ) + + self._write( repr( settings ) ) + + for cell in cells : + if len( cell ) : + last_idx = len( cell ) - 1 + for element_idx, element in enumerate( cell ) : + # wrap plain strings in paragraph tags + if isinstance( element, StringType ) : + element = Paragraph( element ) + + # don't forget the prefix or else word crashes and does all sorts of strange things + if element_idx == last_idx : + self.WriteParagraphElement( element, tag_prefix=r'\intbl', tag_suffix='', opening='', closing='' ) + + else : + self.WriteParagraphElement( element, tag_prefix=r'\intbl', opening='', closing='' ) + + self._write( r'\cell' ) + + else : + self._write( r'\pard\intbl\cell' ) + + self._write( '\\row}\n' ) ADDED gluon/contrib/pyrtf/Styles.py Index: gluon/contrib/pyrtf/Styles.py ================================================================== --- gluon/contrib/pyrtf/Styles.py +++ gluon/contrib/pyrtf/Styles.py @@ -0,0 +1,93 @@ +""" +A Styles is a collection of PropertySets that can be applied to a particular RTF element. + +At present there are only two, Text and Paragraph but ListStyles will be added soon too. + + +""" + +from PropertySets import * + +class TextStyle : + def __init__( self, text_props, name=None, shading_props=None ) : + self.SetTextPropertySet ( text_props ) + self.SetName ( name ) + self.SetShadingPropertySet( shading_props ) + + def Copy( self ) : + return deepcopy( self ) + + def SetName( self, value ) : + self.Name = value + return self + + def SetTextPropertySet( self, value ) : + assert isinstance( value, TextPropertySet ) + self.TextPropertySet = value + return self + + def SetShadingPropertySet( self, value ) : + assert value is None or isinstance( value, ShadingPropertySet ) + self.ShadingPropertySet = value or ShadingPropertySet() + return self + +class ParagraphStyle : + def __init__( self, name, text_style, paragraph_props=None, frame_props=None, shading_props=None ) : + + # A style must have Font and a Font Size but the Text property set doesn't + # make these mandatory so that they can be used for overrides so at this point + # we need to make sure that that we have these values set + if not text_style.TextPropertySet.Font : raise Exception( 'Paragraph Styles must have a Font specified.' ) + if not text_style.TextPropertySet.Size : raise Exception( 'Paragraph Styles must have a Font Size specified.' ) + + self.SetName ( name ) + self.SetTextStyle ( text_style ) + self.SetParagraphPropertySet( paragraph_props ) + self.SetFramePropertySet ( frame_props ) + self.SetShadingPropertySet ( shading_props ) + + self.SetBasedOn( None ) + self.SetNext ( None ) + + def Copy( self ) : + return deepcopy( self ) + + def SetName( self, value ) : + self.Name = value + return self + + def SetTextStyle( self, value ) : + assert isinstance( value, TextStyle ) + self.TextStyle = value + return self + + def SetParagraphPropertySet( self, value ) : + assert value is None or isinstance( value, ParagraphPropertySet ) + self.ParagraphPropertySet = value or ParagraphPropertySet() + return self + + def SetFramePropertySet( self, value ) : + assert value is None or isinstance( value, FramePropertySet ) + self.FramePropertySet = value or FramePropertySet() + return self + + def SetShadingPropertySet( self, value ) : + """Set the background shading for the paragraph.""" + + assert value is None or isinstance( value, ShadingPropertySet ) + self.ShadingPropertySet = value or ShadingPropertySet() + return self + + def SetBasedOn( self, value ) : + """Set the Paragraph Style that this one is based on.""" + + assert not value or isinstance( value, ParagraphStyle ) + self.BasedOn = value + return self + + def SetNext( self, value ) : + """Set the Paragraph Style that should follow this one.""" + + assert not value or isinstance( value, ParagraphStyle ) + self.Next = value + return self ADDED gluon/contrib/pyrtf/__init__.py Index: gluon/contrib/pyrtf/__init__.py ================================================================== --- gluon/contrib/pyrtf/__init__.py +++ gluon/contrib/pyrtf/__init__.py @@ -0,0 +1,11 @@ +from PropertySets import * +from Elements import * +from Styles import * +from Renderer import * + +def dumps(doc): + import cStringIO + s=cStringIO.StringIO() + r=Renderer() + r.Write(doc,s) + return s.getvalue() ADDED gluon/contrib/pysimplesoap/__init__.py Index: gluon/contrib/pysimplesoap/__init__.py ================================================================== --- gluon/contrib/pysimplesoap/__init__.py +++ gluon/contrib/pysimplesoap/__init__.py @@ -0,0 +1,3 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +"Contributed modules" ADDED gluon/contrib/pysimplesoap/client.py Index: gluon/contrib/pysimplesoap/client.py ================================================================== --- gluon/contrib/pysimplesoap/client.py +++ gluon/contrib/pysimplesoap/client.py @@ -0,0 +1,688 @@ +#!/usr/bin/python +# -*- coding: latin-1 -*- +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by the +# Free Software Foundation; either version 3, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. + +"Pythonic simple SOAP Client implementation" + +__author__ = "Mariano Reingart (reingart@gmail.com)" +__copyright__ = "Copyright (C) 2008 Mariano Reingart" +__license__ = "LGPL 3.0" +__version__ = "1.02c" + +import urllib +try: + import httplib2 + Http = httplib2.Http +except ImportError: + import urllib2 + class Http(): # wrapper to use when httplib2 not available + def request(self, url, method, body, headers): + f = urllib2.urlopen(urllib2.Request(url, body, headers)) + return f.info(), f.read() + + +from simplexml import SimpleXMLElement, TYPE_MAP, OrderedDict + +class SoapFault(RuntimeError): + def __init__(self,faultcode,faultstring): + self.faultcode = faultcode + self.faultstring = faultstring + +# soap protocol specification & namespace +soap_namespaces = dict( + soap11="http://schemas.xmlsoap.org/soap/envelope/", + soap="http://schemas.xmlsoap.org/soap/envelope/", + soapenv="http://schemas.xmlsoap.org/soap/envelope/", + soap12="http://www.w3.org/2003/05/soap-env", +) + +class SoapClient(object): + "Simple SOAP Client (s�mil PHP)" + def __init__(self, location = None, action = None, namespace = None, + cert = None, trace = False, exceptions = True, proxy = None, ns=False, + soap_ns=None, wsdl = None, cache = False): + self.certssl = cert + self.keyssl = None + self.location = location # server location (url) + self.action = action # SOAP base action + self.namespace = namespace # message + self.trace = trace # show debug messages + self.exceptions = exceptions # lanzar execpiones? (Soap Faults) + self.xml_request = self.xml_response = '' + if not soap_ns and not ns: + self.__soap_ns = 'soap' # 1.1 + elif not soap_ns and ns: + self.__soap_ns = 'soapenv' # 1.2 + else: + self.__soap_ns = soap_ns + + # parse wsdl url + self.services = wsdl and self.wsdl(wsdl, debug=trace, cache=cache) + self.service_port = None # service port for late binding + + if not proxy: + self.http = Http() + else: + import socks + ##httplib2.debuglevel=4 + self.http = httplib2.Http(proxy_info = httplib2.ProxyInfo( + proxy_type=socks.PROXY_TYPE_HTTP, **proxy)) + #if self.certssl: # esto funciona para validar al server? + # self.http.add_certificate(self.keyssl, self.keyssl, self.certssl) + self.__ns = ns # namespace prefix or False to not use it + if not ns: + self.__xml = """<?xml version="1.0" encoding="UTF-8"?> +<%(soap_ns)s:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:xsd="http://www.w3.org/2001/XMLSchema" + xmlns:%(soap_ns)s="%(soap_uri)s"> +<%(soap_ns)s:Body> + <%(method)s xmlns="%(namespace)s"> + </%(method)s> +</%(soap_ns)s:Body> +</%(soap_ns)s:Envelope>""" + else: + self.__xml = """<?xml version="1.0" encoding="UTF-8"?> +<%(soap_ns)s:Envelope xmlns:%(soap_ns)s="%(soap_uri)s" xmlns:%(ns)s="%(namespace)s"> +<%(soap_ns)s:Header/> +<%(soap_ns)s:Body> + <%(ns)s:%(method)s> + </%(ns)s:%(method)s> +</%(soap_ns)s:Body> +</%(soap_ns)s:Envelope>""" + + def __getattr__(self, attr): + "Return a pseudo-method that can be called" + if not self.services: # not using WSDL? + return lambda self=self, *args, **kwargs: self.call(attr,*args,**kwargs) + else: # using WSDL: + return lambda self=self, *args, **kwargs: self.wsdl_call(attr,*args,**kwargs) + + def call(self, method, *args, **kwargs): + "Prepare xml request and make SOAP call, returning a SimpleXMLElement" + #TODO: method != input_message + # Basic SOAP request: + xml = self.__xml % dict(method=method, namespace=self.namespace, ns=self.__ns, + soap_ns=self.__soap_ns, soap_uri=soap_namespaces[self.__soap_ns]) + request = SimpleXMLElement(xml,namespace=self.__ns and self.namespace, prefix=self.__ns) + # serialize parameters + if kwargs: + parameters = kwargs.items() + else: + parameters = args + if parameters and isinstance(parameters[0], SimpleXMLElement): + # merge xmlelement parameter ("raw" - already marshalled) + for param in parameters[0].children(): + getattr(request,method).import_node(param) + else: + # marshall parameters: + for k,v in parameters: # dict: tag=valor + getattr(request,method).marshall(k,v) + self.xml_request = request.as_xml() + self.xml_response = self.send(method, self.xml_request) + response = SimpleXMLElement(self.xml_response, namespace=self.namespace) + if self.exceptions and response("Fault", ns=soap_namespaces.values(), error=False): + raise SoapFault(unicode(response.faultcode), unicode(response.faultstring)) + return response + + def send(self, method, xml): + "Send SOAP request using HTTP" + if self.location == 'test': return + location = "%s" % self.location #?op=%s" % (self.location, method) + if self.services: + soap_action = self.action + else: + soap_action = self.action+method + headers={ + 'Content-type': 'text/xml; charset="UTF-8"', + 'Content-length': str(len(xml)), + "SOAPAction": "\"%s\"" % (soap_action) + } + if self.trace: + print "-"*80 + print "POST %s" % location + print '\n'.join(["%s: %s" % (k,v) for k,v in headers.items()]) + print u"\n%s" % xml.decode("utf8","ignore") + response, content = self.http.request( + location,"POST", body=xml, headers=headers ) + self.response = response + self.content = content + if self.trace: + print + print '\n'.join(["%s: %s" % (k,v) for k,v in response.items()]) + print content#.decode("utf8","ignore") + print "="*80 + return content + + def get_operation(self, method): + # try to find operation in wsdl file + soap_ver = self.__soap_ns == 'soap12' and 'soap12' or 'soap11' + if not self.service_port: + for service_name, service in self.services.items(): + for port_name, port in [port for port in service['ports'].items()]: + if port['soap_ver'] == soap_ver: + self.service_port = service_name, port_name + break + else: + raise RuntimeError("Cannot determine service in WSDL: " + "SOAP version: %s" % soap_ver) + else: + port = self.services[self.service_port[0]]['ports'][self.service_port[1]] + self.location = port['location'] + operation = port['operations'].get(unicode(method)) + if not operation: + raise RuntimeError("Operation %s not found in WSDL: " + "Service/Port Type: %s" % + (method, self.service_port)) + return operation + + def wsdl_call(self, method, *args, **kwargs): + "Pre and post process SOAP call, input and output parameters using WSDL" + soap_uri = soap_namespaces[self.__soap_ns] + operation = self.get_operation(method) + # get i/o type declarations: + input = operation['input'] + output = operation['output'] + if 'action' in operation: + self.action = operation['action'] + # sort parameters (same order as xsd:sequence) + def sort_dict(od, d): + if isinstance(od, dict): + ret = OrderedDict() + for k in od.keys(): + v = d.get(k) + if v: + if isinstance(v, dict): + v = sort_dict(od[k], v) + elif isinstance(v, list): + v = [sort_dict(od[k][0], v1) + for v1 in v] + ret[str(k)] = v + return ret + else: + return d + if input and kwargs: + params = sort_dict(input.values()[0], kwargs).items() + method = input.keys()[0] + #elif not input: + #TODO: no message! (see wsmtxca.dummy) + else: + params = kwargs and kwargs.items() + # call remote procedure + response = self.call(method, *params) + # parse results: + resp = response('Body',ns=soap_uri).children().unmarshall(output) + return resp and resp.values()[0] # pass Response tag children + + def help(self, method): + "Return operation documentation and invocation/returned value example" + operation = self.get_operation(method) + input = operation['input'].values() + input = input and input[0] + output = operation['output'].values()[0] + return u"%s(%s)\n -> %s:\n\n%s" % ( + method, + input and ", ".join("%s=%s" % (k,repr(v)) for k,v + in input.items()) or "", + output and output or "", + operation.get("documentation",""), + ) + + def wsdl(self, url, debug=False, cache=False): + "Parse Web Service Description v1.1" + soap_ns = { + "http://schemas.xmlsoap.org/wsdl/soap/": 'soap11', + "http://schemas.xmlsoap.org/wsdl/soap12/": 'soap12', + } + wsdl_uri="http://schemas.xmlsoap.org/wsdl/" + xsd_uri="http://www.w3.org/2001/XMLSchema" + xsi_uri="http://www.w3.org/2001/XMLSchema-instance" + + get_local_name = lambda s: str((':' in s) and s.split(':')[1] or s) + + REVERSE_TYPE_MAP = dict([(v,k) for k,v in TYPE_MAP.items()]) + + def fetch(url): + "Fetch a document from a URL, save it locally if cache enabled" + import os, hashlib + # make md5 hash of the url for caching... + filename = "%s.xml" % hashlib.md5(url).hexdigest() + if isinstance(cache, basestring): + filename = os.path.join(cache, filename) + if cache and os.path.exists(filename): + if debug: print "Reading file %s" % (filename, ) + f = open(filename, "r") + xml = f.read() + f.close() + else: + if debug: print "Fetching url %s" % (url, ) + f = urllib.urlopen(url) + xml = f.read() + if cache: + if debug: print "Writing file %s" % (filename, ) + f = open(filename, "w") + f.write(xml) + f.close() + return xml + + # Open uri and read xml: + xml = fetch(url) + # Parse WSDL XML: + wsdl = SimpleXMLElement(xml, namespace=wsdl_uri) + + # detect soap prefix and uri (xmlns attributes of <definitions>) + xsd_ns = None + soap_uris = {} + for k, v in wsdl[:]: + if v in soap_ns and k.startswith("xmlns:"): + soap_uris[get_local_name(k)] = v + if v== xsd_uri and k.startswith("xmlns:"): + xsd_ns = get_local_name(k) + + # Extract useful data: + self.namespace = wsdl['targetNamespace'] + self.documentation = unicode(wsdl('documentation', error=False) or '') + + services = {} + bindings = {} # binding_name: binding + operations = {} # operation_name: operation + port_type_bindings = {} # port_type_name: binding + messages = {} # message: element + elements = {} # element: type def + + for service in wsdl.service: + service_name=service['name'] + if not service_name: + continue # empty service? + if debug: print "Processing service", service_name + serv = services.setdefault(service_name, {'ports': {}}) + serv['documentation']=service['documentation'] or '' + for port in service.port: + binding_name = get_local_name(port['binding']) + address = port('address', ns=soap_uris.values(), error=False) + location = address and address['location'] or None + soap_uri = address and soap_uris.get(address.get_prefix()) + soap_ver = soap_uri and soap_ns.get(soap_uri) + bindings[binding_name] = {'service_name': service_name, + 'location': location, + 'soap_uri': soap_uri, 'soap_ver': soap_ver, + } + serv['ports'][port['name']] = bindings[binding_name] + + for binding in wsdl.binding: + binding_name = binding['name'] + if debug: print "Processing binding", service_name + soap_binding = binding('binding', ns=soap_uris.values(), error=False) + transport = soap_binding and soap_binding['transport'] or None + port_type_name = get_local_name(binding['type']) + bindings[binding_name].update({ + 'port_type_name': port_type_name, + 'transport': transport, 'operations': {}, + }) + port_type_bindings[port_type_name] = bindings[binding_name] + for operation in binding.operation: + op_name = operation['name'] + op = operation('operation',ns=soap_uris.values(), error=False) + action = op and op['soapAction'] + d = operations.setdefault(op_name, {}) + bindings[binding_name]['operations'][op_name] = d + d.update({'name': op_name}) + #if action: #TODO: separe operation_binding from operation + if action: + d["action"] = action + + #TODO: cleanup element/schema/types parsing: + def process_element(element_name, node): + "Parse and define simple element types" + if debug: print "Processing element", element_name + for tag in node: + if tag.get_local_name() in ("annotation", "documentation"): + continue + elif tag.get_local_name() in ('element', 'restriction'): + if debug: print element_name,"has not children!",tag + children = tag # element "alias"? + alias = True + elif tag.children(): + children = tag.children() + alias = False + else: + if debug: print element_name,"has not children!",tag + continue #TODO: abstract? + d = OrderedDict() + for e in children: + t = e['type'] + if not t: + t = e['base'] # complexContent (extension)! + if not t: + t = 'anyType' # no type given! + t = t.split(":") + if len(t)>1: + ns, type_name = t + else: + ns, type_name = None, t[0] + if element_name == type_name: + continue # prevent infinite recursion + uri = ns and e.get_namespace_uri(ns) or xsd_uri + if uri==xsd_uri: + # look for the type, None == any + fn = REVERSE_TYPE_MAP.get(unicode(type_name), None) + else: + # complex type, postprocess later + fn = elements.setdefault(unicode(type_name), OrderedDict()) + if e['name'] is not None and not alias: + e_name = unicode(e['name']) + d[e_name] = fn + else: + if debug: print "complexConent/simpleType/element", element_name, "=", type_name + d[None] = fn + if e['maxOccurs']=="unbounded": + # it's an array... TODO: compound arrays? + d.array = True + if e is not None and e.get_local_name() == 'extension' and e.children(): + # extend base element: + process_element(element_name, e.children()) + elements.setdefault(element_name, OrderedDict()).update(d) + + # check axis2 namespace at schema types attributes + self.namespace = dict(wsdl.types("schema", ns=xsd_uri)[:]).get('targetNamespace', self.namespace) + + imported_schemas = {} + + def preprocess_schema(schema): + "Find schema elements and complex types" + for element in schema.children(): + if element.get_local_name() in ('import', ): + schema_namespace = element['namespace'] + schema_location = element['schemaLocation'] + if schema_location is None: + if debug: print "Schema location not provided for %s!" % (schema_namespace, ) + continue + if schema_location in imported_schemas: + if debug: print "Schema %s already imported!" % (schema_location, ) + continue + imported_schemas[schema_location] = schema_namespace + if debug: print "Importing schema %s from %s" % (schema_namespace, schema_location) + # Open uri and read xml: + xml = fetch(schema_location) + # Parse imported XML schema (recursively): + imported_schema = SimpleXMLElement(xml, namespace=xsd_uri) + preprocess_schema(imported_schema) + + if element.get_local_name() in ('element', 'complexType', "simpleType"): + element_name = unicode(element['name']) + if debug: print "Parsing Element %s: %s" % (element.get_local_name(),element_name) + if element.get_local_name() == 'complexType': + children = element.children() + elif element.get_local_name() == 'simpleType': + children = element("restriction", ns=xsd_uri) + elif element.get_local_name() == 'element' and element['type']: + children = element + else: + children = element.children() + if children: + children = children.children() + elif element.get_local_name() == 'element': + children = element + if children: + process_element(element_name, children) + + def postprocess_element(elements): + "Fix unresolved references (elements referenced before its definition, thanks .net)" + for k,v in elements.items(): + if isinstance(v, OrderedDict): + if v.array: + elements[k] = [v] # convert arrays to python lists + if v!=elements: #TODO: fix recursive elements + postprocess_element(v) + if None in v and v[None]: # extension base? + if isinstance(v[None], dict): + for i, kk in enumerate(v[None]): + # extend base -keep orginal order- + elements[k].insert(kk, v[None][kk], i) + del v[None] + else: # "alias", just replace + if debug: print "Replacing ", k , " = ", v[None] + elements[k] = v[None] + #break + if isinstance(v, list): + for n in v: # recurse list + postprocess_element(n) + + + # process current wsdl schema: + for schema in wsdl.types("schema", ns=xsd_uri): + preprocess_schema(schema) + + postprocess_element(elements) + + for message in wsdl.message: + if debug: print "Processing message", message['name'] + part = message('part', error=False) + element = {} + if part: + element_name = part['element'] + if not element_name: + element_name = part['type'] # some uses type instead + element_name = get_local_name(element_name) + element = {element_name: elements.get(element_name)} + messages[message['name']] = element + + for port_type in wsdl.portType: + port_type_name = port_type['name'] + if debug: print "Processing port type", port_type_name + binding = port_type_bindings[port_type_name] + + for operation in port_type.operation: + op_name = operation['name'] + op = operations[op_name] + op['documentation'] = unicode(operation('documentation', error=False) or '') + if binding['soap_ver']: + #TODO: separe operation_binding from operation (non SOAP?) + input = get_local_name(operation.input['message']) + output = get_local_name(operation.output['message']) + op['input'] = messages[input] + op['output'] = messages[output] + + if debug: + import pprint + pprint.pprint(services) + + return services + +def parse_proxy(proxy_str): + "Parses proxy address user:pass@host:port into a dict suitable for httplib2" + proxy_dict = {} + if proxy_str is None: + return + if "@" in proxy_str: + user_pass, host_port = proxy_str.split("@") + else: + user_pass, host_port = "", proxy_str + if ":" in host_port: + host, port = host_port.split(":") + proxy_dict['proxy_host'], proxy_dict['proxy_port'] = host, int(port) + if ":" in user_pass: + proxy_dict['proxy_user'], proxy_dict['proxy_pass'] = user_pass.split(":") + return proxy_dict + + +if __name__=="__main__": + import sys + + if '--web2py' in sys.argv: + # test local sample webservice exposed by web2py + from client import SoapClient + if not '--wsdl' in sys.argv: + client = SoapClient( + location = "http://127.0.0.1:8000/webservices/sample/call/soap", + action = 'http://127.0.0.1:8000/webservices/sample/call/soap', # SOAPAction + namespace = "http://127.0.0.1:8000/webservices/sample/call/soap", + soap_ns='soap', trace = True, ns = False, exceptions=True) + else: + client = SoapClient(wsdl="http://127.0.0.1:8000/webservices/sample/call/soap?WSDL",trace=True) + response = client.Dummy() + print 'dummy', response + response = client.Echo(value='hola') + print 'echo', repr(response) + response = client.AddIntegers(a=1,b=2) + if not '--wsdl' in sys.argv: + result = response.AddResult # manully convert returned type + print int(result) + else: + result = response['AddResult'] + print result, type(result), "auto-unmarshalled" + + if '--raw' in sys.argv: + # raw (unmarshalled parameter) local sample webservice exposed by web2py + from client import SoapClient + client = SoapClient( + location = "http://127.0.0.1:8000/webservices/sample/call/soap", + action = 'http://127.0.0.1:8000/webservices/sample/call/soap', # SOAPAction + namespace = "http://127.0.0.1:8000/webservices/sample/call/soap", + soap_ns='soap', trace = True, ns = False) + params = SimpleXMLElement("""<?xml version="1.0" encoding="UTF-8"?><AddIntegers><a>3</a><b>2</b></AddIntegers>""") # manully convert returned type + response = client.call('AddIntegers',params) + result = response.AddResult + print int(result) # manully convert returned type + + if '--ctg' in sys.argv: + # test AFIP Agriculture webservice + client = SoapClient( + location = "https://fwshomo.afip.gov.ar/wsctg/services/CTGService", + action = 'http://impl.service.wsctg.afip.gov.ar/CTGService/', # SOAPAction + namespace = "http://impl.service.wsctg.afip.gov.ar/CTGService/", + trace = True, + ns = True) + response = client.dummy() + result = response.dummyResponse + print str(result.appserver) + print str(result.dbserver) + print str(result.authserver) + + if '--wsfe' in sys.argv: + # Demo & Test (AFIP Electronic Invoice): + ta_file = open("TA.xml") + try: + ta_string = ta_file.read() # read access ticket (wsaa.py) + finally: + ta_file.close() + ta = SimpleXMLElement(ta_string) + token = str(ta.credentials.token) + sign = str(ta.credentials.sign) + cuit = long(20267565393) + id = 1234 + cbte =199 + client = SoapClient( + location = "https://wswhomo.afip.gov.ar/wsfe/service.asmx", + action = 'http://ar.gov.afip.dif.facturaelectronica/', # SOAPAction + namespace = "http://ar.gov.afip.dif.facturaelectronica/", + trace = True) + results = client.FERecuperaQTYRequest( + argAuth= {"Token": token, "Sign": sign, "cuit":long(cuit)} + ) + if int(results.FERecuperaQTYRequestResult.RError.percode) != 0: + print "Percode: %s" % results.FERecuperaQTYRequestResult.RError.percode + print "MSGerror: %s" % results.FERecuperaQTYRequestResult.RError.perrmsg + else: + print int(results.FERecuperaQTYRequestResult.qty.value) + + if '--feriados' in sys.argv: + # Demo & Test: Argentina Holidays (Ministerio del Interior): + # this webservice seems disabled + from datetime import datetime, timedelta + client = SoapClient( + location = "http://webservices.mininterior.gov.ar/Feriados/Service.svc", + action = 'http://tempuri.org/IMyService/', # SOAPAction + namespace = "http://tempuri.org/FeriadoDS.xsd", + trace = True) + dt1 = datetime.today() - timedelta(days=60) + dt2 = datetime.today() + timedelta(days=60) + feriadosXML = client.FeriadosEntreFechasas_xml(dt1=dt1.isoformat(), dt2=dt2.isoformat()); + print feriadosXML + + if '--wsdl-parse' in sys.argv: + client = SoapClient() + # Test PySimpleSOAP WSDL + client.wsdl("file:C:/test.wsdl", debug=True) + # Test Java Axis WSDL: + client.wsdl('https://wsaahomo.afip.gov.ar/ws/services/LoginCms?wsdl',debug=True) + # Test .NET 2.0 WSDL: + client.wsdl('https://wswhomo.afip.gov.ar/wsfe/service.asmx?WSDL',debug=True) + client.wsdl('https://wswhomo.afip.gov.ar/wsfex/service.asmx?WSDL',debug=True) + client.wsdl('https://testdia.afip.gov.ar/Dia/Ws/wDigDepFiel/wDigDepFiel.asmx?WSDL',debug=True) + # Test JBoss WSDL: + client.wsdl('https://fwshomo.afip.gov.ar/wsctg/services/CTGService?wsdl',debug=True) + client.wsdl('https://wsaahomo.afip.gov.ar/ws/services/LoginCms?wsdl',debug=True) + + if '--wsdl-client' in sys.argv: + client = SoapClient(wsdl='https://wswhomo.afip.gov.ar/wsfex/service.asmx?WSDL',trace=True) + results = client.FEXDummy() + print results['FEXDummyResult']['AppServer'] + print results['FEXDummyResult']['DbServer'] + print results['FEXDummyResult']['AuthServer'] + ta_file = open("TA.xml") + try: + ta_string = ta_file.read() # read access ticket (wsaa.py) + finally: + ta_file.close() + ta = SimpleXMLElement(ta_string) + token = str(ta.credentials.token) + sign = str(ta.credentials.sign) + response = client.FEXGetCMP( + Auth={"Token": token, "Sign": sign, "Cuit": 20267565393}, + Cmp={"Tipo_cbte": 19, "Punto_vta": 1, "Cbte_nro": 1}) + result = response['FEXGetCMPResult'] + if False: print result + if 'FEXErr' in result: + print "FEXError:", result['FEXErr']['ErrCode'], result['FEXErr']['ErrCode'] + cbt = result['FEXResultGet'] + print cbt['Cae'] + FEX_event = result['FEXEvents'] + print FEX_event['EventCode'], FEX_event['EventMsg'] + + if '--wsdl-ctg' in sys.argv: + client = SoapClient(wsdl='https://fwshomo.afip.gov.ar/wsctg/services/CTGService?wsdl', + trace=True, ns = "ctg") + results = client.dummy() + print results + print results['DummyResponse']['appserver'] + print results['DummyResponse']['dbserver'] + print results['DummyResponse']['authserver'] + ta_file = open("TA.xml") + try: + ta_string = ta_file.read() # read access ticket (wsaa.py) + finally: + ta_file.close() + ta = SimpleXMLElement(ta_string) + token = str(ta.credentials.token) + sign = str(ta.credentials.sign) + print client.help("obtenerProvincias") + response = client.obtenerProvincias(auth={"token":token, "sign":sign, "cuitRepresentado":20267565393}) + print "response=",response + for ret in response: + print ret['return']['codigoProvincia'], ret['return']['descripcionProvincia'].encode("latin1") + prueba = dict(numeroCartaDePorte=512345678, codigoEspecie=23, + cuitRemitenteComercial=20267565393, cuitDestino=20267565393, cuitDestinatario=20267565393, + codigoLocalidadOrigen=3058, codigoLocalidadDestino=3059, + codigoCosecha='0910', pesoNetoCarga=1000, cantHoras=1, + patenteVehiculo='CZO985', cuitTransportista=20267565393, + numeroCTG="43816783", transaccion='10000001681', observaciones='', + ) + + response = client.solicitarCTG( + auth={"token": token, "sign": sign, "cuitRepresentado": 20267565393}, + solicitarCTGRequest= prueba) + + print response['return']['numeroCTG'] + + ##print parse_proxy(None) + ##print parse_proxy("host:1234") + ##print parse_proxy("user:pass@host:1234") + ##sys.exit(0) ADDED gluon/contrib/pysimplesoap/server.py Index: gluon/contrib/pysimplesoap/server.py ================================================================== --- gluon/contrib/pysimplesoap/server.py +++ gluon/contrib/pysimplesoap/server.py @@ -0,0 +1,454 @@ +#!/usr/bin/python +# -*- coding: latin-1 -*- +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by the +# Free Software Foundation; either version 3, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. + +"Simple SOAP Server implementation" + +__author__ = "Mariano Reingart (reingart@gmail.com)" +__copyright__ = "Copyright (C) 2010 Mariano Reingart" +__license__ = "LGPL 3.0" +__version__ = "1.02c" + +from simplexml import SimpleXMLElement, TYPE_MAP, DateTime, Date, Decimal + +DEBUG = False + + +class SoapDispatcher(object): + "Simple Dispatcher for SOAP Server" + + def __init__(self, name, documentation='', action='', location='', + namespace=None, prefix=False, + soap_uri="http://schemas.xmlsoap.org/soap/envelope/", + soap_ns='soap', + **kwargs): + self.methods = {} + self.name = name + self.documentation = documentation + self.action = action # base SoapAction + self.location = location + self.namespace = namespace # targetNamespace + self.prefix = prefix + self.soap_ns = soap_ns + self.soap_uri = soap_uri + + def register_function(self, name, fn, returns=None, args=None, doc=None): + self.methods[name] = fn, returns, args, doc or getattr(fn,"__doc__","") + + def dispatch(self, xml, action=None): + "Receive and proccess SOAP call" + # default values: + prefix = self.prefix + ret = fault = None + soap_ns, soap_uri = self.soap_ns, self.soap_uri + soap_fault_code = 'VersionMismatch' + + try: + request = SimpleXMLElement(xml, namespace=self.namespace) + + # detect soap prefix and uri (xmlns attributes of Envelope) + for k, v in request[:]: + if v in ("http://schemas.xmlsoap.org/soap/envelope/", + "http://www.w3.org/2003/05/soap-env",): + soap_ns = request.attributes()[k].localName + soap_uri = request.attributes()[k].value + + soap_fault_code = 'Client' + + # parse request message and get local method + method = request('Body', ns=soap_uri).children()(0) + if action: + # method name = action + name = action[len(self.action)+1:-1] + prefix = self.prefix + if not action or not name: + # method name = input message name + name = method.get_local_name() + prefix = method.get_prefix() + + if DEBUG: print "dispatch method", name + function, returns_types, args_types, doc = self.methods[name] + + # de-serialize parameters (if type definitions given) + if args_types: + args = method.children().unmarshall(args_types) + elif args_types is None: + args = {'request':method} # send raw request + else: + args = {} # no parameters + + soap_fault_code = 'Server' + # execute function + ret = function(**args) + if DEBUG: print ret + + except Exception, e: + import sys + etype, evalue, etb = sys.exc_info() + if DEBUG: + import traceback + detail = ''.join(traceback.format_exception(etype, evalue, etb)) + detail += '\n\nXML REQUEST\n\n' + xml + else: + detail = None + fault = {'faultcode': "%s.%s" % (soap_fault_code, etype.__name__), + 'faultstring': unicode(evalue), + 'detail': detail} + + # build response message + if not prefix: + xml = """<%(soap_ns)s:Envelope xmlns:%(soap_ns)s="%(soap_uri)s"/>""" + else: + xml = """<%(soap_ns)s:Envelope xmlns:%(soap_ns)s="%(soap_uri)s" + xmlns:%(prefix)s="%(namespace)s"/>""" + + xml = xml % {'namespace': self.namespace, 'prefix': prefix, + 'soap_ns': soap_ns, 'soap_uri': soap_uri} + + response = SimpleXMLElement(xml, namespace=self.namespace, + prefix=prefix) + + response['xmlns:xsi'] = "http://www.w3.org/2001/XMLSchema-instance" + response['xmlns:xsd'] = "http://www.w3.org/2001/XMLSchema" + + body = response.add_child("%s:Body" % soap_ns, ns=False) + if fault: + # generate a Soap Fault (with the python exception) + body.marshall("%s:Fault" % soap_ns, fault, ns=False) + else: + # return normal value + res = body.add_child("%sResponse" % name, ns=prefix) + if not prefix: + res['xmlns'] = self.namespace # add target namespace + + # serialize returned values (response) if type definition available + if returns_types: + if not isinstance(ret, dict): + res.marshall(returns_types.keys()[0], ret, ) + else: + for k,v in ret.items(): + res.marshall(k, v) + elif returns_types is None: + # merge xmlelement returned + res.import_node(ret) + + return response.as_xml() + + # Introspection functions: + + def list_methods(self): + "Return a list of aregistered operations" + return [(method, doc) for method, (function, returns, args, doc) in self.methods.items()] + + def help(self, method=None): + "Generate sample request and response messages" + (function, returns, args, doc) = self.methods[method] + xml = """ +<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> +<soap:Body><%(method)s xmlns="%(namespace)s"/></soap:Body> +</soap:Envelope>""" % {'method':method, 'namespace':self.namespace} + request = SimpleXMLElement(xml, namespace=self.namespace, prefix=self.prefix) + if args: + items = args.items() + elif args is None: + items = [('value', None)] + else: + items = [] + for k,v in items: + request(method).marshall(k, v, add_comments=True, ns=False) + + xml = """ +<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> +<soap:Body><%(method)sResponse xmlns="%(namespace)s"/></soap:Body> +</soap:Envelope>""" % {'method':method, 'namespace':self.namespace} + response = SimpleXMLElement(xml, namespace=self.namespace, prefix=self.prefix) + if returns: + items = returns.items() + elif args is None: + items = [('value', None)] + else: + items = [] + for k,v in items: + response('%sResponse'%method).marshall(k, v, add_comments=True, ns=False) + + return request.as_xml(pretty=True), response.as_xml(pretty=True), doc + + + def wsdl(self): + "Generate Web Service Description v1.1" + xml = """<?xml version="1.0"?> +<wsdl:definitions name="%(name)s" + targetNamespace="%(namespace)s" + xmlns:tns="%(namespace)s" + xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" + xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" + xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">%(documentation)s</wsdl:documentation> + + <wsdl:types> + <xsd:schema targetNamespace="%(namespace)s" + elementFormDefault="qualified" + xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + </xsd:schema> + </wsdl:types> + +</wsdl:definitions> +""" % {'namespace': self.namespace, 'name': self.name, 'documentation': self.documentation} + wsdl = SimpleXMLElement(xml) + + for method, (function, returns, args, doc) in self.methods.items(): + # create elements: + + def parse_element(name, values, array=False, complex=False): + if not complex: + element = wsdl('wsdl:types')('xsd:schema').add_child('xsd:element') + complex = element.add_child("xsd:complexType") + else: + complex = wsdl('wsdl:types')('xsd:schema').add_child('xsd:complexType') + element = complex + element['name'] = name + if values: + items = values + elif values is None: + items = [('value', None)] + else: + items = [] + if not array and items: + all = complex.add_child("xsd:all") + elif items: + all = complex.add_child("xsd:sequence") + for k,v in items: + e = all.add_child("xsd:element") + e['name'] = k + if array: + e[:]={'minOccurs': "0", 'maxOccurs': "unbounded"} + if v in TYPE_MAP.keys(): + t='xsd:%s' % TYPE_MAP[v] + elif v is None: + t='xsd:anyType' + elif isinstance(v, list): + n="ArrayOf%s%s" % (name, k) + l = [] + for d in v: + l.extend(d.items()) + parse_element(n, l, array=True, complex=True) + t = "tns:%s" % n + elif isinstance(v, dict): + n="%s%s" % (name, k) + parse_element(n, v.items(), complex=True) + t = "tns:%s" % n + e.add_attribute('type', t) + + parse_element("%s" % method, args and args.items()) + parse_element("%sResponse" % method, returns and returns.items()) + + # create messages: + for m,e in ('Input',''), ('Output','Response'): + message = wsdl.add_child('wsdl:message') + message['name'] = "%s%s" % (method, m) + part = message.add_child("wsdl:part") + part[:] = {'name': 'parameters', + 'element': 'tns:%s%s' % (method,e)} + + # create ports + portType = wsdl.add_child('wsdl:portType') + portType['name'] = "%sPortType" % self.name + for method, (function, returns, args, doc) in self.methods.items(): + op = portType.add_child('wsdl:operation') + op['name'] = method + if doc: + op.add_child("wsdl:documentation", doc) + input = op.add_child("wsdl:input") + input['message'] = "tns:%sInput" % method + output = op.add_child("wsdl:output") + output['message'] = "tns:%sOutput" % method + + # create bindings + binding = wsdl.add_child('wsdl:binding') + binding['name'] = "%sBinding" % self.name + binding['type'] = "tns:%sPortType" % self.name + soapbinding = binding.add_child('soap:binding') + soapbinding['style'] = "document" + soapbinding['transport'] = "http://schemas.xmlsoap.org/soap/http" + for method in self.methods.keys(): + op = binding.add_child('wsdl:operation') + op['name'] = method + soapop = op.add_child('soap:operation') + soapop['soapAction'] = self.action + method + soapop['style'] = 'document' + input = op.add_child("wsdl:input") + ##input.add_attribute('name', "%sInput" % method) + soapbody = input.add_child("soap:body") + soapbody["use"] = "literal" + output = op.add_child("wsdl:output") + ##output.add_attribute('name', "%sOutput" % method) + soapbody = output.add_child("soap:body") + soapbody["use"] = "literal" + + service = wsdl.add_child('wsdl:service') + service["name"] = "%sService" % self.name + service.add_child('wsdl:documentation', text=self.documentation) + port=service.add_child('wsdl:port') + port["name"] = "%s" % self.name + port["binding"] = "tns:%sBinding" % self.name + soapaddress = port.add_child('soap:address') + soapaddress["location"] = self.location + return wsdl.as_xml(pretty=True) + + +from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer +class SOAPHandler(BaseHTTPRequestHandler): + def do_GET(self): + "User viewable help information and wsdl" + args = self.path[1:].split("?") + print "serving", args + if self.path != "/" and args[0] not in self.server.dispatcher.methods.keys(): + self.send_error(404, "Method not found: %s" % args[0]) + else: + if self.path == "/": + # return wsdl if no method supplied + response = self.server.dispatcher.wsdl() + else: + # return supplied method help (?request or ?response messages) + req, res, doc = self.server.dispatcher.help(args[0]) + if len(args)==1 or args[1]=="request": + response = req + else: + response = res + self.send_response(200) + self.send_header("Content-type", "text/xml") + self.end_headers() + self.wfile.write(response) + + def do_POST(self): + "SOAP POST gateway" + self.send_response(200) + self.send_header("Content-type", "text/xml") + self.end_headers() + request = self.rfile.read(int(self.headers.getheader('content-length'))) + response = self.server.dispatcher.dispatch(request) + self.wfile.write(response) + + +if __name__=="__main__": + import sys + + dispatcher = SoapDispatcher( + name = "PySimpleSoapSample", + location = "http://localhost:8008/", + action = 'http://localhost:8008/', # SOAPAction + namespace = "http://example.com/pysimplesoapsamle/", prefix="ns0", + documentation = 'Example soap service using PySimpleSoap', + trace = True, + ns = True) + + def adder(p,c, dt=None): + "Add several values" + print c[0]['d'],c[1]['d'], + import datetime + dt = dt + datetime.timedelta(365) + return {'ab': p['a']+p['b'], 'dd': c[0]['d']+c[1]['d'], 'dt': dt} + + def dummy(in0): + "Just return input" + return in0 + + def echo(request): + "Copy request->response (generic, any type)" + return request.value + + dispatcher.register_function('Adder', adder, + returns={'AddResult': {'ab': int, 'dd': str } }, + args={'p': {'a': int,'b': int}, 'dt': Date, 'c': [{'d': Decimal}]}) + + dispatcher.register_function('Dummy', dummy, + returns={'out0': str}, + args={'in0': str}) + + dispatcher.register_function('Echo', echo) + + if '--local' in sys.argv: + + wsdl=dispatcher.wsdl() + print wsdl + testfile = open("C:/test.wsdl","w") + try: + testfile.write(wsdl) + finally: + testfile.close() + # dummy local test (clasic soap dialect) + xml = """<?xml version="1.0" encoding="UTF-8"?> + <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> + <soap:Body> + <Adder xmlns="http://example.com/sample.wsdl"> + <p><a>1</a><b>2</b></p><c><d>5000000.1</d><d>.2</d></c><dt>20100724</dt> + </Adder> + </soap:Body> + </soap:Envelope>""" + + print dispatcher.dispatch(xml) + + # dummy local test (modern soap dialect, SoapUI) + xml = """ +<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:pys="http://example.com/pysimplesoapsamle/"> + <soapenv:Header/> + <soapenv:Body> + <pys:Adder> + <pys:p><pys:a>9</pys:a><pys:b>3</pys:b></pys:p> + <pys:dt>19690720<!--1969-07-20T21:28:00--></pys:dt> + <pys:c><pys:d>10.001</pys:d><pys:d>5.02</pys:d></pys:c> + </pys:Adder> + </soapenv:Body> +</soapenv:Envelope> + """ + print dispatcher.dispatch(xml) + + # echo local test (generic soap service) + xml = """<?xml version="1.0" encoding="UTF-8"?> + <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <soap:Body> + <Echo xmlns="http://example.com/sample.wsdl"> + <value xsi:type="xsd:string">Hello world</value> + </Echo> + </soap:Body> + </soap:Envelope>""" + + print dispatcher.dispatch(xml) + + + for method, doc in dispatcher.list_methods(): + request, response, doc = dispatcher.help(method) + ##print request + ##print response + + if '--serve' in sys.argv: + print "Starting server..." + httpd = HTTPServer(("", 8008), SOAPHandler) + httpd.dispatcher = dispatcher + httpd.serve_forever() + + if '--consume' in sys.argv: + from client import SoapClient + client = SoapClient( + location = "http://localhost:8008/", + action = 'http://localhost:8008/', # SOAPAction + namespace = "http://example.com/sample.wsdl", + soap_ns='soap', + trace = True, + ns = False) + response = client.Adder(p={'a':1,'b':2},dt='20100724',c=[{'d':'1.20'},{'d':'2.01'}]) + result = response.AddResult + print int(result.ab) + print str(result.dd) + + ADDED gluon/contrib/pysimplesoap/simplexml.py Index: gluon/contrib/pysimplesoap/simplexml.py ================================================================== --- gluon/contrib/pysimplesoap/simplexml.py +++ gluon/contrib/pysimplesoap/simplexml.py @@ -0,0 +1,415 @@ +#!/usr/bin/python +# -*- coding: latin-1 -*- +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by the +# Free Software Foundation; either version 3, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. + +"Simple XML manipulation" + +__author__ = "Mariano Reingart (reingart@gmail.com)" +__copyright__ = "Copyright (C) 2008/009 Mariano Reingart" +__license__ = "LGPL 3.0" +__version__ = "1.02c" + +import xml.dom.minidom +from decimal import Decimal +import datetime +import time + +DEBUG = False + +# Functions to serialize/unserialize special immutable types: +datetime_u = lambda s: datetime.datetime.strptime(s, "%Y-%m-%dT%H:%M:%S") +datetime_m = lambda dt: dt.isoformat('T') +date_u = lambda s: datetime.datetime.strptime(s[0:10], "%Y-%m-%d").date() +date_m = lambda d: d.strftime("%Y-%m-%d") +time_u = lambda s: datetime.datetime.strptime(s, "%H:%M:%S").time() +time_m = lambda d: d.strftime("%H%M%S") +bool_u = lambda s: {'0':False, 'false': False, '1': True, 'true': True}[s] + +# aliases: +class Alias(): + def __init__(self, py_type, xml_type): + self.py_type, self.xml_type = py_type, xml_type + def __call__(self, value): + return self.py_type(value) + def __repr__(self): + return "<alias '%s' for '%s'>" % (self.xml_type, self.py_type) + +byte = Alias(str,'byte') +short = Alias(int,'short') +double = Alias(float,'double') +integer = Alias(long,'integer') +DateTime = datetime.datetime +Date = datetime.date +Time = datetime.time + +# Define convertion function (python type): xml schema type +TYPE_MAP = {str:'string',unicode:'string', + bool:'boolean', short:'short', byte:'byte', + int:'int', long:'long', integer:'integer', + float:'float', double:'double', + Decimal:'decimal', + datetime.datetime:'dateTime', datetime.date:'date', + } +TYPE_MARSHAL_FN = {datetime.datetime:datetime_m, datetime.date:date_m,} +TYPE_UNMARSHAL_FN = {datetime.datetime:datetime_u, datetime.date:date_u, + bool:bool_u, + } + + +class OrderedDict(dict): + "Minimal ordered dictionary for xsd:sequences" + def __init__(self): + self.__keys = [] + self.array = False + def __setitem__(self, key, value): + if key not in self.__keys: + self.__keys.append(key) + dict.__setitem__(self, key, value) + def insert(self, key, value, index=0): + if key not in self.__keys: + self.__keys.insert(index, key) + dict.__setitem__(self, key, value) + def __delitem__(self, key): + if key in self.__keys: + self.__keys.remove(key) + dict.__delitem__(self, key) + def __iter__(self): + return iter(self.__keys) + def keys(self): + return self.__keys + def items(self): + return [(key, self[key]) for key in self.__keys] + def update(self, other): + for k,v in other.items(): + self[k] = v + if isinstance(other, OrderedDict): + self.array = other.array + def __str__(self): + return "*%s*" % dict.__str__(self) + def __repr__(self): + s= "*{%s}*" % ", ".join(['%s: %s' % (repr(k),repr(v)) for k,v in self.items()]) + if self.array and False: + s = "[%s]" % s + return s + + +class SimpleXMLElement(object): + "Simple XML manipulation (simil PHP)" + + def __init__(self, text = None, elements = None, document = None, namespace = None, prefix=None): + self.__ns = namespace + self.__prefix = prefix + if text: + try: + self.__document = xml.dom.minidom.parseString(text) + except: + if DEBUG: print text + raise + self.__elements = [self.__document.documentElement] + else: + self.__elements = elements + self.__document = document + + def add_child(self,name,text=None,ns=True): + "Adding a child tag to a node" + if not ns or not self.__ns: + if DEBUG: print "adding %s" % (name) + element = self.__document.createElement(name) + else: + if DEBUG: print "adding %s ns %s %s" % (name, self.__ns,ns) + if self.__prefix: + element = self.__document.createElementNS(self.__ns, "%s:%s" % (self.__prefix, name)) + else: + element = self.__document.createElementNS(self.__ns, name) + if text: + if isinstance(text, unicode): + element.appendChild(self.__document.createTextNode(text)) + else: + element.appendChild(self.__document.createTextNode(str(text))) + self._element.appendChild(element) + return SimpleXMLElement( + elements=[element], + document=self.__document, + namespace=self.__ns, + prefix=self.__prefix) + + def __setattr__(self, tag, text): + "Add text child tag node (short form)" + if tag.startswith("_"): + object.__setattr__(self, tag, text) + else: + if DEBUG: print "__setattr__(%s,%s)" % (tag, text) + self.add_child(tag,text) + + def add_comment(self, data): + "Add an xml comment to this child" + comment = self.__document.createComment(data) + self._element.appendChild(comment) + + def as_xml(self,filename=None,pretty=False): + "Return the XML representation of the document" + if not pretty: + return self.__document.toxml('UTF-8') + else: + return self.__document.toprettyxml(encoding='UTF-8') + + def __repr__(self): + "Return the XML representation of this tag" + return self._element.toxml('UTF-8') + + def get_name(self): + "Return the tag name of this node" + return self._element.tagName + + def get_local_name(self): + "Return the tag loca name (prefix:name) of this node" + return self._element.localName + + def get_prefix(self): + "Return the namespace prefix of this node" + return self._element.prefix + + def get_namespace_uri(self, ns): + "Return the namespace uri for a prefix" + v = self.__document.documentElement.attributes['xmlns:%s' % ns] + return v.value + + def attributes(self): + "Return a dict of attributes for this tag" + #TODO: use slice syntax [:]? + return self._element.attributes + + def __getitem__(self, item): + "Return xml tag attribute value or a slice of attributes (iter)" + if DEBUG: print "__getitem__(%s)" % item + if isinstance(item,basestring): + if self._element.hasAttribute(item): + return self._element.attributes[item].value + elif isinstance(item, slice): + # return a list with name:values + return self._element.attributes.items()[item] + else: + # return element by index (position) + element = self.__elements[item] + return SimpleXMLElement( + elements=[element], + document=self.__document, + namespace=self.__ns, + prefix=self.__prefix) + + def add_attribute(self, name, value): + "Set an attribute value from a string" + self._element.setAttribute(name, value) + + def __setitem__(self, item, value): + "Set an attribute value" + if isinstance(item,basestring): + self.add_attribute(item, value) + elif isinstance(item, slice): + # set multiple attributes at once + for k, v in value.items(): + self.add_attribute(k, v) + + def __call__(self, tag=None, ns=None, children=False, error=True): + "Search (even in child nodes) and return a child tag by name" + try: + if tag is None: + # if no name given, iterate over siblings (same level) + return self.__iter__() + if children: + # future: filter children? by ns? + return self.children() + elements = None + if isinstance(tag, int): + # return tag by index + elements=[self.__elements[tag]] + if ns and not elements: + for ns_uri in isinstance(ns, (tuple, list)) and ns or (ns, ): + if DEBUG: print "searching %s by ns=%s" % (tag,ns_uri) + elements = self._element.getElementsByTagNameNS(ns_uri, tag) + if elements: + break + if self.__ns and not elements: + if DEBUG: print "searching %s by ns=%s" % (tag, self.__ns) + elements = self._element.getElementsByTagNameNS(self.__ns, tag) + if not elements: + if DEBUG: print "searching %s " % (tag) + elements = self._element.getElementsByTagName(tag) + if not elements: + if DEBUG: print self._element.toxml() + if error: + raise AttributeError("No elements found") + else: + return + return SimpleXMLElement( + elements=elements, + document=self.__document, + namespace=self.__ns, + prefix=self.__prefix) + except AttributeError, e: + raise AttributeError("Tag not found: %s (%s)" % (tag, str(e))) + + def __getattr__(self, tag): + "Shortcut for __call__" + return self.__call__(tag) + + def __iter__(self): + "Iterate over xml tags at this level" + try: + for __element in self.__elements: + yield SimpleXMLElement( + elements=[__element], + document=self.__document, + namespace=self.__ns, + prefix=self.__prefix) + except: + raise + + def __dir__(self): + "List xml children tags names" + return [node.tagName for node + in self._element.childNodes + if node.nodeType != node.TEXT_NODE] + + def children(self): + "Return xml children tags element" + elements=[__element for __element in self._element.childNodes + if __element.nodeType == __element.ELEMENT_NODE] + if not elements: + return None + #raise IndexError("Tag %s has no children" % self._element.tagName) + return SimpleXMLElement( + elements=elements, + document=self.__document, + namespace=self.__ns, + prefix=self.__prefix) + + def __len__(self): + "Return elements count" + return len(self.__elements) + + def __contains__( self, item): + "Search for a tag name in this element or child nodes" + return self._element.getElementsByTagName(item) + + def __unicode__(self): + "Returns the unicode text nodes of the current element" + if self._element.childNodes: + rc = u"" + for node in self._element.childNodes: + if node.nodeType == node.TEXT_NODE: + rc = rc + node.data + return rc + return '' + + def __str__(self): + "Returns the str text nodes of the current element" + return unicode(self).encode("utf8","ignore") + + def __int__(self): + "Returns the integer value of the current element" + return int(self.__str__()) + + def __float__(self): + "Returns the float value of the current element" + try: + return float(self.__str__()) + except: + raise IndexError(self._element.toxml()) + + _element = property(lambda self: self.__elements[0]) + + def unmarshall(self, types): + "Convert to python values the current serialized xml element" + # types is a dict of {tag name: convertion function} + # example: types={'p': {'a': int,'b': int}, 'c': [{'d':str}]} + # expected xml: <p><a>1</a><b>2</b></p><c><d>hola</d><d>chau</d> + # returnde value: {'p': {'a':1,'b':2}, `'c':[{'d':'hola'},{'d':'chau'}]} + d = {} + for node in self(): + name = str(node.get_local_name()) + try: + fn = types[name] + except (KeyError, ), e: + raise TypeError("Tag: %s invalid" % (name,)) + if isinstance(fn,list): + value = [] + children = node.children() + for child in children and children() or []: + value.append(child.unmarshall(fn[0])) + elif isinstance(fn,dict): + children = node.children() + value = children and children.unmarshall(fn) + else: + if fn is None: # xsd:anyType not unmarshalled + value = node + elif str(node) or fn == str: + try: + # get special desserialization function (if any) + fn = TYPE_UNMARSHAL_FN.get(fn,fn) + value = fn(unicode(node)) + except (ValueError, TypeError), e: + raise ValueError("Tag: %s: %s" % (name, unicode(e))) + else: + value = None + d[name] = value + return d + + def marshall(self, name, value, add_child=True, add_comments=False, ns=False): + "Analize python value and add the serialized XML element using tag name" + if isinstance(value, dict): # serialize dict (<key>value</key>) + child = add_child and self.add_child(name,ns=ns) or self + for k,v in value.items(): + child.marshall(k, v, add_comments=add_comments, ns=ns) + elif isinstance(value, tuple): # serialize tuple (<key>value</key>) + child = add_child and self.add_child(name,ns=ns) or self + for k,v in value: + getattr(self,name).marshall(k, v, add_comments=add_comments, ns=ns) + elif isinstance(value, list): # serialize lists + child=self.add_child(name,ns=ns) + if add_comments: + child.add_comment("Repetitive array of:") + for t in value: + child.marshall(name,t, False, add_comments=add_comments, ns=ns) + elif isinstance(value, basestring): # do not convert strings or unicodes + self.add_child(name,value,ns=ns) + elif value is None: # sent a empty tag? + self.add_child(name,ns=ns) + elif value in TYPE_MAP.keys(): + # add commented placeholders for simple tipes (for examples/help only) + child = self.add_child(name,ns=ns) + child.add_comment(TYPE_MAP[value]) + else: # the rest of object types are converted to string + # get special serialization function (if any) + fn = TYPE_MARSHAL_FN.get(type(value),str) + self.add_child(name,fn(value),ns=ns) + + def import_node(self, other): + x = self.__document.importNode(other._element, True) # deep copy + self._element.appendChild(x) + + +if __name__ == "__main__": + span = SimpleXMLElement('<span><a href="python.org.ar">pyar</a><prueba><i>1</i><float>1.5</float></prueba></span>') + assert str(span.a)==str(span('a'))==str(span.a(0))=="pyar" + assert span.a['href']=="python.org.ar" + assert int(span.prueba.i)==1 and float(span.prueba.float)==1.5 + span1 = SimpleXMLElement('<span><a href="google.com">google</a><a>yahoo</a><a>hotmail</a></span>') + assert [str(a) for a in span1.a()] == ['google', 'yahoo', 'hotmail'] + span1.add_child('a','altavista') + span1.b = "ex msn" + d = {'href':'http://www.bing.com/', 'alt': 'Bing'} + span1.b[:] = d + assert sorted([(k,v) for k,v in span1.b[:]]) == sorted(d.items()) + print span1.as_xml() + assert 'b' in span1 + span.import_node(span1) + print span.as_xml() ADDED gluon/contrib/rss2.py Index: gluon/contrib/rss2.py ================================================================== --- gluon/contrib/rss2.py +++ gluon/contrib/rss2.py @@ -0,0 +1,589 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +"""PyRSS2Gen - A Python library for generating RSS 2.0 feeds.""" + +__name__ = 'PyRSS2Gen' +__version__ = (1, 0, 0) +__author__ = 'Andrew Dalke <dalke@dalkescientific.com>' + +_generator_name = __name__ + '-' + '.'.join(map(str, __version__)) + +import datetime +import cStringIO + +# Could make this the base class; will need to add 'publish' + + +class WriteXmlMixin: + + def write_xml(self, outfile, encoding='iso-8859-1'): + from xml.sax import saxutils + handler = saxutils.XMLGenerator(outfile, encoding) + handler.startDocument() + self.publish(handler) + handler.endDocument() + + def to_xml(self, encoding='iso-8859-1'): + try: + import cStringIO as StringIO + except ImportError: + import StringIO + f = StringIO.StringIO() + self.write_xml(f, encoding) + return f.getvalue() + + +def _element( + handler, + name, + obj, + d={}, + ): + if isinstance(obj, basestring) or obj is None: + + # special-case handling to make the API easier + # to use for the common case. + + handler.startElement(name, d) + if obj is not None: + handler.characters(obj) + handler.endElement(name) + else: + + # It better know how to emit the correct XML. + + obj.publish(handler) + + +def _opt_element(handler, name, obj): + if obj is None: + return + _element(handler, name, obj) + + +def _format_date(dt): + """convert a datetime into an RFC 822 formatted date + + Input date must be in GMT. + """ + + # Looks like: + # Sat, 07 Sep 2002 00:00:01 GMT + # Can't use strftime because that's locale dependent + # + # Isn't there a standard way to do this for Python? The + # rfc822 and email.Utils modules assume a timestamp. The + # following is based on the rfc822 module. + + return '%s, %02d %s %04d %02d:%02d:%02d GMT' % ( + [ + 'Mon', + 'Tue', + 'Wed', + 'Thu', + 'Fri', + 'Sat', + 'Sun', + ][dt.weekday()], + dt.day, + [ + 'Jan', + 'Feb', + 'Mar', + 'Apr', + 'May', + 'Jun', + 'Jul', + 'Aug', + 'Sep', + 'Oct', + 'Nov', + 'Dec', + ][dt.month - 1], + dt.year, + dt.hour, + dt.minute, + dt.second, + ) + + +## +# A couple simple wrapper objects for the fields which +# take a simple value other than a string. + + +class IntElement: + + """implements the 'publish' API for integers + + Takes the tag name and the integer value to publish. + + (Could be used for anything which uses str() to be published + to text for XML.) + """ + + element_attrs = {} + + def __init__(self, name, val): + self.name = name + self.val = val + + def publish(self, handler): + handler.startElement(self.name, self.element_attrs) + handler.characters(str(self.val)) + handler.endElement(self.name) + + +class DateElement: + + """implements the 'publish' API for a datetime.datetime + + Takes the tag name and the datetime to publish. + + Converts the datetime to RFC 2822 timestamp (4-digit year). + """ + + def __init__(self, name, dt): + self.name = name + self.dt = dt + + def publish(self, handler): + _element(handler, self.name, _format_date(self.dt)) + + +# ### + + +class Category: + + """Publish a category element""" + + def __init__(self, category, domain=None): + self.category = category + self.domain = domain + + def publish(self, handler): + d = {} + if self.domain is not None: + d['domain'] = self.domain + _element(handler, 'category', self.category, d) + + +class Cloud: + + """Publish a cloud""" + + def __init__( + self, + domain, + port, + path, + registerProcedure, + protocol, + ): + self.domain = domain + self.port = port + self.path = path + self.registerProcedure = registerProcedure + self.protocol = protocol + + def publish(self, handler): + _element(handler, 'cloud', None, { + 'domain': self.domain, + 'port': str(self.port), + 'path': self.path, + 'registerProcedure': self.registerProcedure, + 'protocol': self.protocol, + }) + + +class Image: + + """Publish a channel Image""" + + element_attrs = {} + + def __init__( + self, + url, + title, + link, + width=None, + height=None, + description=None, + ): + self.url = url + self.title = title + self.link = link + self.width = width + self.height = height + self.description = description + + def publish(self, handler): + handler.startElement('image', self.element_attrs) + + _element(handler, 'url', self.url) + _element(handler, 'title', self.title) + _element(handler, 'link', self.link) + + width = self.width + if isinstance(width, int): + width = IntElement('width', width) + _opt_element(handler, 'width', width) + + height = self.height + if isinstance(height, int): + height = IntElement('height', height) + _opt_element(handler, 'height', height) + + _opt_element(handler, 'description', self.description) + + handler.endElement('image') + + +class Guid: + + """Publish a guid + + Defaults to being a permalink, which is the assumption if it's + omitted. Hence strings are always permalinks. + """ + + def __init__(self, guid, isPermaLink=1): + self.guid = guid + self.isPermaLink = isPermaLink + + def publish(self, handler): + d = {} + if self.isPermaLink: + d['isPermaLink'] = 'true' + else: + d['isPermaLink'] = 'false' + _element(handler, 'guid', self.guid, d) + + +class TextInput: + + """Publish a textInput + + Apparently this is rarely used. + """ + + element_attrs = {} + + def __init__( + self, + title, + description, + name, + link, + ): + self.title = title + self.description = description + self.name = name + self.link = link + + def publish(self, handler): + handler.startElement('textInput', self.element_attrs) + _element(handler, 'title', self.title) + _element(handler, 'description', self.description) + _element(handler, 'name', self.name) + _element(handler, 'link', self.link) + handler.endElement('textInput') + + +class Enclosure: + + """Publish an enclosure""" + + def __init__( + self, + url, + length, + type, + ): + self.url = url + self.length = length + self.type = type + + def publish(self, handler): + _element(handler, 'enclosure', None, + {'url': self.url, 'length': str(self.length), 'type': self.type}) + + +class Source: + + """Publish the item's original source, used by aggregators""" + + def __init__(self, name, url): + self.name = name + self.url = url + + def publish(self, handler): + _element(handler, 'source', self.name, {'url': self.url}) + + +class SkipHours: + + """Publish the skipHours + + This takes a list of hours, as integers. + """ + + element_attrs = {} + + def __init__(self, hours): + self.hours = hours + + def publish(self, handler): + if self.hours: + handler.startElement('skipHours', self.element_attrs) + for hour in self.hours: + _element(handler, 'hour', str(hour)) + handler.endElement('skipHours') + + +class SkipDays: + + """Publish the skipDays + + This takes a list of days as strings. + """ + + element_attrs = {} + + def __init__(self, days): + self.days = days + + def publish(self, handler): + if self.days: + handler.startElement('skipDays', self.element_attrs) + for day in self.days: + _element(handler, 'day', day) + handler.endElement('skipDays') + + +class RSS2(WriteXmlMixin): + + """The main RSS class. + + Stores the channel attributes, with the \"category\" elements under + \".categories\" and the RSS items under \".items\". + """ + + rss_attrs = {'version': '2.0'} + element_attrs = {} + + def __init__( + self, + title, + link, + description, + language=None, + copyright=None, + managingEditor=None, + webMaster=None, + pubDate=None, + lastBuildDate=None, + categories=None, + generator=_generator_name, + docs='http://blogs.law.harvard.edu/tech/rss', + cloud=None, + ttl=None, + image=None, + rating=None, + textInput=None, + skipHours=None, + skipDays=None, + items=None, + ): + + self.title = title + self.link = link + self.description = description + self.language = language + self.copyright = copyright + self.managingEditor = managingEditor + + self.webMaster = webMaster + self.pubDate = pubDate + self.lastBuildDate = lastBuildDate + + if categories is None: + categories = [] + self.categories = categories + self.generator = generator + self.docs = docs + self.cloud = cloud + self.ttl = ttl + self.image = image + self.rating = rating + self.textInput = textInput + self.skipHours = skipHours + self.skipDays = skipDays + + if items is None: + items = [] + self.items = items + + def publish(self, handler): + handler.startElement('rss', self.rss_attrs) + handler.startElement('channel', self.element_attrs) + _element(handler, 'title', self.title) + _element(handler, 'link', self.link) + _element(handler, 'description', self.description) + + self.publish_extensions(handler) + + _opt_element(handler, 'language', self.language) + _opt_element(handler, 'copyright', self.copyright) + _opt_element(handler, 'managingEditor', self.managingEditor) + _opt_element(handler, 'webMaster', self.webMaster) + + pubDate = self.pubDate + if isinstance(pubDate, datetime.datetime): + pubDate = DateElement('pubDate', pubDate) + _opt_element(handler, 'pubDate', pubDate) + + lastBuildDate = self.lastBuildDate + if isinstance(lastBuildDate, datetime.datetime): + lastBuildDate = DateElement('lastBuildDate', lastBuildDate) + _opt_element(handler, 'lastBuildDate', lastBuildDate) + + for category in self.categories: + if isinstance(category, basestring): + category = Category(category) + category.publish(handler) + + _opt_element(handler, 'generator', self.generator) + _opt_element(handler, 'docs', self.docs) + + if self.cloud is not None: + self.cloud.publish(handler) + + ttl = self.ttl + if isinstance(self.ttl, int): + ttl = IntElement('ttl', ttl) + _opt_element(handler, 'tt', ttl) + + if self.image is not None: + self.image.publish(handler) + + _opt_element(handler, 'rating', self.rating) + if self.textInput is not None: + self.textInput.publish(handler) + if self.skipHours is not None: + self.skipHours.publish(handler) + if self.skipDays is not None: + self.skipDays.publish(handler) + + for item in self.items: + item.publish(handler) + + handler.endElement('channel') + handler.endElement('rss') + + def publish_extensions(self, handler): + + # Derived classes can hook into this to insert + # output after the three required fields. + + pass + + +class RSSItem(WriteXmlMixin): + + """Publish an RSS Item""" + + element_attrs = {} + + def __init__( + self, + title=None, + link=None, + description=None, + author=None, + categories=None, + comments=None, + enclosure=None, + guid=None, + pubDate=None, + source=None, + ): + + if title is None and description is None: + raise TypeError( + "RSSItem must define at least one of 'title' or 'description'") + self.title = title + self.link = link + self.description = description + self.author = author + if categories is None: + categories = [] + self.categories = categories + self.comments = comments + self.enclosure = enclosure + self.guid = guid + self.pubDate = pubDate + self.source = source + + # It sure does get tedious typing these names three times... + + def publish(self, handler): + handler.startElement('item', self.element_attrs) + _opt_element(handler, 'title', self.title) + _opt_element(handler, 'link', self.link) + self.publish_extensions(handler) + _opt_element(handler, 'description', self.description) + _opt_element(handler, 'author', self.author) + + for category in self.categories: + if isinstance(category, basestring): + category = Category(category) + category.publish(handler) + + _opt_element(handler, 'comments', self.comments) + if self.enclosure is not None: + self.enclosure.publish(handler) + _opt_element(handler, 'guid', self.guid) + + pubDate = self.pubDate + if isinstance(pubDate, datetime.datetime): + pubDate = DateElement('pubDate', pubDate) + _opt_element(handler, 'pubDate', pubDate) + + if self.source is not None: + self.source.publish(handler) + + handler.endElement('item') + + def publish_extensions(self, handler): + + # Derived classes can hook into this to insert + # output after the title and link elements + + pass + + +def dumps(rss, encoding='utf-8'): + s = cStringIO.StringIO() + rss.write_xml(s, encoding) + return s.getvalue() + + +def test(): + rss = RSS2(title='web2py feed', link='http://www.web2py.com', + description='About web2py', + lastBuildDate=datetime.datetime.now(), + items=[RSSItem(title='web2py and PyRSS2Gen-0.0', + link='http://www.web2py.com/examples/simple_examples/getrss', + description='web2py can now make rss feeds!', + guid=Guid('http://www.web2py.com/'), + pubDate=datetime.datetime(2007, 11, 14, 10, 30))]) + return dumps(rss) + + +if __name__ == '__main__': + print test() + ADDED gluon/contrib/shell.py Index: gluon/contrib/shell.py ================================================================== --- gluon/contrib/shell.py +++ gluon/contrib/shell.py @@ -0,0 +1,267 @@ +#!/usr/bin/python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Modified by Massimo Di Pierro so it works with and without GAE with web2py +# the modified version of this file is still released under the original Apache license +# and it is not released under the web2py license. +# +# This should be compatible with the Apache license since it states: +# "For the purposes of this License, Derivative Works shall not include works +# that remain separable from, or merely link (or bind by name) to the interfaces of, +# the Work and Derivative Works thereof." +# +# In fact this file is Apache-licensed and it is separable from the rest of web2py. + + +""" +An interactive, stateful AJAX shell that runs Python code on the server. +""" + +import logging +import new +import os +import cPickle +import sys +import traceback +import types +import wsgiref.handlers +import StringIO +import threading +locker = threading.RLock() + +# Set to True if stack traces should be shown in the browser, etc. +_DEBUG = True + +# The entity kind for shell historys. Feel free to rename to suit your app. +_HISTORY_KIND = '_Shell_History' + +# Types that can't be pickled. +UNPICKLABLE_TYPES = ( + types.ModuleType, + types.TypeType, + types.ClassType, + types.FunctionType, + ) + +# Unpicklable statements to seed new historys with. +INITIAL_UNPICKLABLES = [ + 'import logging', + 'import os', + 'import sys', + ] + + +class History: + """A shell history. Stores the history's globals. + + Each history globals is stored in one of two places: + + If the global is picklable, it's stored in the parallel globals and + global_names list properties. (They're parallel lists to work around the + unfortunate fact that the datastore can't store dictionaries natively.) + + If the global is not picklable (e.g. modules, classes, and functions), or if + it was created by the same statement that created an unpicklable global, + it's not stored directly. Instead, the statement is stored in the + unpicklables list property. On each request, before executing the current + statement, the unpicklable statements are evaluated to recreate the + unpicklable globals. + + The unpicklable_names property stores all of the names of globals that were + added by unpicklable statements. When we pickle and store the globals after + executing a statement, we skip the ones in unpicklable_names. + + Using Text instead of string is an optimization. We don't query on any of + these properties, so they don't need to be indexed. + """ + global_names = [] + globals = [] + unpicklable_names = [] + unpicklables = [] + + def set_global(self, name, value): + """Adds a global, or updates it if it already exists. + + Also removes the global from the list of unpicklable names. + + Args: + name: the name of the global to remove + value: any picklable value + """ + blob = cPickle.dumps(value) + + if name in self.global_names: + index = self.global_names.index(name) + self.globals[index] = blob + else: + self.global_names.append(name) + self.globals.append(blob) + + self.remove_unpicklable_name(name) + + def remove_global(self, name): + """Removes a global, if it exists. + + Args: + name: string, the name of the global to remove + """ + if name in self.global_names: + index = self.global_names.index(name) + del self.global_names[index] + del self.globals[index] + + def globals_dict(self): + """Returns a dictionary view of the globals. + """ + return dict((name, cPickle.loads(val)) + for name, val in zip(self.global_names, self.globals)) + + def add_unpicklable(self, statement, names): + """Adds a statement and list of names to the unpicklables. + + Also removes the names from the globals. + + Args: + statement: string, the statement that created new unpicklable global(s). + names: list of strings; the names of the globals created by the statement. + """ + self.unpicklables.append(statement) + + for name in names: + self.remove_global(name) + if name not in self.unpicklable_names: + self.unpicklable_names.append(name) + + def remove_unpicklable_name(self, name): + """Removes a name from the list of unpicklable names, if it exists. + + Args: + name: string, the name of the unpicklable global to remove + """ + if name in self.unpicklable_names: + self.unpicklable_names.remove(name) + +def represent(obj): + """Returns a string representing the given object's value, which should allow the + code below to determine whether the object changes over time. + """ + try: + return cPickle.dumps(obj) + except: + return repr(obj) + +def run(history, statement, env={}): + """ + Evaluates a python statement in a given history and returns the result. + """ + history.unpicklables = INITIAL_UNPICKLABLES + + # extract the statement to be run + if not statement: + return '' + + # the python compiler doesn't like network line endings + statement = statement.replace('\r\n', '\n') + + # add a couple newlines at the end of the statement. this makes + # single-line expressions such as 'class Foo: pass' evaluate happily. + statement += '\n\n' + + + # log and compile the statement up front + try: + logging.info('Compiling and evaluating:\n%s' % statement) + compiled = compile(statement, '<string>', 'single') + except: + return str(traceback.format_exc()) + + # create a dedicated module to be used as this statement's __main__ + statement_module = new.module('__main__') + + # use this request's __builtin__, since it changes on each request. + # this is needed for import statements, among other things. + import __builtin__ + statement_module.__builtins__ = __builtin__ + + # load the history from the datastore + history = History() + + # swap in our custom module for __main__. then unpickle the history + # globals, run the statement, and re-pickle the history globals, all + # inside it. + old_main = sys.modules.get('__main__') + output = StringIO.StringIO() + try: + sys.modules['__main__'] = statement_module + statement_module.__name__ = '__main__' + statement_module.__dict__.update(env) + + # re-evaluate the unpicklables + for code in history.unpicklables: + exec code in statement_module.__dict__ + + # re-initialize the globals + for name, val in history.globals_dict().items(): + try: + statement_module.__dict__[name] = val + except: + msg = 'Dropping %s since it could not be unpickled.\n' % name + output.write(msg) + logging.warning(msg + traceback.format_exc()) + history.remove_global(name) + + # run! + old_globals = dict((key,represent(value)) for key,value in statement_module.__dict__.items()) + try: + old_stdout, old_stderr = sys.stdout, sys.stderr + try: + sys.stderr = sys.stdout = output + locker.acquire() + exec compiled in statement_module.__dict__ + finally: + locker.release() + sys.stdout, sys.stderr = old_stdout, old_stderr + except: + output.write(str(traceback.format_exc())) + return output.getvalue() + + # extract the new globals that this statement added + new_globals = {} + for name, val in statement_module.__dict__.items(): + if name not in old_globals or represent(val) != old_globals[name]: + new_globals[name] = val + + if True in [isinstance(val, UNPICKLABLE_TYPES) + for val in new_globals.values()]: + # this statement added an unpicklable global. store the statement and + # the names of all of the globals it added in the unpicklables. + history.add_unpicklable(statement, new_globals.keys()) + logging.debug('Storing this statement as an unpicklable.') + else: + # this statement didn't add any unpicklables. pickle and store the + # new globals back into the datastore. + for name, val in new_globals.items(): + if not name.startswith('__'): + history.set_global(name, val) + + finally: + sys.modules['__main__'] = old_main + return output.getvalue() + +if __name__=='__main__': + history=History() + while True: print run(history, raw_input('>>> ')).rstrip() + ADDED gluon/contrib/simplejson/LICENSE.txt Index: gluon/contrib/simplejson/LICENSE.txt ================================================================== --- gluon/contrib/simplejson/LICENSE.txt +++ gluon/contrib/simplejson/LICENSE.txt @@ -0,0 +1,19 @@ +Copyright (c) 2006 Bob Ippolito + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. ADDED gluon/contrib/simplejson/__init__.py Index: gluon/contrib/simplejson/__init__.py ================================================================== --- gluon/contrib/simplejson/__init__.py +++ gluon/contrib/simplejson/__init__.py @@ -0,0 +1,439 @@ +r"""JSON (JavaScript Object Notation) <http://json.org> is a subset of +JavaScript syntax (ECMA-262 3rd edition) used as a lightweight data +interchange format. + +:mod:`simplejson` exposes an API familiar to users of the standard library +:mod:`marshal` and :mod:`pickle` modules. It is the externally maintained +version of the :mod:`json` library contained in Python 2.6, but maintains +compatibility with Python 2.4 and Python 2.5 and (currently) has +significant performance advantages, even without using the optional C +extension for speedups. + +Encoding basic Python object hierarchies:: + + >>> import simplejson as json + >>> json.dumps(['foo', {'bar': ('baz', None, 1.0, 2)}]) + '["foo", {"bar": ["baz", null, 1.0, 2]}]' + >>> print json.dumps("\"foo\bar") + "\"foo\bar" + >>> print json.dumps(u'\u1234') + "\u1234" + >>> print json.dumps('\\') + "\\" + >>> print json.dumps({"c": 0, "b": 0, "a": 0}, sort_keys=True) + {"a": 0, "b": 0, "c": 0} + >>> from StringIO import StringIO + >>> io = StringIO() + >>> json.dump(['streaming API'], io) + >>> io.getvalue() + '["streaming API"]' + +Compact encoding:: + + >>> import simplejson as json + >>> json.dumps([1,2,3,{'4': 5, '6': 7}], separators=(',',':')) + '[1,2,3,{"4":5,"6":7}]' + +Pretty printing:: + + >>> import simplejson as json + >>> s = json.dumps({'4': 5, '6': 7}, sort_keys=True, indent=' ') + >>> print '\n'.join([l.rstrip() for l in s.splitlines()]) + { + "4": 5, + "6": 7 + } + +Decoding JSON:: + + >>> import simplejson as json + >>> obj = [u'foo', {u'bar': [u'baz', None, 1.0, 2]}] + >>> json.loads('["foo", {"bar":["baz", null, 1.0, 2]}]') == obj + True + >>> json.loads('"\\"foo\\bar"') == u'"foo\x08ar' + True + >>> from StringIO import StringIO + >>> io = StringIO('["streaming API"]') + >>> json.load(io)[0] == 'streaming API' + True + +Specializing JSON object decoding:: + + >>> import simplejson as json + >>> def as_complex(dct): + ... if '__complex__' in dct: + ... return complex(dct['real'], dct['imag']) + ... return dct + ... + >>> json.loads('{"__complex__": true, "real": 1, "imag": 2}', + ... object_hook=as_complex) + (1+2j) + >>> from decimal import Decimal + >>> json.loads('1.1', parse_float=Decimal) == Decimal('1.1') + True + +Specializing JSON object encoding:: + + >>> import simplejson as json + >>> def encode_complex(obj): + ... if isinstance(obj, complex): + ... return [obj.real, obj.imag] + ... raise TypeError(repr(o) + " is not JSON serializable") + ... + >>> json.dumps(2 + 1j, default=encode_complex) + '[2.0, 1.0]' + >>> json.JSONEncoder(default=encode_complex).encode(2 + 1j) + '[2.0, 1.0]' + >>> ''.join(json.JSONEncoder(default=encode_complex).iterencode(2 + 1j)) + '[2.0, 1.0]' + + +Using simplejson.tool from the shell to validate and pretty-print:: + + $ echo '{"json":"obj"}' | python -m simplejson.tool + { + "json": "obj" + } + $ echo '{ 1.2:3.4}' | python -m simplejson.tool + Expecting property name: line 1 column 2 (char 2) +""" +__version__ = '2.1.3' +__all__ = [ + 'dump', 'dumps', 'load', 'loads', + 'JSONDecoder', 'JSONDecodeError', 'JSONEncoder', + 'OrderedDict', +] + +__author__ = 'Bob Ippolito <bob@redivi.com>' + +from decimal import Decimal + +from decoder import JSONDecoder, JSONDecodeError +from encoder import JSONEncoder +def _import_OrderedDict(): + import collections + try: + return collections.OrderedDict + except AttributeError: + import ordered_dict + return ordered_dict.OrderedDict +OrderedDict = _import_OrderedDict() + +def _import_c_make_encoder(): + try: + raise ImportError # because assumes simplejson in path + from simplejson._speedups import make_encoder + return make_encoder + except ImportError: + return None + +_default_encoder = JSONEncoder( + skipkeys=False, + ensure_ascii=True, + check_circular=True, + allow_nan=True, + indent=None, + separators=None, + encoding='utf-8', + default=None, + use_decimal=False, +) + +def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True, + allow_nan=True, cls=None, indent=None, separators=None, + encoding='utf-8', default=None, use_decimal=False, **kw): + """Serialize ``obj`` as a JSON formatted stream to ``fp`` (a + ``.write()``-supporting file-like object). + + If ``skipkeys`` is true then ``dict`` keys that are not basic types + (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``) + will be skipped instead of raising a ``TypeError``. + + If ``ensure_ascii`` is false, then the some chunks written to ``fp`` + may be ``unicode`` instances, subject to normal Python ``str`` to + ``unicode`` coercion rules. Unless ``fp.write()`` explicitly + understands ``unicode`` (as in ``codecs.getwriter()``) this is likely + to cause an error. + + If ``check_circular`` is false, then the circular reference check + for container types will be skipped and a circular reference will + result in an ``OverflowError`` (or worse). + + If ``allow_nan`` is false, then it will be a ``ValueError`` to + serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``) + in strict compliance of the JSON specification, instead of using the + JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``). + + If *indent* is a string, then JSON array elements and object members + will be pretty-printed with a newline followed by that string repeated + for each level of nesting. ``None`` (the default) selects the most compact + representation without any newlines. For backwards compatibility with + versions of simplejson earlier than 2.1.0, an integer is also accepted + and is converted to a string with that many spaces. + + If ``separators`` is an ``(item_separator, dict_separator)`` tuple + then it will be used instead of the default ``(', ', ': ')`` separators. + ``(',', ':')`` is the most compact JSON representation. + + ``encoding`` is the character encoding for str instances, default is UTF-8. + + ``default(obj)`` is a function that should return a serializable version + of obj or raise TypeError. The default simply raises TypeError. + + If *use_decimal* is true (default: ``False``) then decimal.Decimal + will be natively serialized to JSON with full precision. + + To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the + ``.default()`` method to serialize additional types), specify it with + the ``cls`` kwarg. + + """ + # cached encoder + if (not skipkeys and ensure_ascii and + check_circular and allow_nan and + cls is None and indent is None and separators is None and + encoding == 'utf-8' and default is None and not use_decimal + and not kw): + iterable = _default_encoder.iterencode(obj) + else: + if cls is None: + cls = JSONEncoder + iterable = cls(skipkeys=skipkeys, ensure_ascii=ensure_ascii, + check_circular=check_circular, allow_nan=allow_nan, indent=indent, + separators=separators, encoding=encoding, + default=default, use_decimal=use_decimal, **kw).iterencode(obj) + # could accelerate with writelines in some versions of Python, at + # a debuggability cost + for chunk in iterable: + fp.write(chunk) + + +def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True, + allow_nan=True, cls=None, indent=None, separators=None, + encoding='utf-8', default=None, use_decimal=False, **kw): + """Serialize ``obj`` to a JSON formatted ``str``. + + If ``skipkeys`` is false then ``dict`` keys that are not basic types + (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``) + will be skipped instead of raising a ``TypeError``. + + If ``ensure_ascii`` is false, then the return value will be a + ``unicode`` instance subject to normal Python ``str`` to ``unicode`` + coercion rules instead of being escaped to an ASCII ``str``. + + If ``check_circular`` is false, then the circular reference check + for container types will be skipped and a circular reference will + result in an ``OverflowError`` (or worse). + + If ``allow_nan`` is false, then it will be a ``ValueError`` to + serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``) in + strict compliance of the JSON specification, instead of using the + JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``). + + If ``indent`` is a string, then JSON array elements and object members + will be pretty-printed with a newline followed by that string repeated + for each level of nesting. ``None`` (the default) selects the most compact + representation without any newlines. For backwards compatibility with + versions of simplejson earlier than 2.1.0, an integer is also accepted + and is converted to a string with that many spaces. + + If ``separators`` is an ``(item_separator, dict_separator)`` tuple + then it will be used instead of the default ``(', ', ': ')`` separators. + ``(',', ':')`` is the most compact JSON representation. + + ``encoding`` is the character encoding for str instances, default is UTF-8. + + ``default(obj)`` is a function that should return a serializable version + of obj or raise TypeError. The default simply raises TypeError. + + If *use_decimal* is true (default: ``False``) then decimal.Decimal + will be natively serialized to JSON with full precision. + + To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the + ``.default()`` method to serialize additional types), specify it with + the ``cls`` kwarg. + + """ + # cached encoder + if (not skipkeys and ensure_ascii and + check_circular and allow_nan and + cls is None and indent is None and separators is None and + encoding == 'utf-8' and default is None and not use_decimal + and not kw): + return _default_encoder.encode(obj) + if cls is None: + cls = JSONEncoder + return cls( + skipkeys=skipkeys, ensure_ascii=ensure_ascii, + check_circular=check_circular, allow_nan=allow_nan, indent=indent, + separators=separators, encoding=encoding, default=default, + use_decimal=use_decimal, **kw).encode(obj) + + +_default_decoder = JSONDecoder(encoding=None, object_hook=None, + object_pairs_hook=None) + + +def load(fp, encoding=None, cls=None, object_hook=None, parse_float=None, + parse_int=None, parse_constant=None, object_pairs_hook=None, + use_decimal=False, **kw): + """Deserialize ``fp`` (a ``.read()``-supporting file-like object containing + a JSON document) to a Python object. + + *encoding* determines the encoding used to interpret any + :class:`str` objects decoded by this instance (``'utf-8'`` by + default). It has no effect when decoding :class:`unicode` objects. + + Note that currently only encodings that are a superset of ASCII work, + strings of other encodings should be passed in as :class:`unicode`. + + *object_hook*, if specified, will be called with the result of every + JSON object decoded and its return value will be used in place of the + given :class:`dict`. This can be used to provide custom + deserializations (e.g. to support JSON-RPC class hinting). + + *object_pairs_hook* is an optional function that will be called with + the result of any object literal decode with an ordered list of pairs. + The return value of *object_pairs_hook* will be used instead of the + :class:`dict`. This feature can be used to implement custom decoders + that rely on the order that the key and value pairs are decoded (for + example, :func:`collections.OrderedDict` will remember the order of + insertion). If *object_hook* is also defined, the *object_pairs_hook* + takes priority. + + *parse_float*, if specified, will be called with the string of every + JSON float to be decoded. By default, this is equivalent to + ``float(num_str)``. This can be used to use another datatype or parser + for JSON floats (e.g. :class:`decimal.Decimal`). + + *parse_int*, if specified, will be called with the string of every + JSON int to be decoded. By default, this is equivalent to + ``int(num_str)``. This can be used to use another datatype or parser + for JSON integers (e.g. :class:`float`). + + *parse_constant*, if specified, will be called with one of the + following strings: ``'-Infinity'``, ``'Infinity'``, ``'NaN'``. This + can be used to raise an exception if invalid JSON numbers are + encountered. + + If *use_decimal* is true (default: ``False``) then it implies + parse_float=decimal.Decimal for parity with ``dump``. + + To use a custom ``JSONDecoder`` subclass, specify it with the ``cls`` + kwarg. + + """ + return loads(fp.read(), + encoding=encoding, cls=cls, object_hook=object_hook, + parse_float=parse_float, parse_int=parse_int, + parse_constant=parse_constant, object_pairs_hook=object_pairs_hook, + use_decimal=use_decimal, **kw) + + +def loads(s, encoding=None, cls=None, object_hook=None, parse_float=None, + parse_int=None, parse_constant=None, object_pairs_hook=None, + use_decimal=False, **kw): + """Deserialize ``s`` (a ``str`` or ``unicode`` instance containing a JSON + document) to a Python object. + + *encoding* determines the encoding used to interpret any + :class:`str` objects decoded by this instance (``'utf-8'`` by + default). It has no effect when decoding :class:`unicode` objects. + + Note that currently only encodings that are a superset of ASCII work, + strings of other encodings should be passed in as :class:`unicode`. + + *object_hook*, if specified, will be called with the result of every + JSON object decoded and its return value will be used in place of the + given :class:`dict`. This can be used to provide custom + deserializations (e.g. to support JSON-RPC class hinting). + + *object_pairs_hook* is an optional function that will be called with + the result of any object literal decode with an ordered list of pairs. + The return value of *object_pairs_hook* will be used instead of the + :class:`dict`. This feature can be used to implement custom decoders + that rely on the order that the key and value pairs are decoded (for + example, :func:`collections.OrderedDict` will remember the order of + insertion). If *object_hook* is also defined, the *object_pairs_hook* + takes priority. + + *parse_float*, if specified, will be called with the string of every + JSON float to be decoded. By default, this is equivalent to + ``float(num_str)``. This can be used to use another datatype or parser + for JSON floats (e.g. :class:`decimal.Decimal`). + + *parse_int*, if specified, will be called with the string of every + JSON int to be decoded. By default, this is equivalent to + ``int(num_str)``. This can be used to use another datatype or parser + for JSON integers (e.g. :class:`float`). + + *parse_constant*, if specified, will be called with one of the + following strings: ``'-Infinity'``, ``'Infinity'``, ``'NaN'``. This + can be used to raise an exception if invalid JSON numbers are + encountered. + + If *use_decimal* is true (default: ``False``) then it implies + parse_float=decimal.Decimal for parity with ``dump``. + + To use a custom ``JSONDecoder`` subclass, specify it with the ``cls`` + kwarg. + + """ + if (cls is None and encoding is None and object_hook is None and + parse_int is None and parse_float is None and + parse_constant is None and object_pairs_hook is None + and not use_decimal and not kw): + return _default_decoder.decode(s) + if cls is None: + cls = JSONDecoder + if object_hook is not None: + kw['object_hook'] = object_hook + if object_pairs_hook is not None: + kw['object_pairs_hook'] = object_pairs_hook + if parse_float is not None: + kw['parse_float'] = parse_float + if parse_int is not None: + kw['parse_int'] = parse_int + if parse_constant is not None: + kw['parse_constant'] = parse_constant + if use_decimal: + if parse_float is not None: + raise TypeError("use_decimal=True implies parse_float=Decimal") + kw['parse_float'] = Decimal + return cls(encoding=encoding, **kw).decode(s) + + +def _toggle_speedups(enabled): + import decoder as dec + import encoder as enc + import scanner as scan + c_make_encoder = _import_c_make_encoder() + if enabled: + dec.scanstring = dec.c_scanstring or dec.py_scanstring + enc.c_make_encoder = c_make_encoder + enc.encode_basestring_ascii = (enc.c_encode_basestring_ascii or + enc.py_encode_basestring_ascii) + scan.make_scanner = scan.c_make_scanner or scan.py_make_scanner + else: + dec.scanstring = dec.py_scanstring + enc.c_make_encoder = None + enc.encode_basestring_ascii = enc.py_encode_basestring_ascii + scan.make_scanner = scan.py_make_scanner + dec.make_scanner = scan.make_scanner + global _default_decoder + _default_decoder = JSONDecoder( + encoding=None, + object_hook=None, + object_pairs_hook=None, + ) + global _default_encoder + _default_encoder = JSONEncoder( + skipkeys=False, + ensure_ascii=True, + check_circular=True, + allow_nan=True, + indent=None, + separators=None, + encoding='utf-8', + default=None, + ) ADDED gluon/contrib/simplejson/decoder.py Index: gluon/contrib/simplejson/decoder.py ================================================================== --- gluon/contrib/simplejson/decoder.py +++ gluon/contrib/simplejson/decoder.py @@ -0,0 +1,422 @@ +"""Implementation of JSONDecoder +""" +import re +import sys +import struct + +from scanner import make_scanner +def _import_c_scanstring(): + try: + raise ImportError # because assumes simplejson in path + from simplejson._speedups import scanstring + return scanstring + except ImportError: + return None +c_scanstring = _import_c_scanstring() + +__all__ = ['JSONDecoder'] + +FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL + +def _floatconstants(): + _BYTES = '7FF80000000000007FF0000000000000'.decode('hex') + # The struct module in Python 2.4 would get frexp() out of range here + # when an endian is specified in the format string. Fixed in Python 2.5+ + if sys.byteorder != 'big': + _BYTES = _BYTES[:8][::-1] + _BYTES[8:][::-1] + nan, inf = struct.unpack('dd', _BYTES) + return nan, inf, -inf + +NaN, PosInf, NegInf = _floatconstants() + + +class JSONDecodeError(ValueError): + """Subclass of ValueError with the following additional properties: + + msg: The unformatted error message + doc: The JSON document being parsed + pos: The start index of doc where parsing failed + end: The end index of doc where parsing failed (may be None) + lineno: The line corresponding to pos + colno: The column corresponding to pos + endlineno: The line corresponding to end (may be None) + endcolno: The column corresponding to end (may be None) + + """ + def __init__(self, msg, doc, pos, end=None): + ValueError.__init__(self, errmsg(msg, doc, pos, end=end)) + self.msg = msg + self.doc = doc + self.pos = pos + self.end = end + self.lineno, self.colno = linecol(doc, pos) + if end is not None: + self.endlineno, self.endcolno = linecol(doc, end) + else: + self.endlineno, self.endcolno = None, None + + +def linecol(doc, pos): + lineno = doc.count('\n', 0, pos) + 1 + if lineno == 1: + colno = pos + else: + colno = pos - doc.rindex('\n', 0, pos) + return lineno, colno + + +def errmsg(msg, doc, pos, end=None): + # Note that this function is called from _speedups + lineno, colno = linecol(doc, pos) + if end is None: + #fmt = '{0}: line {1} column {2} (char {3})' + #return fmt.format(msg, lineno, colno, pos) + fmt = '%s: line %d column %d (char %d)' + return fmt % (msg, lineno, colno, pos) + endlineno, endcolno = linecol(doc, end) + #fmt = '{0}: line {1} column {2} - line {3} column {4} (char {5} - {6})' + #return fmt.format(msg, lineno, colno, endlineno, endcolno, pos, end) + fmt = '%s: line %d column %d - line %d column %d (char %d - %d)' + return fmt % (msg, lineno, colno, endlineno, endcolno, pos, end) + + +_CONSTANTS = { + '-Infinity': NegInf, + 'Infinity': PosInf, + 'NaN': NaN, +} + +STRINGCHUNK = re.compile(r'(.*?)(["\\\x00-\x1f])', FLAGS) +BACKSLASH = { + '"': u'"', '\\': u'\\', '/': u'/', + 'b': u'\b', 'f': u'\f', 'n': u'\n', 'r': u'\r', 't': u'\t', +} + +DEFAULT_ENCODING = "utf-8" + +def py_scanstring(s, end, encoding=None, strict=True, + _b=BACKSLASH, _m=STRINGCHUNK.match): + """Scan the string s for a JSON string. End is the index of the + character in s after the quote that started the JSON string. + Unescapes all valid JSON string escape sequences and raises ValueError + on attempt to decode an invalid string. If strict is False then literal + control characters are allowed in the string. + + Returns a tuple of the decoded string and the index of the character in s + after the end quote.""" + if encoding is None: + encoding = DEFAULT_ENCODING + chunks = [] + _append = chunks.append + begin = end - 1 + while 1: + chunk = _m(s, end) + if chunk is None: + raise JSONDecodeError( + "Unterminated string starting at", s, begin) + end = chunk.end() + content, terminator = chunk.groups() + # Content is contains zero or more unescaped string characters + if content: + if not isinstance(content, unicode): + content = unicode(content, encoding) + _append(content) + # Terminator is the end of string, a literal control character, + # or a backslash denoting that an escape sequence follows + if terminator == '"': + break + elif terminator != '\\': + if strict: + msg = "Invalid control character %r at" % (terminator,) + #msg = "Invalid control character {0!r} at".format(terminator) + raise JSONDecodeError(msg, s, end) + else: + _append(terminator) + continue + try: + esc = s[end] + except IndexError: + raise JSONDecodeError( + "Unterminated string starting at", s, begin) + # If not a unicode escape sequence, must be in the lookup table + if esc != 'u': + try: + char = _b[esc] + except KeyError: + msg = "Invalid \\escape: " + repr(esc) + raise JSONDecodeError(msg, s, end) + end += 1 + else: + # Unicode escape sequence + esc = s[end + 1:end + 5] + next_end = end + 5 + if len(esc) != 4: + msg = "Invalid \\uXXXX escape" + raise JSONDecodeError(msg, s, end) + uni = int(esc, 16) + # Check for surrogate pair on UCS-4 systems + if 0xd800 <= uni <= 0xdbff and sys.maxunicode > 65535: + msg = "Invalid \\uXXXX\\uXXXX surrogate pair" + if not s[end + 5:end + 7] == '\\u': + raise JSONDecodeError(msg, s, end) + esc2 = s[end + 7:end + 11] + if len(esc2) != 4: + raise JSONDecodeError(msg, s, end) + uni2 = int(esc2, 16) + uni = 0x10000 + (((uni - 0xd800) << 10) | (uni2 - 0xdc00)) + next_end += 6 + char = unichr(uni) + end = next_end + # Append the unescaped character + _append(char) + return u''.join(chunks), end + + +# Use speedup if available +scanstring = c_scanstring or py_scanstring + +WHITESPACE = re.compile(r'[ \t\n\r]*', FLAGS) +WHITESPACE_STR = ' \t\n\r' + +def JSONObject((s, end), encoding, strict, scan_once, object_hook, + object_pairs_hook, memo=None, + _w=WHITESPACE.match, _ws=WHITESPACE_STR): + # Backwards compatibility + if memo is None: + memo = {} + memo_get = memo.setdefault + pairs = [] + # Use a slice to prevent IndexError from being raised, the following + # check will raise a more specific ValueError if the string is empty + nextchar = s[end:end + 1] + # Normally we expect nextchar == '"' + if nextchar != '"': + if nextchar in _ws: + end = _w(s, end).end() + nextchar = s[end:end + 1] + # Trivial empty object + if nextchar == '}': + if object_pairs_hook is not None: + result = object_pairs_hook(pairs) + return result, end + 1 + pairs = {} + if object_hook is not None: + pairs = object_hook(pairs) + return pairs, end + 1 + elif nextchar != '"': + raise JSONDecodeError("Expecting property name", s, end) + end += 1 + while True: + key, end = scanstring(s, end, encoding, strict) + key = memo_get(key, key) + + # To skip some function call overhead we optimize the fast paths where + # the JSON key separator is ": " or just ":". + if s[end:end + 1] != ':': + end = _w(s, end).end() + if s[end:end + 1] != ':': + raise JSONDecodeError("Expecting : delimiter", s, end) + + end += 1 + + try: + if s[end] in _ws: + end += 1 + if s[end] in _ws: + end = _w(s, end + 1).end() + except IndexError: + pass + + try: + value, end = scan_once(s, end) + except StopIteration: + raise JSONDecodeError("Expecting object", s, end) + pairs.append((key, value)) + + try: + nextchar = s[end] + if nextchar in _ws: + end = _w(s, end + 1).end() + nextchar = s[end] + except IndexError: + nextchar = '' + end += 1 + + if nextchar == '}': + break + elif nextchar != ',': + raise JSONDecodeError("Expecting , delimiter", s, end - 1) + + try: + nextchar = s[end] + if nextchar in _ws: + end += 1 + nextchar = s[end] + if nextchar in _ws: + end = _w(s, end + 1).end() + nextchar = s[end] + except IndexError: + nextchar = '' + + end += 1 + if nextchar != '"': + raise JSONDecodeError("Expecting property name", s, end - 1) + + if object_pairs_hook is not None: + result = object_pairs_hook(pairs) + return result, end + pairs = dict(pairs) + if object_hook is not None: + pairs = object_hook(pairs) + return pairs, end + +def JSONArray((s, end), scan_once, _w=WHITESPACE.match, _ws=WHITESPACE_STR): + values = [] + nextchar = s[end:end + 1] + if nextchar in _ws: + end = _w(s, end + 1).end() + nextchar = s[end:end + 1] + # Look-ahead for trivial empty array + if nextchar == ']': + return values, end + 1 + _append = values.append + while True: + try: + value, end = scan_once(s, end) + except StopIteration: + raise JSONDecodeError("Expecting object", s, end) + _append(value) + nextchar = s[end:end + 1] + if nextchar in _ws: + end = _w(s, end + 1).end() + nextchar = s[end:end + 1] + end += 1 + if nextchar == ']': + break + elif nextchar != ',': + raise JSONDecodeError("Expecting , delimiter", s, end) + + try: + if s[end] in _ws: + end += 1 + if s[end] in _ws: + end = _w(s, end + 1).end() + except IndexError: + pass + + return values, end + +class JSONDecoder(object): + """Simple JSON <http://json.org> decoder + + Performs the following translations in decoding by default: + + +---------------+-------------------+ + | JSON | Python | + +===============+===================+ + | object | dict | + +---------------+-------------------+ + | array | list | + +---------------+-------------------+ + | string | unicode | + +---------------+-------------------+ + | number (int) | int, long | + +---------------+-------------------+ + | number (real) | float | + +---------------+-------------------+ + | true | True | + +---------------+-------------------+ + | false | False | + +---------------+-------------------+ + | null | None | + +---------------+-------------------+ + + It also understands ``NaN``, ``Infinity``, and ``-Infinity`` as + their corresponding ``float`` values, which is outside the JSON spec. + + """ + + def __init__(self, encoding=None, object_hook=None, parse_float=None, + parse_int=None, parse_constant=None, strict=True, + object_pairs_hook=None): + """ + *encoding* determines the encoding used to interpret any + :class:`str` objects decoded by this instance (``'utf-8'`` by + default). It has no effect when decoding :class:`unicode` objects. + + Note that currently only encodings that are a superset of ASCII work, + strings of other encodings should be passed in as :class:`unicode`. + + *object_hook*, if specified, will be called with the result of every + JSON object decoded and its return value will be used in place of the + given :class:`dict`. This can be used to provide custom + deserializations (e.g. to support JSON-RPC class hinting). + + *object_pairs_hook* is an optional function that will be called with + the result of any object literal decode with an ordered list of pairs. + The return value of *object_pairs_hook* will be used instead of the + :class:`dict`. This feature can be used to implement custom decoders + that rely on the order that the key and value pairs are decoded (for + example, :func:`collections.OrderedDict` will remember the order of + insertion). If *object_hook* is also defined, the *object_pairs_hook* + takes priority. + + *parse_float*, if specified, will be called with the string of every + JSON float to be decoded. By default, this is equivalent to + ``float(num_str)``. This can be used to use another datatype or parser + for JSON floats (e.g. :class:`decimal.Decimal`). + + *parse_int*, if specified, will be called with the string of every + JSON int to be decoded. By default, this is equivalent to + ``int(num_str)``. This can be used to use another datatype or parser + for JSON integers (e.g. :class:`float`). + + *parse_constant*, if specified, will be called with one of the + following strings: ``'-Infinity'``, ``'Infinity'``, ``'NaN'``. This + can be used to raise an exception if invalid JSON numbers are + encountered. + + *strict* controls the parser's behavior when it encounters an + invalid control character in a string. The default setting of + ``True`` means that unescaped control characters are parse errors, if + ``False`` then control characters will be allowed in strings. + + """ + self.encoding = encoding + self.object_hook = object_hook + self.object_pairs_hook = object_pairs_hook + self.parse_float = parse_float or float + self.parse_int = parse_int or int + self.parse_constant = parse_constant or _CONSTANTS.__getitem__ + self.strict = strict + self.parse_object = JSONObject + self.parse_array = JSONArray + self.parse_string = scanstring + self.memo = {} + self.scan_once = make_scanner(self) + + def decode(self, s, _w=WHITESPACE.match): + """Return the Python representation of ``s`` (a ``str`` or ``unicode`` + instance containing a JSON document) + + """ + obj, end = self.raw_decode(s, idx=_w(s, 0).end()) + end = _w(s, end).end() + if end != len(s): + raise JSONDecodeError("Extra data", s, end, len(s)) + return obj + + def raw_decode(self, s, idx=0): + """Decode a JSON document from ``s`` (a ``str`` or ``unicode`` + beginning with a JSON document) and return a 2-tuple of the Python + representation and the index in ``s`` where the document ended. + + This can be used to decode a JSON document from a string that may + have extraneous data at the end. + + """ + try: + obj, end = self.scan_once(s, idx) + except StopIteration: + raise JSONDecodeError("No JSON object could be decoded", s, idx) + return obj, end ADDED gluon/contrib/simplejson/encoder.py Index: gluon/contrib/simplejson/encoder.py ================================================================== --- gluon/contrib/simplejson/encoder.py +++ gluon/contrib/simplejson/encoder.py @@ -0,0 +1,502 @@ +"""Implementation of JSONEncoder +""" +import re +from decimal import Decimal + +def _import_speedups(): + try: + raise ImportError # because assumes simplejson in path + from simplejson import _speedups + return _speedups.encode_basestring_ascii, _speedups.make_encoder + except ImportError: + return None, None +c_encode_basestring_ascii, c_make_encoder = _import_speedups() + +from decoder import PosInf + +ESCAPE = re.compile(r'[\x00-\x1f\\"\b\f\n\r\t]') +ESCAPE_ASCII = re.compile(r'([\\"]|[^\ -~])') +HAS_UTF8 = re.compile(r'[\x80-\xff]') +ESCAPE_DCT = { + '\\': '\\\\', + '"': '\\"', + '\b': '\\b', + '\f': '\\f', + '\n': '\\n', + '\r': '\\r', + '\t': '\\t', +} +for i in range(0x20): + #ESCAPE_DCT.setdefault(chr(i), '\\u{0:04x}'.format(i)) + ESCAPE_DCT.setdefault(chr(i), '\\u%04x' % (i,)) + +FLOAT_REPR = repr + +def encode_basestring(s): + """Return a JSON representation of a Python string + + """ + if isinstance(s, str) and HAS_UTF8.search(s) is not None: + s = s.decode('utf-8') + def replace(match): + return ESCAPE_DCT[match.group(0)] + return u'"' + ESCAPE.sub(replace, s) + u'"' + + +def py_encode_basestring_ascii(s): + """Return an ASCII-only JSON representation of a Python string + + """ + if isinstance(s, str) and HAS_UTF8.search(s) is not None: + s = s.decode('utf-8') + def replace(match): + s = match.group(0) + try: + return ESCAPE_DCT[s] + except KeyError: + n = ord(s) + if n < 0x10000: + #return '\\u{0:04x}'.format(n) + return '\\u%04x' % (n,) + else: + # surrogate pair + n -= 0x10000 + s1 = 0xd800 | ((n >> 10) & 0x3ff) + s2 = 0xdc00 | (n & 0x3ff) + #return '\\u{0:04x}\\u{1:04x}'.format(s1, s2) + return '\\u%04x\\u%04x' % (s1, s2) + return '"' + str(ESCAPE_ASCII.sub(replace, s)) + '"' + + +encode_basestring_ascii = ( + c_encode_basestring_ascii or py_encode_basestring_ascii) + +class JSONEncoder(object): + """Extensible JSON <http://json.org> encoder for Python data structures. + + Supports the following objects and types by default: + + +-------------------+---------------+ + | Python | JSON | + +===================+===============+ + | dict | object | + +-------------------+---------------+ + | list, tuple | array | + +-------------------+---------------+ + | str, unicode | string | + +-------------------+---------------+ + | int, long, float | number | + +-------------------+---------------+ + | True | true | + +-------------------+---------------+ + | False | false | + +-------------------+---------------+ + | None | null | + +-------------------+---------------+ + + To extend this to recognize other objects, subclass and implement a + ``.default()`` method with another method that returns a serializable + object for ``o`` if possible, otherwise it should call the superclass + implementation (to raise ``TypeError``). + + """ + item_separator = ', ' + key_separator = ': ' + def __init__(self, skipkeys=False, ensure_ascii=True, + check_circular=True, allow_nan=True, sort_keys=False, + indent=None, separators=None, encoding='utf-8', default=None, + use_decimal=False): + """Constructor for JSONEncoder, with sensible defaults. + + If skipkeys is false, then it is a TypeError to attempt + encoding of keys that are not str, int, long, float or None. If + skipkeys is True, such items are simply skipped. + + If ensure_ascii is true, the output is guaranteed to be str + objects with all incoming unicode characters escaped. If + ensure_ascii is false, the output will be unicode object. + + If check_circular is true, then lists, dicts, and custom encoded + objects will be checked for circular references during encoding to + prevent an infinite recursion (which would cause an OverflowError). + Otherwise, no such check takes place. + + If allow_nan is true, then NaN, Infinity, and -Infinity will be + encoded as such. This behavior is not JSON specification compliant, + but is consistent with most JavaScript based encoders and decoders. + Otherwise, it will be a ValueError to encode such floats. + + If sort_keys is true, then the output of dictionaries will be + sorted by key; this is useful for regression tests to ensure + that JSON serializations can be compared on a day-to-day basis. + + If indent is a string, then JSON array elements and object members + will be pretty-printed with a newline followed by that string repeated + for each level of nesting. ``None`` (the default) selects the most compact + representation without any newlines. For backwards compatibility with + versions of simplejson earlier than 2.1.0, an integer is also accepted + and is converted to a string with that many spaces. + + If specified, separators should be a (item_separator, key_separator) + tuple. The default is (', ', ': '). To get the most compact JSON + representation you should specify (',', ':') to eliminate whitespace. + + If specified, default is a function that gets called for objects + that can't otherwise be serialized. It should return a JSON encodable + version of the object or raise a ``TypeError``. + + If encoding is not None, then all input strings will be + transformed into unicode using that encoding prior to JSON-encoding. + The default is UTF-8. + + If use_decimal is true (not the default), ``decimal.Decimal`` will + be supported directly by the encoder. For the inverse, decode JSON + with ``parse_float=decimal.Decimal``. + + """ + + self.skipkeys = skipkeys + self.ensure_ascii = ensure_ascii + self.check_circular = check_circular + self.allow_nan = allow_nan + self.sort_keys = sort_keys + self.use_decimal = use_decimal + if isinstance(indent, (int, long)): + indent = ' ' * indent + self.indent = indent + if separators is not None: + self.item_separator, self.key_separator = separators + if default is not None: + self.default = default + self.encoding = encoding + + def default(self, o): + """Implement this method in a subclass such that it returns + a serializable object for ``o``, or calls the base implementation + (to raise a ``TypeError``). + + For example, to support arbitrary iterators, you could + implement default like this:: + + def default(self, o): + try: + iterable = iter(o) + except TypeError: + pass + else: + return list(iterable) + return JSONEncoder.default(self, o) + + """ + raise TypeError(repr(o) + " is not JSON serializable") + + def encode(self, o): + """Return a JSON string representation of a Python data structure. + + >>> from simplejson import JSONEncoder + >>> JSONEncoder().encode({"foo": ["bar", "baz"]}) + '{"foo": ["bar", "baz"]}' + + """ + # This is for extremely simple cases and benchmarks. + if isinstance(o, basestring): + if isinstance(o, str): + _encoding = self.encoding + if (_encoding is not None + and not (_encoding == 'utf-8')): + o = o.decode(_encoding) + if self.ensure_ascii: + return encode_basestring_ascii(o) + else: + return encode_basestring(o) + # This doesn't pass the iterator directly to ''.join() because the + # exceptions aren't as detailed. The list call should be roughly + # equivalent to the PySequence_Fast that ''.join() would do. + chunks = self.iterencode(o, _one_shot=True) + if not isinstance(chunks, (list, tuple)): + chunks = list(chunks) + if self.ensure_ascii: + return ''.join(chunks) + else: + return u''.join(chunks) + + def iterencode(self, o, _one_shot=False): + """Encode the given object and yield each string + representation as available. + + For example:: + + for chunk in JSONEncoder().iterencode(bigobject): + mysocket.write(chunk) + + """ + if self.check_circular: + markers = {} + else: + markers = None + if self.ensure_ascii: + _encoder = encode_basestring_ascii + else: + _encoder = encode_basestring + if self.encoding != 'utf-8': + def _encoder(o, _orig_encoder=_encoder, _encoding=self.encoding): + if isinstance(o, str): + o = o.decode(_encoding) + return _orig_encoder(o) + + def floatstr(o, allow_nan=self.allow_nan, + _repr=FLOAT_REPR, _inf=PosInf, _neginf=-PosInf): + # Check for specials. Note that this type of test is processor + # and/or platform-specific, so do tests which don't depend on + # the internals. + + if o != o: + text = 'NaN' + elif o == _inf: + text = 'Infinity' + elif o == _neginf: + text = '-Infinity' + else: + return _repr(o) + + if not allow_nan: + raise ValueError( + "Out of range float values are not JSON compliant: " + + repr(o)) + + return text + + + key_memo = {} + if (_one_shot and c_make_encoder is not None + and self.indent is None): + _iterencode = c_make_encoder( + markers, self.default, _encoder, self.indent, + self.key_separator, self.item_separator, self.sort_keys, + self.skipkeys, self.allow_nan, key_memo, self.use_decimal) + else: + _iterencode = _make_iterencode( + markers, self.default, _encoder, self.indent, floatstr, + self.key_separator, self.item_separator, self.sort_keys, + self.skipkeys, _one_shot, self.use_decimal) + try: + return _iterencode(o, 0) + finally: + key_memo.clear() + + +class JSONEncoderForHTML(JSONEncoder): + """An encoder that produces JSON safe to embed in HTML. + + To embed JSON content in, say, a script tag on a web page, the + characters &, < and > should be escaped. They cannot be escaped + with the usual entities (e.g. &) because they are not expanded + within <script> tags. + """ + + def encode(self, o): + # Override JSONEncoder.encode because it has hacks for + # performance that make things more complicated. + chunks = self.iterencode(o, True) + if self.ensure_ascii: + return ''.join(chunks) + else: + return u''.join(chunks) + + def iterencode(self, o, _one_shot=False): + chunks = super(JSONEncoderForHTML, self).iterencode(o, _one_shot) + for chunk in chunks: + chunk = chunk.replace('&', '\\u0026') + chunk = chunk.replace('<', '\\u003c') + chunk = chunk.replace('>', '\\u003e') + yield chunk + + +def _make_iterencode(markers, _default, _encoder, _indent, _floatstr, + _key_separator, _item_separator, _sort_keys, _skipkeys, _one_shot, + _use_decimal, + ## HACK: hand-optimized bytecode; turn globals into locals + False=False, + True=True, + ValueError=ValueError, + basestring=basestring, + Decimal=Decimal, + dict=dict, + float=float, + id=id, + int=int, + isinstance=isinstance, + list=list, + long=long, + str=str, + tuple=tuple, + ): + + def _iterencode_list(lst, _current_indent_level): + if not lst: + yield '[]' + return + if markers is not None: + markerid = id(lst) + if markerid in markers: + raise ValueError("Circular reference detected") + markers[markerid] = lst + buf = '[' + if _indent is not None: + _current_indent_level += 1 + newline_indent = '\n' + (_indent * _current_indent_level) + separator = _item_separator + newline_indent + buf += newline_indent + else: + newline_indent = None + separator = _item_separator + first = True + for value in lst: + if first: + first = False + else: + buf = separator + if isinstance(value, basestring): + yield buf + _encoder(value) + elif value is None: + yield buf + 'null' + elif value is True: + yield buf + 'true' + elif value is False: + yield buf + 'false' + elif isinstance(value, (int, long)): + yield buf + str(value) + elif isinstance(value, float): + yield buf + _floatstr(value) + elif _use_decimal and isinstance(value, Decimal): + yield buf + str(value) + else: + yield buf + if isinstance(value, (list, tuple)): + chunks = _iterencode_list(value, _current_indent_level) + elif isinstance(value, dict): + chunks = _iterencode_dict(value, _current_indent_level) + else: + chunks = _iterencode(value, _current_indent_level) + for chunk in chunks: + yield chunk + if newline_indent is not None: + _current_indent_level -= 1 + yield '\n' + (_indent * _current_indent_level) + yield ']' + if markers is not None: + del markers[markerid] + + def _iterencode_dict(dct, _current_indent_level): + if not dct: + yield '{}' + return + if markers is not None: + markerid = id(dct) + if markerid in markers: + raise ValueError("Circular reference detected") + markers[markerid] = dct + yield '{' + if _indent is not None: + _current_indent_level += 1 + newline_indent = '\n' + (_indent * _current_indent_level) + item_separator = _item_separator + newline_indent + yield newline_indent + else: + newline_indent = None + item_separator = _item_separator + first = True + if _sort_keys: + items = dct.items() + items.sort(key=lambda kv: kv[0]) + else: + items = dct.iteritems() + for key, value in items: + if isinstance(key, basestring): + pass + # JavaScript is weakly typed for these, so it makes sense to + # also allow them. Many encoders seem to do something like this. + elif isinstance(key, float): + key = _floatstr(key) + elif key is True: + key = 'true' + elif key is False: + key = 'false' + elif key is None: + key = 'null' + elif isinstance(key, (int, long)): + key = str(key) + elif _skipkeys: + continue + else: + raise TypeError("key " + repr(key) + " is not a string") + if first: + first = False + else: + yield item_separator + yield _encoder(key) + yield _key_separator + if isinstance(value, basestring): + yield _encoder(value) + elif value is None: + yield 'null' + elif value is True: + yield 'true' + elif value is False: + yield 'false' + elif isinstance(value, (int, long)): + yield str(value) + elif isinstance(value, float): + yield _floatstr(value) + elif _use_decimal and isinstance(value, Decimal): + yield str(value) + else: + if isinstance(value, (list, tuple)): + chunks = _iterencode_list(value, _current_indent_level) + elif isinstance(value, dict): + chunks = _iterencode_dict(value, _current_indent_level) + else: + chunks = _iterencode(value, _current_indent_level) + for chunk in chunks: + yield chunk + if newline_indent is not None: + _current_indent_level -= 1 + yield '\n' + (_indent * _current_indent_level) + yield '}' + if markers is not None: + del markers[markerid] + + def _iterencode(o, _current_indent_level): + if isinstance(o, basestring): + yield _encoder(o) + elif o is None: + yield 'null' + elif o is True: + yield 'true' + elif o is False: + yield 'false' + elif isinstance(o, (int, long)): + yield str(o) + elif isinstance(o, float): + yield _floatstr(o) + elif isinstance(o, (list, tuple)): + for chunk in _iterencode_list(o, _current_indent_level): + yield chunk + elif isinstance(o, dict): + for chunk in _iterencode_dict(o, _current_indent_level): + yield chunk + elif _use_decimal and isinstance(o, Decimal): + yield str(o) + else: + if markers is not None: + markerid = id(o) + if markerid in markers: + raise ValueError("Circular reference detected") + markers[markerid] = o + o = _default(o) + for chunk in _iterencode(o, _current_indent_level): + yield chunk + if markers is not None: + del markers[markerid] + + return _iterencode ADDED gluon/contrib/simplejson/ordered_dict.py Index: gluon/contrib/simplejson/ordered_dict.py ================================================================== --- gluon/contrib/simplejson/ordered_dict.py +++ gluon/contrib/simplejson/ordered_dict.py @@ -0,0 +1,119 @@ +"""Drop-in replacement for collections.OrderedDict by Raymond Hettinger + +http://code.activestate.com/recipes/576693/ + +""" +from UserDict import DictMixin + +# Modified from original to support Python 2.4, see +# http://code.google.com/p/simplejson/issues/detail?id=53 +try: + all +except NameError: + def all(seq): + for elem in seq: + if not elem: + return False + return True + +class OrderedDict(dict, DictMixin): + + def __init__(self, *args, **kwds): + if len(args) > 1: + raise TypeError('expected at most 1 arguments, got %d' % len(args)) + try: + self.__end + except AttributeError: + self.clear() + self.update(*args, **kwds) + + def clear(self): + self.__end = end = [] + end += [None, end, end] # sentinel node for doubly linked list + self.__map = {} # key --> [key, prev, next] + dict.clear(self) + + def __setitem__(self, key, value): + if key not in self: + end = self.__end + curr = end[1] + curr[2] = end[1] = self.__map[key] = [key, curr, end] + dict.__setitem__(self, key, value) + + def __delitem__(self, key): + dict.__delitem__(self, key) + key, prev, next = self.__map.pop(key) + prev[2] = next + next[1] = prev + + def __iter__(self): + end = self.__end + curr = end[2] + while curr is not end: + yield curr[0] + curr = curr[2] + + def __reversed__(self): + end = self.__end + curr = end[1] + while curr is not end: + yield curr[0] + curr = curr[1] + + def popitem(self, last=True): + if not self: + raise KeyError('dictionary is empty') + # Modified from original to support Python 2.4, see + # http://code.google.com/p/simplejson/issues/detail?id=53 + if last: + key = reversed(self).next() + else: + key = iter(self).next() + value = self.pop(key) + return key, value + + def __reduce__(self): + items = [[k, self[k]] for k in self] + tmp = self.__map, self.__end + del self.__map, self.__end + inst_dict = vars(self).copy() + self.__map, self.__end = tmp + if inst_dict: + return (self.__class__, (items,), inst_dict) + return self.__class__, (items,) + + def keys(self): + return list(self) + + setdefault = DictMixin.setdefault + update = DictMixin.update + pop = DictMixin.pop + values = DictMixin.values + items = DictMixin.items + iterkeys = DictMixin.iterkeys + itervalues = DictMixin.itervalues + iteritems = DictMixin.iteritems + + def __repr__(self): + if not self: + return '%s()' % (self.__class__.__name__,) + return '%s(%r)' % (self.__class__.__name__, self.items()) + + def copy(self): + return self.__class__(self) + + @classmethod + def fromkeys(cls, iterable, value=None): + d = cls() + for key in iterable: + d[key] = value + return d + + def __eq__(self, other): + if isinstance(other, OrderedDict): + return len(self)==len(other) and \ + all(p==q for p, q in zip(self.items(), other.items())) + return dict.__eq__(self, other) + + def __ne__(self, other): + return not self == other ADDED gluon/contrib/simplejson/scanner.py Index: gluon/contrib/simplejson/scanner.py ================================================================== --- gluon/contrib/simplejson/scanner.py +++ gluon/contrib/simplejson/scanner.py @@ -0,0 +1,78 @@ +"""JSON token scanner +""" +import re +def _import_c_make_scanner(): + try: + raise ImportError # because assumes simplejson in path + from simplejson._speedups import make_scanner + return make_scanner + except ImportError: + return None +c_make_scanner = _import_c_make_scanner() + +__all__ = ['make_scanner'] + +NUMBER_RE = re.compile( + r'(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?', + (re.VERBOSE | re.MULTILINE | re.DOTALL)) + +def py_make_scanner(context): + parse_object = context.parse_object + parse_array = context.parse_array + parse_string = context.parse_string + match_number = NUMBER_RE.match + encoding = context.encoding + strict = context.strict + parse_float = context.parse_float + parse_int = context.parse_int + parse_constant = context.parse_constant + object_hook = context.object_hook + object_pairs_hook = context.object_pairs_hook + memo = context.memo + + def _scan_once(string, idx): + try: + nextchar = string[idx] + except IndexError: + raise StopIteration + + if nextchar == '"': + return parse_string(string, idx + 1, encoding, strict) + elif nextchar == '{': + return parse_object((string, idx + 1), encoding, strict, + _scan_once, object_hook, object_pairs_hook, memo) + elif nextchar == '[': + return parse_array((string, idx + 1), _scan_once) + elif nextchar == 'n' and string[idx:idx + 4] == 'null': + return None, idx + 4 + elif nextchar == 't' and string[idx:idx + 4] == 'true': + return True, idx + 4 + elif nextchar == 'f' and string[idx:idx + 5] == 'false': + return False, idx + 5 + + m = match_number(string, idx) + if m is not None: + integer, frac, exp = m.groups() + if frac or exp: + res = parse_float(integer + (frac or '') + (exp or '')) + else: + res = parse_int(integer) + return res, m.end() + elif nextchar == 'N' and string[idx:idx + 3] == 'NaN': + return parse_constant('NaN'), idx + 3 + elif nextchar == 'I' and string[idx:idx + 8] == 'Infinity': + return parse_constant('Infinity'), idx + 8 + elif nextchar == '-' and string[idx:idx + 9] == '-Infinity': + return parse_constant('-Infinity'), idx + 9 + else: + raise StopIteration + + def scan_once(string, idx): + try: + return _scan_once(string, idx) + finally: + memo.clear() + + return scan_once + +make_scanner = c_make_scanner or py_make_scanner ADDED gluon/contrib/simplejson/tool.py Index: gluon/contrib/simplejson/tool.py ================================================================== --- gluon/contrib/simplejson/tool.py +++ gluon/contrib/simplejson/tool.py @@ -0,0 +1,42 @@ +r"""Command-line tool to validate and pretty-print JSON + +Usage:: + + $ echo '{"json":"obj"}' | python -m simplejson.tool + { + "json": "obj" + } + $ echo '{ 1.2:3.4}' | python -m simplejson.tool + Expecting property name: line 1 column 2 (char 2) + +""" +import sys +import simplejson as json + +def main(): + if len(sys.argv) == 1: + infile = sys.stdin + outfile = sys.stdout + elif len(sys.argv) == 2: + infile = open(sys.argv[1], 'rb') + outfile = sys.stdout + elif len(sys.argv) == 3: + infile = open(sys.argv[1], 'rb') + outfile = open(sys.argv[2], 'wb') + else: + raise SystemExit(sys.argv[0] + " [infile [outfile]]") + try: + try: + obj = json.load(infile, + object_pairs_hook=json.OrderedDict, + use_decimal=True) + except ValueError, e: + raise SystemExit(e) + json.dump(obj, outfile, sort_keys=True, indent=' ', use_decimal=True) + outfile.write('\n') + finally: + infile.close() + outfile.close() + +if __name__ == '__main__': + main() ADDED gluon/contrib/sms_utils.py Index: gluon/contrib/sms_utils.py ================================================================== --- gluon/contrib/sms_utils.py +++ gluon/contrib/sms_utils.py @@ -0,0 +1,113 @@ +SMSCODES = { + 'Aliant':'@chat.wirefree.ca', + 'Alltel':'@message.alltel.com', + 'Ameritech':'@paging.acswireless.com', + 'AT&T':'@txt.att.net', + 'AU by KDDI':'@ezweb.ne.jp', + 'BeeLine GSM':'@sms.beemail.ru', + 'Bell Mobility Canada':'@txt.bellmobility.ca', + 'Bellsouth':'@bellsouth.cl', + 'BellSouth Mobility':'@blsdcs.net', + 'Blue Sky Frog':'@blueskyfrog.com', + 'Boost':'@myboostmobile.com', + 'Cellular South':'@csouth1.com', + 'CellularOne':'@mobile.celloneusa.com', + 'CellularOne West':'@mycellone.com', + 'Cincinnati Bell':'@gocbw.com', + 'Claro':'@clarotorpedo.com.br', + 'Comviq':'@sms.comviq.se', + 'Dutchtone/Orange-NL':'@sms.orange.nl', + 'Edge Wireless':'@sms.edgewireless.com', + 'EinsteinPCS / Airadigm Communications':'@einsteinsms.com', + 'EPlus':'@smsmail.eplus.de', + 'Fido Canada':'@fido.ca', + 'Golden Telecom':'@sms.goldentele.com', + 'Idea Cellular':'@ideacellular.net', + 'Kyivstar':'@sms.kyivstar.net', + 'LMT':'@sms.lmt.lv', + 'Manitoba Telecom Systems':'@text.mtsmobility.com', + 'Meteor':'@sms.mymeteor.ie', + 'Metro PCS':'@mymetropcs.com', + 'Metrocall Pager':'@page.metrocall.com', + 'MobileOne':'@m1.com.sg', + 'Mobilfone':'@page.mobilfone.com', + 'Mobility Bermuda':'@ml.bm', + 'Netcom':'@sms.netcom.no', + 'Nextel':'@messaging.nextel.com', + 'NPI Wireless':'@npiwireless.com', + 'O2':'@o2.co.uk', + 'O2 M-mail':'@mmail.co.uk', + 'Optus':'@optusmobile.com.au', + 'Orange':'@orange.net', + 'Oskar':'@mujoskar.cz', + 'Pagenet':'@pagenet.net', + 'PCS Rogers':'@pcs.rogers.com', + 'Personal Communication':'@pcom.ru', + 'Plus GSM Poland':'@text.plusgsm.pl', + 'Powertel':'@ptel.net', + 'Primtel':'@sms.primtel.ru', + 'PSC Wireless':'@sms.pscel.com', + 'Qualcomm':'@pager.qualcomm.com', + 'Qwest':'@qwestmp.com', + 'Safaricom':'@safaricomsms.com', + 'Satelindo GSM':'@satelindogsm.com', + 'SCS-900':'@scs-900.ru', + 'Simple Freedom':'@text.simplefreedom.net', + 'Skytel - Alphanumeric':'@skytel.com', + 'Smart Telecom':'@mysmart.mymobile.ph', + 'Southern Linc':'@page.southernlinc.com', + 'Sprint PCS':'@messaging.sprintpcs.com', + 'Sprint PCS - Short Mail':'@sprintpcs.com', + 'SunCom':'@tms.suncom.com', + 'SureWest Communications':'@mobile.surewest.com', + 'SwissCom Mobile':'@bluewin.ch', + 'T-Mobile Germany':'@T-D1-SMS.de', + 'T-Mobile Netherlands':'@gin.nl', + 'T-Mobile UK':'@t-mobile.uk.net', + 'T-Mobile USA (tmail)':'@tmail.com', + 'T-Mobile USA (tmomail)':'@tmomail.net', + 'Tele2 Latvia':'@sms.tele2.lv', + 'Telefonica Movistar':'@movistar.net', + 'Telenor':'@mobilpost.no', + 'Telia Denmark':'@gsm1800.telia.dk', + 'Telus Mobility':'@msg.telus.com', + 'The Phone House':'@sms.phonehouse.de', + 'TIM':'@timnet.com', + 'UMC':'@sms.umc.com.ua', + 'Unicel':'@utext.com', + 'US Cellular':'@email.uscc.net', + 'Verizon Wireless (vtext)':'@vtext.com', + 'Verizon Wireless (airtouchpaging)':'@airtouchpaging.com', + 'Verizon Wireless (myairmail)':'@myairmail.com', + 'Vessotel':'@pager.irkutsk.ru', + 'Virgin Mobile Canada':'@vmobile.ca', + 'Virgin Mobile USA':'@vmobl.com', + 'Vodafone Italy':'@sms.vodafone.it', + 'Vodafone Japan (n)':'@n.vodafone.ne.jp', + 'Vodafone Japan (d)':'@d.vodafone.ne.jp', + 'Vodafone Japan (r)':'@r.vodafone.ne.jp', + 'Vodafone Japan (k)':'@k.vodafone.ne.jp', + 'Vodafone Japan (t)':'@t.vodafone.ne.jp', + 'Vodafone Japan (q)':'@q.vodafone.ne.jp', + 'Vodafone Japan (s)':'@s.vodafone.ne.jp', + 'Vodafone Japan (h)':'@h.vodafone.ne.jp', + 'Vodafone Japan (c)':'@c.vodafone.ne.jp', + 'Vodafone Spain':'@vodafone.es', + 'Vodafone UK':'@vodafone.net', + 'Weblink Wireless':'@airmessage.net', + 'WellCom':'@sms.welcome2well.com', + 'WyndTell':'@wyndtell.com', + } + +def sms_email(number,provider): + """ + >>> print sms_email('1 (312) 375-6536','T-Mobile USA (tmail)') + print 13123756536@tmail.com + """ + import re + if number[0]=='+1': number=number[1:] + elif number[0]=='+': number=number[3:] + elif number[:2]=='00': number=number[3:] + number=re.sub('[^\d]','',number) + return number+SMSCODES[provider] + ADDED gluon/contrib/spreadsheet.py Index: gluon/contrib/spreadsheet.py ================================================================== --- gluon/contrib/spreadsheet.py +++ gluon/contrib/spreadsheet.py @@ -0,0 +1,263 @@ +""" +Developed by Massimo Di Pierro, optional component of web2py, GPL2 license. +""" +import re +import pickle +import copy + + +def quote(text): + return str(text).replace('\\', '\\\\').replace("'", "\\'") + + +class Node: + """ + Example:: + + # controller + from gluon.contrib.spreadsheet import Sheet + + def callback(): + return cache.ram('sheet1', lambda: None, None).process(request) + + def index(): + sheet = cache.ram('sheet1', + lambda: Sheet(10, 10, URL(r=request, f='callback')), 0) + #sheet.cell('r0c3', value='=r0c0+r0c1+r0c2', readonly=True) + return dict(sheet=sheet) + + # view + {{extend 'layout.html'}} + {{=sheet}} + + or insert invidivual cells via + + {{=sheet.nodes['r0c0']}} + + """ + + def __init__(self, name, value, url='.', readonly=False, active=True, + onchange=None): + self.url = url + self.name = name + self.value = str(value) + self.computed_value = '' + self.incoming = {} + self.outcoming = {} + self.readonly = readonly + self.active = active + self.onchange = onchange + self.size = 4 + self.locked = False + + def xml(self): + return """<input name="%s" id="%s" value="%s" size="%s" + onkeyup="ajax('%s/keyup',['%s'], ':eval');" + onfocus="ajax('%s/focus',['%s'], ':eval');" + onblur="ajax('%s/blur',['%s'], ':eval');" %s/> + """ % (self.name, self.name, self.computed_value, self.size, + self.url, self.name, self.url, self.name, self.url, self.name, + (self.readonly and 'readonly ') or '') + + def __repr__(self): + return '%s:%s' % (self.name, self.computed_value) + + +class Sheet: + + regex=re.compile('(?<!\w)[a-zA-Z_]\w*') + + re_strings = re.compile(r'(?P<name>' + + r"[uU]?[rR]?'''([^']+|'{1,2}(?!'))*'''|" + + r"'([^'\\]|\\.)*'|" + + r'"""([^"]|"{1,2}(?!"))*"""|' + + r'"([^"\\]|\\.)*")', re.DOTALL) + + def dumps(self): + dump = pickle.dumps(self) + return dump + + @staticmethod + def loads(data): + sheet = pickle.loads(data) + return sheet + + def process(self, request): + """ + call this in action that creates table, it will handle ajax callbacks + """ + cell = request.vars.keys()[0] + if request.args(0) == 'focus': + return "jQuery('#%s').val('%s');" % (cell, quote(self[cell].value)) + value = request.vars[cell] + self[cell] = value + if request.args(0) == 'blur': + return "jQuery('#%s').val('%s');" \ + % (cell, quote(self[cell].computed_value)) + elif request.args(0) == 'keyup': + jquery = '' + for other_key in self.modified: + if other_key != cell: + jquery += "jQuery('#%s').val('%s');" % \ + (other_key, quote(self[other_key].computed_value)) + return jquery + + def __init__(self, rows, cols, url='.', readonly=False, active=True, + onchange=None): + self.rows = rows + self.cols = cols + self.url = url + self.nodes = {} + self.error = 'ERROR: %(error)s' + self.allowed_keywords = ['for', 'in', 'if', 'else', 'and', 'or', 'not', + 'i', 'j', 'k', 'x', 'y', 'z', 'sum'] + self.environment = {} + [self.cell('r%sc%s'%(k/cols, k%cols), '0.0', readonly, active, onchange) + for k in xrange(rows*cols)] + exec('from math import *', {}, self.environment) + + def delete_from(self, other_list): + indices = [k for (k, node) in enumerate(other_list) if k == node] + if indices: + del other_list[indices[0]] + + def changed(self, node, changed_nodes=[]): + for other_node in node.outcoming: + if not other_node in changed_nodes: + changed_nodes.append(other_node) + self.changed(other_node, changed_nodes) + return changed_nodes + + def define(self, name, obj): + self.environment[name] = obj + + def cell(self, key, value, readonly=False, active=True, onchange=None): + """ + key is the name of the cell + value is the initial value of the cell. It can be a formula "=1+3" + a cell is active if it evaluates formuls + """ + key = str(key) + if not self.regex.match(key): + raise SyntaxError, "Invalid cell name: %s" % key + node = Node(key, value, self.url, readonly, active, onchange) + self.nodes[key] = node + self[key] = value + + def __setitem__(self, key, value): + key = str(key) + value = str(value) + node = self.nodes[key] + node.value = value + if value[:1] == '=' and node.active: + # clear all edges involving current node + for other_node in node.incoming: + del other_node.outcoming[node] + node.incoming.clear() + # build new edges + command = self.re_strings.sub("''", value[1:]) + node.locked = False + for match in self.regex.finditer(command): + other_key = match.group() + if other_key == key: + self.computed_value = self.error % dict(error='cycle') + self.modified={} + break + if other_key in self.nodes: + other_node = self.nodes[other_key] + other_node.outcoming[node] = True + node.incoming[other_node] = True + elif not other_key in self.allowed_keywords and \ + not other_key in self.environment: + node.locked = True + node.computed_value = \ + self.error % dict(error='invalid keyword: ' + other_key) + self.modified = {} + break + self.compute(node) + else: + try: + node.computed_value = int(node.value) + except: + try: + node.computed_value = float(node.value) + except: + node.computed_value = node.value + self.environment[key] = node.computed_value + if node.onchange: + node.onchange(node) + self.modified = self.iterate(node) + + def compute(self, node): + if node.value[:1] == '=' and not node.locked: + try: + exec('__value__=' + node.value[1:], {}, self.environment) + node.computed_value = self.environment['__value__'] + del self.environment['__value__'] + except Exception, e: + node.computed_value = self.error % dict(error=str(e)) + self.environment[node.name] = node.computed_value + if node.onchange: + node.onchange(node) + + def iterate(self, node): + output = {node.name: node.computed_value} + changed_nodes = self.changed(node) + while changed_nodes: + ok=False + set_changed_nodes = set(changed_nodes) + for (k, other_node) in enumerate(changed_nodes): + #print other_node, changed_nodes + if not set(other_node.incoming.keys()).\ + intersection(set_changed_nodes): + #print 'ok' + self.compute(other_node) + output[other_node.name] = other_node.computed_value + #print other_node + del changed_nodes[k] + ok = True + break + if not ok: + return {} + return output + + def __getitem__(self, key): + return self.nodes[str(key)] + + def get_computed_values(self): + d={} + for key in self.nodes: + node = self.nodes[key] + if node.value[:1] != '=' or not node.active: + d[key] = node.computed_value + return d + + def set_computed_values(self, d): + for key in d: + if not key in self.nodes: + continue + node = self.nodes[key] + if node.value[:1] != '=' or not node.active: + node.value = d[key] + + def xml(self): + import gluon.html + (DIV, TABLE, TR, TD, TH, BR) = \ + (gluon.html.DIV, gluon.html.TABLE, gluon.html.TR, gluon.html.TD, + gluon.html.TH, gluon.html.BR) + regex = re.compile('r\d+c\d+') + return DIV(TABLE(TR(TH(), *[TH('c%s' % c) for c in range(self.cols)]), + *[TR(TH('r%s' % r), *[TD(self.nodes['r%sc%s'%(r, c)]) \ + for c in range(self.cols)]) \ + for r in range(self.rows)]), + BR(), + TABLE(*[TR(TH(key), TD(self.nodes[key])) \ + for key in self.nodes if not regex.match(key)])).xml() + +if __name__ == '__main__': + s = Sheet(0, 0) + s.cell('a', value="2") + s.cell('b', value="=sin(a)") + s.cell('c', value="=cos(a)**2+b*b") + print s['c'].computed_value + ADDED gluon/contrib/taskbar_widget.py Index: gluon/contrib/taskbar_widget.py ================================================================== --- gluon/contrib/taskbar_widget.py +++ gluon/contrib/taskbar_widget.py @@ -0,0 +1,244 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# # Creates a taskbar icon for web2py +# # Author: Mark Larsen, mostly stolen from Mark Hammond's +# # C:\Python25\Lib\site-packages\win32\Demos\win32gui_taskbar.py +# # 11/7/08 +# dual licensed under the web2py license (LGPL) and the Python license. + +import os +import sys +import base64 +import win32con +import win32api +import win32gui + + +class TaskBarIcon: + + def __init__(self, iconPath=None): + + self.iconPath = iconPath + self.status = [] + + msg_TaskbarRestart = \ + win32api.RegisterWindowMessage('TaskbarCreated') + message_map = { + msg_TaskbarRestart: self.OnRestart, + win32con.WM_DESTROY: self.OnDestroy, + win32con.WM_COMMAND: self.OnCommand, + win32con.WM_USER + 20: self.OnTaskbarNotify, + } + + # Register the Window class. + + wc = win32gui.WNDCLASS() + hinst = wc.hInstance = win32api.GetModuleHandle(None) + wc.lpszClassName = 'web2pyTaskbar' + wc.style = win32con.CS_VREDRAW | win32con.CS_HREDRAW + wc.hCursor = win32gui.LoadCursor(0, win32con.IDC_ARROW) + wc.hbrBackground = win32con.COLOR_WINDOW + wc.lpfnWndProc = message_map # could also specify a wndproc. + classAtom = win32gui.RegisterClass(wc) + + # Create the Window. + + style = win32con.WS_OVERLAPPED | win32con.WS_SYSMENU + self.hwnd = win32gui.CreateWindow( + classAtom, + 'web2pyTaskbar', + style, + 0, + 0, + win32con.CW_USEDEFAULT, + win32con.CW_USEDEFAULT, + 0, + 0, + hinst, + None, + ) + win32gui.UpdateWindow(self.hwnd) + self.SetServerStopped() + + def __createIcon(self): + + # try and use custom icon + + if self.iconPath and os.path.isfile(self.iconPath): + hicon = self.__loadFromFile(self.iconPath) + else: + try: + fp = 'tmp.ico' + icFH = file(fp, 'wb') + if self.serverState == self.EnumServerState.STOPPED: + icFH.write(base64.b64decode(self.__getIconStopped())) + elif self.serverState == self.EnumServerState.RUNNING: + icFH.write(base64.b64decode(self.__getIconRunning())) + icFH.close() + hicon = self.__loadFromFile(fp) + os.unlink(fp) + except: + print "Can't load web2py icons - using default" + hicon = win32gui.LoadIcon(0, win32con.IDI_APPLICATION) + + flags = win32gui.NIF_ICON | win32gui.NIF_MESSAGE\ + | win32gui.NIF_TIP + nid = ( + self.hwnd, + 0, + flags, + win32con.WM_USER + 20, + hicon, + 'web2py Framework', + ) + try: + win32gui.Shell_NotifyIcon(win32gui.NIM_MODIFY, nid) + except: + try: + win32gui.Shell_NotifyIcon(win32gui.NIM_ADD, nid) + except win32api.error: + + # This is common when windows is starting, and this code is hit + # before the taskbar has been created. + + print 'Failed to add the taskbar icon - is explorer running?' + + # but keep running anyway - when explorer starts, we get the + + def OnRestart( + self, + hwnd, + msg, + wparam, + lparam, + ): + self._DoCreateIcons() + + def OnDestroy( + self, + hwnd, + msg, + wparam, + lparam, + ): + nid = (self.hwnd, 0) + win32gui.Shell_NotifyIcon(win32gui.NIM_DELETE, nid) + + def OnTaskbarNotify( + self, + hwnd, + msg, + wparam, + lparam, + ): + if lparam == win32con.WM_LBUTTONUP: + pass + elif lparam == win32con.WM_LBUTTONDBLCLK: + pass + elif lparam == win32con.WM_RBUTTONUP: + menu = win32gui.CreatePopupMenu() + win32gui.AppendMenu(menu, win32con.MF_STRING, 1023, + 'Toggle Display') + win32gui.AppendMenu(menu, win32con.MF_SEPARATOR, 0, '') + if self.serverState == self.EnumServerState.STOPPED: + win32gui.AppendMenu(menu, win32con.MF_STRING, 1024, + 'Start Server') + win32gui.AppendMenu(menu, win32con.MF_STRING + | win32con.MF_GRAYED, 1025, + 'Restart Server') + win32gui.AppendMenu(menu, win32con.MF_STRING + | win32con.MF_GRAYED, 1026, + 'Stop Server') + else: + win32gui.AppendMenu(menu, win32con.MF_STRING + | win32con.MF_GRAYED, 1024, + 'Start Server') + win32gui.AppendMenu(menu, win32con.MF_STRING, 1025, + 'Restart Server') + win32gui.AppendMenu(menu, win32con.MF_STRING, 1026, + 'Stop Server') + win32gui.AppendMenu(menu, win32con.MF_SEPARATOR, 0, '') + win32gui.AppendMenu(menu, win32con.MF_STRING, 1027, + 'Quit (pid:%i)' % os.getpid()) + pos = win32gui.GetCursorPos() + + # See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/menus_0hdi.asp + + win32gui.SetForegroundWindow(self.hwnd) + win32gui.TrackPopupMenu( + menu, + win32con.TPM_LEFTALIGN, + pos[0], + pos[1], + 0, + self.hwnd, + None, + ) + win32api.PostMessage(self.hwnd, win32con.WM_NULL, 0, 0) + return 1 + + def OnCommand( + self, + hwnd, + msg, + wparam, + lparam, + ): + id = win32api.LOWORD(wparam) + if id == 1023: + self.status.append(self.EnumStatus.TOGGLE) + elif id == 1024: + self.status.append(self.EnumStatus.START) + elif id == 1025: + self.status.append(self.EnumStatus.RESTART) + elif id == 1026: + self.status.append(self.EnumStatus.STOP) + elif id == 1027: + self.status.append(self.EnumStatus.QUIT) + self.Destroy() + else: + print 'Unknown command -', id + + def Destroy(self): + win32gui.DestroyWindow(self.hwnd) + + def SetServerRunning(self): + self.serverState = self.EnumServerState.RUNNING + self.__createIcon() + + def SetServerStopped(self): + self.serverState = self.EnumServerState.STOPPED + self.__createIcon() + + def __getIconRunning(self): + return 'AAABAAEAEBAQAAAAAAAoAQAAFgAAACgAAAAQAAAAIAAAAAEABAAAAAAAgAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAIXMGAABe/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABERAgAAIAAAEAACAAAgAAABEAIiACIgAAABAgAgIAIAEAECACAgAgABEAIiACACAAAAAAAAAAAAICACIiAiIAICAgIAACACAgICAgAAIAICAgICIiAiIAICAgIAACACAgICAgAAIAICAgICIiAiIAAAAAAAAAAAD//wAAhe8AAL3vAADMYwAA9a0AALWtAADMbQAA//8AAKwjAABV7QAAVe0AAFQjAABV7QAAVe0AAFQjAAD//wAA' + + def __getIconStopped(self): + return 'AAABAAEAEBAQAAEABAAoAQAAFgAAACgAAAAQAAAAIAAAAAEABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJCdIAIXMGAABe/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzMzMzMzMzAwERMjMzIzAzEDMyMzMjAzMxAzIiMyAjMzMwMjMjAzIzEzECMyAjMjMxEzAiAyMyMzMzMwAzMzMzIyMyACMiIzIyMjAzAyMyMjIyAjMwIzIyMjAyIiMCIzIyAjIzMyAyMjAyMjMzIwIyAjIyIiMiIDAzMzMzMzMzB//gAAhe0AAJ3rAADMYwAA9a0AALGNAADMLQAA/n8AAKwjAABVrQAAUc0AAFQjAABF5QAAVekAABQhAAB//gAA' + + def __loadFromFile(self, iconPath): + hinst = win32api.GetModuleHandle(None) + icon_flags = win32con.LR_LOADFROMFILE | win32con.LR_DEFAULTSIZE + hicon = win32gui.LoadImage( + hinst, + iconPath, + win32con.IMAGE_ICON, + 0, + 0, + icon_flags, + ) + return hicon + + class EnumStatus: + + TOGGLE = 0 + START = 1 + STOP = 2 + RESTART = 3 + QUIT = 4 + + class EnumServerState: + + RUNNING = 0 + STOPPED = 1 + ADDED gluon/contrib/user_agent_parser.py Index: gluon/contrib/user_agent_parser.py ================================================================== --- gluon/contrib/user_agent_parser.py +++ gluon/contrib/user_agent_parser.py @@ -0,0 +1,394 @@ +""" +Extract client information from http user agent +The module does not try to detect all capabilities of browser in current form (it can easily be extended though). +Aim is + * fast + * very easy to extend + * reliable enough for practical purposes + * and assist python web apps to detect clients. + +Taken from http://pypi.python.org/pypi/httpagentparser (MIT license) +Modified my Ross Peoples for web2py to better support iPhone and iPad. +""" +import sys +from storage import Storage + +class DetectorsHub(dict): + _known_types = ['os', 'dist', 'flavor', 'browser'] + + def __init__(self, *args, **kw): + dict.__init__(self, *args, **kw) + for typ in self._known_types: + self.setdefault(typ, []) + self.registerDetectors() + + def register(self, detector): + if detector.info_type not in self._known_types: + self[detector.info_type] = [detector] + self._known_types.insert(detector.order, detector.info_type) + else: + self[detector.info_type].append(detector) + + def reorderByPrefs(self, detectors, prefs): + if prefs is None: + return [] + elif prefs == []: + return detectors + else: + prefs.insert(0, '') + def key_name(d): + return d.name in prefs and prefs.index(d.name) or sys.maxint + return sorted(detectors, key=key_name) + + def __iter__(self): + return iter(self._known_types) + + def registerDetectors(self): + detectors = [v() for v in globals().values() \ + if DetectorBase in getattr(v, '__mro__', [])] + for d in detectors: + if d.can_register: + self.register(d) + + +class DetectorBase(object): + name = "" # "to perform match in DetectorsHub object" + info_type = "override me" + result_key = "override me" + order = 10 # 0 is highest + look_for = "string to look for" + skip_if_found = [] # strings if present stop processin + can_register = False + prefs = Storage() # dict(info_type = [name1, name2], ..) + version_splitters = ["/", " "] + _suggested_detectors = None + + def __init__(self): + if not self.name: + self.name = self.__class__.__name__ + self.can_register = (self.__class__.__dict__.get('can_register', True)) + + def detect(self, agent, result): + if agent and self.checkWords(agent): + result[self.info_type] = Storage(name=self.name) + version = self.getVersion(agent) + if version: + result[self.info_type].version = version + return True + return False + + def checkWords(self, agent): + for w in self.skip_if_found: + if w in agent: + return False + if self.look_for: + return True + return False + + def getVersion(self, agent): + # -> version string /None + vs = self.version_splitters + return agent.split(self.look_for + vs[0])[-1].split(vs[1])[0].strip() + + +class OS(DetectorBase): + info_type = "os" + can_register = False + version_splitters = [";", " "] + + +class Dist(DetectorBase): + info_type = "dist" + can_register = False + + +class Flavor(DetectorBase): + info_type = "flavor" + can_register = False + + +class Browser(DetectorBase): + info_type = "browser" + can_register = False + + +class Macintosh(OS): + look_for = 'Macintosh' + prefs = Storage(dist=None) + def getVersion(self, agent): + pass + + +class Firefox(Browser): + look_for = "Firefox" + + +class Konqueror(Browser): + look_for = "Konqueror" + version_splitters = ["/", ";"] + + +class Opera(Browser): + look_for = "Opera" + def getVersion(self, agent): + return agent.split(self.look_for)[1][1:].split(' ')[0] + +class Netscape(Browser): + look_for = "Netscape" + +class MSIE(Browser): + look_for = "MSIE" + skip_if_found = ["Opera"] + name = "Microsoft Internet Explorer" + version_splitters = [" ", ";"] + + +class Galeon(Browser): + look_for = "Galeon" + + +class Safari(Browser): + look_for = "Safari" + + def checkWords(self, agent): + unless_list = ["Chrome", "OmniWeb"] + if self.look_for in agent: + for word in unless_list: + if word in agent: + return False + return True + + def getVersion(self, agent): + if "Version/" in agent: + return agent.split('Version/')[-1].split(' ')[0].strip() + else: + # Mobile Safari + return agent.split('Safari ')[-1].split(' ')[0].strip() + + +class Linux(OS): + look_for = 'Linux' + prefs = Storage(browser=["Firefox"], + dist=["Ubuntu", "Android"], flavor=None) + + def getVersion(self, agent): + pass + + +class Macintosh(OS): + look_for = 'Macintosh' + prefs = Storage(dist=None, flavor=['MacOS']) + def getVersion(self, agent): + pass + + +class MacOS(Flavor): + look_for = 'Mac OS' + prefs = Storage(browser=['Firefox', 'Opera', "Microsoft Internet Explorer"]) + + def getVersion(self, agent): + version_end_chars = [';', ')'] + part = agent.split('Mac OS')[-1].strip() + for c in version_end_chars: + if c in part: + version = part.split(c)[0] + break + return version.replace('_', '.') + + +class Windows(OS): + look_for = 'Windows' + prefs = Storage(browser=["Microsoft Internet Explorer", 'Firefox'], + dict=None, flavor=None) + + def getVersion(self, agent): + v = agent.split('Windows')[-1].split(';')[0].strip() + if ')' in v: + v = v.split(')')[0] + return v + + +class Ubuntu(Dist): + look_for = 'Ubuntu' + version_splitters = ["/", " "] + prefs = Storage(browser=['Firefox']) + + +class Debian(Dist): + look_for = 'Debian' + version_splitters = ["/", " "] + prefs = Storage(browser=['Firefox']) + + +class Chrome(Browser): + look_for = "Chrome" + version_splitters = ["/", " "] + +class ChromeOS(OS): + look_for = "CrOS" + version_splitters = [" ", " "] + prefs = Storage(browser=['Chrome']) + def getVersion(self, agent): + vs = self.version_splitters + return agent.split(self.look_for+vs[0])[-1].split(vs[1])[1].strip()[:-1] + +class Android(Dist): + look_for = 'Android' + + def getVersion(self, agent): + return agent.split('Android')[-1].split(';')[0].strip() + + +class iPhone(Dist): + look_for = 'iPhone' + + def getVersion(self, agent): + version_end_chars = ['like', ';', ')'] + part = agent.split('CPU OS')[-1].strip() + for c in version_end_chars: + if c in part: + version = 'iOS ' + part.split(c)[0].strip() + break + return version.replace('_', '.') + +class iPad(Dist): + look_for = 'iPad' + + def getVersion(self, agent): + version_end_chars = ['like', ';', ')'] + part = agent.split('CPU OS')[-1].strip() + for c in version_end_chars: + if c in part: + version = 'iOS ' + part.split(c)[0].strip() + break + return version.replace('_', '.') + +detectorshub = DetectorsHub() + +def detect(agent): + result = Storage() + prefs = Storage() + _suggested_detectors = [] + for info_type in detectorshub: + if not _suggested_detectors: + detectors = detectorshub[info_type] + _d_prefs = prefs.get(info_type, []) + detectors = detectorshub.reorderByPrefs(detectors, _d_prefs) + if "detector" in locals(): + detector._suggested_detectors = detectors + else: + detectors = _suggested_detectors + for detector in detectors: + print "detector name: ", detector.name + if detector.detect(agent, result): + prefs = detector.prefs + _suggested_detectors = detector._suggested_detectors + break + return result + + +class Result(Storage): + def __missing__(self, k): + return "" + + +def detect(agent): + result = Result() + _suggested_detectors = [] + for info_type in detectorshub: + detectors = _suggested_detectors or detectorshub[info_type] + for detector in detectors: + if detector.detect(agent, result): + if detector.prefs and not detector._suggested_detectors: + _suggested_detectors = detectorshub.reorderByPrefs( + detectors, detector.prefs.get(info_type)) + detector._suggested_detectors = _suggested_detectors + break + return result + + +def simple_detect(agent): + """ + -> (os, browser) # tuple of strings + """ + result = detect(agent) + os_list = [] + if 'flavor' in result: os_list.append(result['flavor']['name']) + if 'dist' in result: os_list.append(result['dist']['name']) + if 'os' in result: os_list.append(result['os']['name']) + + os = os_list and " ".join(os_list) or "Unknown OS" + os_version = os_list and (result['flavor'] and result['flavor'].get( + 'version')) or (result['dist'] and result['dist'].get('version')) \ + or (result['os'] and result['os'].get('version')) or "" + browser = 'browser' in result and result['browser']['name'] \ + or 'Unknown Browser' + browser_version = 'browser' in result \ + and result['browser'].get('version') or "" + if browser_version: + browser = " ".join((browser, browser_version)) + if os_version: + os = " ".join((os, os_version)) + return os, browser + + +if __name__ == '__main__': + import time + import unittest + + data = ( + ("Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-GB; rv:1.9.0.10) Gecko/2009042315 Firefox/3.0.10", + ('MacOS Macintosh X 10.5', 'Firefox 3.0.10'), + {'flavor': {'version': 'X 10.5', 'name': 'MacOS'}, 'os': {'name': 'Macintosh'}, 'browser': {'version': '3.0.10', 'name': 'Firefox'}},), + ("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_6) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.3 Safari/534.24,gzip(gfe)", + ('MacOS Macintosh X 10.6.6', 'Chrome 11.0.696.3'), + {'flavor': {'version': 'X 10.6.6', 'name': 'MacOS'}, 'os': {'name': 'Macintosh'}, 'browser': {'version': '11.0.696.3', 'name': 'Chrome'}},), + ("Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2) Gecko/20100308 Ubuntu/10.04 (lucid) Firefox/3.6 GTB7.1", + ('Ubuntu Linux 10.04', 'Firefox 3.6'), + {'dist': {'version': '10.04', 'name': 'Ubuntu'}, 'os': {'name': 'Linux'}, 'browser': {'version': '3.6', 'name': 'Firefox'}},), + ("Mozilla/5.0 (Linux; U; Android 2.2.1; fr-ch; A43 Build/FROYO) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1", + ('Android Linux 2.2.1', 'Safari 4.0'), + {'dist': {'version': '2.2.1', 'name': 'Android'}, 'os': {'name': 'Linux'}, 'browser': {'version': '4.0', 'name': 'Safari'}},), + ("Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleWebKit/420+ (KHTML, like Gecko) Version/3.0 Mobile/1A543a Safari/419.3", + ('MacOS IPhone X', 'Safari 3.0'), + {'flavor': {'version': 'X', 'name': 'MacOS'}, 'dist': {'version': 'X', 'name': 'IPhone'}, 'browser': {'version': '3.0', 'name': 'Safari'}},), + ("Mozilla/5.0 (X11; CrOS i686 0.0.0) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.27 Safari/534.24,gzip(gfe)", + ('ChromeOS 0.0.0', 'Chrome 11.0.696.27'), + {'os': {'name': 'ChromeOS', 'version': '0.0.0'}, 'browser': {'name': 'Chrome', 'version': '11.0.696.27'}},), + ("Mozilla/4.0 (compatible; MSIE 6.0; MSIE 5.5; Windows NT 5.1) Opera 7.02 [en]", + ('Windows NT 5.1', 'Opera 7.02'), + {'os': {'name': 'Windows', 'version': 'NT 5.1'}, 'browser': {'name': 'Opera', 'version': '7.02'}},), + ("Opera/9.80 (X11; Linux i686; U; en) Presto/2.9.168 Version/11.50", + ("Linux", "Opera 9.80"), + {"os": {"name": "Linux"}, "browser": {"name": "Opera", "version": "9.80"}},), + ("Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.5) Gecko/20060127 Netscape/8.1", + ("Windows NT 5.1", "Netscape 8.1"), + {'os': {'name': 'Windows', 'version': 'NT 5.1'}, 'browser': {'name': 'Netscape', 'version': '8.1'}},), + ) + + class TestHAP(unittest.TestCase): + def setUp(self): + self.harass_repeat = 1000 + self.data = data + + def test_simple_detect(self): + for agent, simple_res, res in data: + self.assertEqual(simple_detect(agent), simple_res) + + def test_detect(self): + for agent, simple_res, res in data: + self.assertEqual(detect(agent), res) + + def test_harass(self): + then = time.time() + for agent, simple_res, res in data * self.harass_repeat: + detect(agent) + time_taken = time.time() - then + no_of_tests = len(self.data) * self.harass_repeat + print "\nTime taken for %s detecttions: %s" \ + % (no_of_tests, time_taken) + print "Time taken for single detecttion: ", \ + time_taken / (len(self.data) * self.harass_repeat) + + unittest.main() + ADDED gluon/custom_import.py Index: gluon/custom_import.py ================================================================== --- gluon/custom_import.py +++ gluon/custom_import.py @@ -0,0 +1,311 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import __builtin__ +import os +import re +import sys +import threading + +# Install the new import function: +def custom_import_install(web2py_path): + global _web2py_importer + global _web2py_path + if _web2py_importer: + return # Already installed + _web2py_path = web2py_path + _web2py_importer = _Web2pyImporter(web2py_path) + __builtin__.__import__ = _web2py_importer + +def is_tracking_changes(): + """ + @return: True: neo_importer is tracking changes made to Python source + files. False: neo_import does not reload Python modules. + """ + + global _is_tracking_changes + return _is_tracking_changes + +def track_changes(track=True): + """ + Tell neo_importer to start/stop tracking changes made to Python modules. + @param track: True: Start tracking changes. False: Stop tracking changes. + """ + + global _is_tracking_changes + global _web2py_importer + global _web2py_date_tracker_importer + assert track is True or track is False, "Boolean expected." + if track == _is_tracking_changes: + return + if track: + if not _web2py_date_tracker_importer: + _web2py_date_tracker_importer = \ + _Web2pyDateTrackerImporter(_web2py_path) + __builtin__.__import__ = _web2py_date_tracker_importer + else: + __builtin__.__import__ = _web2py_importer + _is_tracking_changes = track + +_STANDARD_PYTHON_IMPORTER = __builtin__.__import__ # Keep standard importer +_web2py_importer = None # The standard web2py importer +_web2py_date_tracker_importer = None # The web2py importer with date tracking +_web2py_path = None # Absolute path of the web2py directory + +_is_tracking_changes = False # The tracking mode + +class _BaseImporter(object): + """ + The base importer. Dispatch the import the call to the standard Python + importer. + """ + + def begin(self): + """ + Many imports can be made for a single import statement. This method + help the management of this aspect. + """ + + def __call__(self, name, globals={}, locals={}, fromlist=[], level=-1): + """ + The import method itself. + """ + return _STANDARD_PYTHON_IMPORTER(name, globals, locals, fromlist, + level) + + def end(self): + """ + Needed for clean up. + """ + + +class _DateTrackerImporter(_BaseImporter): + """ + An importer tracking the date of the module files and reloading them when + they have changed. + """ + + _PACKAGE_PATH_SUFFIX = os.path.sep+"__init__.py" + + def __init__(self): + super(_DateTrackerImporter, self).__init__() + self._import_dates = {} # Import dates of the files of the modules + # Avoid reloading cause by file modifications of reload: + self._tl = threading.local() + self._tl._modules_loaded = None + + def begin(self): + self._tl._modules_loaded = set() + + def __call__(self, name, globals={}, locals={}, fromlist=[], level=-1): + """ + The import method itself. + """ + + call_begin_end = self._tl._modules_loaded == None + if call_begin_end: + self.begin() + + try: + self._tl.globals = globals + self._tl.locals = locals + self._tl.level = level + + # Check the date and reload if needed: + self._update_dates(name, fromlist) + + # Try to load the module and update the dates if it works: + result = super(_DateTrackerImporter, self) \ + .__call__(name, globals, locals, fromlist, level) + # Module maybe loaded for the 1st time so we need to set the date + self._update_dates(name, fromlist) + return result + except Exception, e: + raise e # Don't hide something that went wrong + finally: + if call_begin_end: + self.end() + + def _update_dates(self, name, fromlist): + """ + Update all the dates associated to the statement import. A single + import statement may import many modules. + """ + + self._reload_check(name) + if fromlist: + for fromlist_name in fromlist: + self._reload_check("%s.%s" % (name, fromlist_name)) + + def _reload_check(self, name): + """ + Update the date associated to the module and reload the module if + the file has changed. + """ + + module = sys.modules.get(name) + file = self._get_module_file(module) + if file: + date = self._import_dates.get(file) + new_date = None + reload_mod = False + mod_to_pack = False # Module turning into a package? (special case) + try: + new_date = os.path.getmtime(file) + except: + self._import_dates.pop(file, None) # Clean up + # Handle module changing in package and + #package changing in module: + if file.endswith(".py"): + # Get path without file ext: + file = os.path.splitext(file)[0] + reload_mod = os.path.isdir(file) \ + and os.path.isfile(file+self._PACKAGE_PATH_SUFFIX) + mod_to_pack = reload_mod + else: # Package turning into module? + file += ".py" + reload_mod = os.path.isfile(file) + if reload_mod: + new_date = os.path.getmtime(file) # Refresh file date + if reload_mod or not date or new_date > date: + self._import_dates[file] = new_date + if reload_mod or (date and new_date > date): + if module not in self._tl._modules_loaded: + if mod_to_pack: + # Module turning into a package: + mod_name = module.__name__ + del sys.modules[mod_name] # Delete the module + # Reload the module: + super(_DateTrackerImporter, self).__call__ \ + (mod_name, self._tl.globals, self._tl.locals, [], + self._tl.level) + else: + reload(module) + self._tl._modules_loaded.add(module) + + def end(self): + self._tl._modules_loaded = None + + @classmethod + def _get_module_file(cls, module): + """ + Get the absolute path file associated to the module or None. + """ + + file = getattr(module, "__file__", None) + if file: + # Make path absolute if not: + #file = os.path.join(cls.web2py_path, file) + + file = os.path.splitext(file)[0]+".py" # Change .pyc for .py + if file.endswith(cls._PACKAGE_PATH_SUFFIX): + file = os.path.dirname(file) # Track dir for packages + return file + +class _Web2pyImporter(_BaseImporter): + """ + The standard web2py importer. Like the standard Python importer but it + tries to transform import statements as something like + "import applications.app_name.modules.x". If the import failed, fall back + on _BaseImporter. + """ + + _RE_ESCAPED_PATH_SEP = re.escape(os.path.sep) # os.path.sep escaped for re + + def __init__(self, web2py_path): + """ + @param web2py_path: The absolute path of the web2py installation. + """ + + global DEBUG + super(_Web2pyImporter, self).__init__() + self.web2py_path = web2py_path + self.__web2py_path_os_path_sep = self.web2py_path+os.path.sep + self.__web2py_path_os_path_sep_len = len(self.__web2py_path_os_path_sep) + self.__RE_APP_DIR = re.compile( + self._RE_ESCAPED_PATH_SEP.join( \ + ( \ + #"^" + re.escape(web2py_path), # Not working with Python 2.5 + "^(" + "applications", + "[^", + "]+)", + "", + ) )) + + def _matchAppDir(self, file_path): + """ + Does the file in a directory inside the "applications" directory? + """ + + if file_path.startswith(self.__web2py_path_os_path_sep): + file_path = file_path[self.__web2py_path_os_path_sep_len:] + return self.__RE_APP_DIR.match(file_path) + return False + + def __call__(self, name, globals={}, locals={}, fromlist=[], level=-1): + """ + The import method itself. + """ + + self.begin() + #try: + # if not relative and not from applications: + if not name.startswith(".") and level <= 0 \ + and not name.startswith("applications.") \ + and isinstance(globals, dict): + # Get the name of the file do the import + caller_file_name = os.path.join(self.web2py_path, \ + globals.get("__file__", "")) + # Is the path in an application directory? + match_app_dir = self._matchAppDir(caller_file_name) + if match_app_dir: + try: + # Get the prefix to add for the import + # (like applications.app_name.modules): + modules_prefix = \ + ".".join((match_app_dir.group(1). \ + replace(os.path.sep, "."), "modules")) + if not fromlist: + # import like "import x" or "import x.y" + return self.__import__dot(modules_prefix, name, + globals, locals, fromlist, level) + else: + # import like "from x import a, b, ..." + return super(_Web2pyImporter, self) \ + .__call__(modules_prefix+"."+name, + globals, locals, fromlist, level) + except ImportError: + pass + return super(_Web2pyImporter, self).__call__(name, globals, locals, + fromlist, level) + #except Exception, e: + # raise e # Don't hide something that went wrong + #finally: + self.end() + + def __import__dot(self, prefix, name, globals, locals, fromlist, + level): + """ + Here we will import x.y.z as many imports like: + from applications.app_name.modules import x + from applications.app_name.modules.x import y + from applications.app_name.modules.x.y import z. + x will be the module returned. + """ + + result = None + for name in name.split("."): + new_mod = super(_Web2pyImporter, self).__call__(prefix, globals, + locals, [name], level) + try: + result = result or new_mod.__dict__[name] + except KeyError: + raise ImportError() + prefix += "." + name + return result + +class _Web2pyDateTrackerImporter(_Web2pyImporter, _DateTrackerImporter): + """ + Like _Web2pyImporter but using a _DateTrackerImporter. + """ + ADDED gluon/dal.py Index: gluon/dal.py ================================================================== --- gluon/dal.py +++ gluon/dal.py @@ -0,0 +1,6063 @@ +#!/bin/env python +# -*- coding: utf-8 -*- + +""" +This file is part of the web2py Web Framework +Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> +License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) + +Thanks to + * Niall Sweeny <niall.sweeny@fonjax.com> for MS SQL support + * Marcel Leuthi <mluethi@mlsystems.ch> for Oracle support + * Denes + * Chris Clark + * clach05 + * Denes Lengyel + * and many others who have contributed to current and previous versions + +This file contains the DAL support for many relational databases, +including: +- SQLite +- MySQL +- Postgres +- Oracle +- MS SQL +- DB2 +- Interbase +- Ingres +- SapDB (experimental) +- Cubrid (experimental) +- CouchDB (experimental) +- MongoDB (in progress) +- Google:nosql +- Google:sql + +Example of usage: + +>>> # from dal import DAL, Field + +### create DAL connection (and create DB if not exists) +>>> db=DAL(('mysql://a:b@locahost/x','sqlite://storage.sqlite'),folder=None) + +### define a table 'person' (create/aster as necessary) +>>> person = db.define_table('person',Field('name','string')) + +### insert a record +>>> id = person.insert(name='James') + +### retrieve it by id +>>> james = person(id) + +### retrieve it by name +>>> james = person(name='James') + +### retrieve it by arbitrary query +>>> query = (person.name=='James')&(person.name.startswith('J')) +>>> james = db(query).select(person.ALL)[0] + +### update one record +>>> james.update_record(name='Jim') + +### update multiple records by query +>>> db(person.name.like('J%')).update(name='James') +1 + +### delete records by query +>>> db(person.name.lower()=='jim').delete() +0 + +### retrieve multiple records (rows) +>>> people = db(person).select(orderby=person.name,groupby=person.name,limitby=(0,100)) + +### further filter them +>>> james = people.find(lambda row: row.name=='James').first() +>>> print james.id, james.name +1 James + +### check aggrgates +>>> counter = person.id.count() +>>> print db(person).select(counter).first()(counter) +1 + +### delete one record +>>> james.delete_record() +1 + +### delete (drop) entire database table +>>> person.drop() + +Supported field types: +id string text boolean integer double decimal password upload blob time date datetime, + +Supported DAL URI strings: +'sqlite://test.db' +'sqlite:memory' +'jdbc:sqlite://test.db' +'mysql://root:none@localhost/test' +'postgres://mdipierro:none@localhost/test' +'jdbc:postgres://mdipierro:none@localhost/test' +'mssql://web2py:none@A64X2/web2py_test' +'mssql2://web2py:none@A64X2/web2py_test' # alternate mappings +'oracle://username:password@database' +'firebird://user:password@server:3050/database' +'db2://DSN=dsn;UID=user;PWD=pass' +'firebird://username:password@hostname/database' +'firebird_embedded://username:password@c://path' +'informix://user:password@server:3050/database' +'informixu://user:password@server:3050/database' # unicode informix +'google:datastore' # for google app engine datastore +'google:sql' # for google app engine with sql (mysql compatible) +'teradata://DSN=dsn;UID=user;PWD=pass' # experimental + +For more info: +help(DAL) +help(Field) +""" + +################################################################################### +# this file orly exposes DAL and Field +################################################################################### + +__all__ = ['DAL', 'Field'] +MAXCHARLENGTH = 512 +INFINITY = 2**15 # not quite but reasonable default max char length + +import re +import sys +import locale +import os +import types +import cPickle +import datetime +import threading +import time +import cStringIO +import csv +import copy +import socket +import logging +import copy_reg +import base64 +import shutil +import marshal +import decimal +import struct +import urllib +import hashlib +import uuid +import glob + +CALLABLETYPES = (types.LambdaType, types.FunctionType, types.BuiltinFunctionType, + types.MethodType, types.BuiltinMethodType) + + +################################################################################### +# following checks allows running of dal without web2py as a standalone module +################################################################################### +try: + from utils import web2py_uuid +except ImportError: + import uuid + def web2py_uuid(): return str(uuid.uuid4()) + +try: + import portalocker + have_portalocker = True +except ImportError: + have_portalocker = False + +try: + import serializers + have_serializers = True +except ImportError: + have_serializers = False + +try: + import validators + have_validators = True +except ImportError: + have_validators = False + +logger = logging.getLogger("web2py.dal") +DEFAULT = lambda:0 + +sql_locker = threading.RLock() +thread = threading.local() + +# internal representation of tables with field +# <table>.<field>, tables and fields may only be [a-zA-Z0-0_] + +regex_dbname = re.compile('^(\w+)(\:\w+)*') +table_field = re.compile('^[\w_]+\.[\w_]+$') +regex_content = re.compile('(?P<table>[\w\-]+)\.(?P<field>[\w\-]+)\.(?P<uuidkey>[\w\-]+)\.(?P<name>\w+)\.\w+$') +regex_cleanup_fn = re.compile('[\'"\s;]+') +string_unpack=re.compile('(?<!\|)\|(?!\|)') +regex_python_keywords = re.compile('^(and|del|from|not|while|as|elif|global|or|with|assert|else|if|pass|yield|break|except|import|print|class|exec|in|raise|continue|finally|is|return|def|for|lambda|try)$') + + + +# list of drivers will be built on the fly +# and lists only what is available +drivers = [] + +try: + from new import classobj + from google.appengine.ext import db as gae + from google.appengine.api import namespace_manager, rdbms + from google.appengine.api.datastore_types import Key ### needed for belongs on ID + from google.appengine.ext.db.polymodel import PolyModel + drivers.append('google') +except ImportError: + pass + +if not 'google' in drivers: + + try: + from pysqlite2 import dbapi2 as sqlite3 + drivers.append('pysqlite2') + except ImportError: + try: + from sqlite3 import dbapi2 as sqlite3 + drivers.append('SQLite3') + except ImportError: + logger.debug('no sqlite3 or pysqlite2.dbapi2 driver') + + try: + import contrib.pymysql as pymysql + drivers.append('pymysql') + except ImportError: + logger.debug('no pymysql driver') + + try: + import psycopg2 + drivers.append('PostgreSQL') + except ImportError: + logger.debug('no psycopg2 driver') + + try: + import cx_Oracle + drivers.append('Oracle') + except ImportError: + logger.debug('no cx_Oracle driver') + + try: + import pyodbc + drivers.append('MSSQL/DB2') + except ImportError: + logger.debug('no MSSQL/DB2 driver') + + try: + import kinterbasdb + drivers.append('Interbase') + except ImportError: + logger.debug('no kinterbasdb driver') + + try: + import firebirdsql + drivers.append('Firebird') + except ImportError: + logger.debug('no Firebird driver') + + try: + import informixdb + drivers.append('Informix') + logger.warning('Informix support is experimental') + except ImportError: + logger.debug('no informixdb driver') + + try: + import sapdb + drivers.append('SAPDB') + logger.warning('SAPDB support is experimental') + except ImportError: + logger.debug('no sapdb driver') + + try: + import cubriddb + drivers.append('Cubrid') + logger.warning('Cubrid support is experimental') + except ImportError: + logger.debug('no cubriddb driver') + + try: + from com.ziclix.python.sql import zxJDBC + import java.sql + # Try sqlite jdbc driver from http://www.zentus.com/sqlitejdbc/ + from org.sqlite import JDBC # required by java.sql; ensure we have it + drivers.append('zxJDBC') + logger.warning('zxJDBC support is experimental') + is_jdbc = True + except ImportError: + logger.debug('no zxJDBC driver') + is_jdbc = False + + try: + import ingresdbi + drivers.append('Ingres') + except ImportError: + logger.debug('no Ingres driver') + # NOTE could try JDBC....... + + try: + import couchdb + drivers.append('CouchDB') + except ImportError: + logger.debug('no couchdb driver') + + try: + import pymongo + drivers.append('mongoDB') + except: + logger.debug('no mongoDB driver') + + +if 'google' in drivers: + + is_jdbc = False + + class GAEDecimalProperty(gae.Property): + """ + GAE decimal implementation + """ + data_type = decimal.Decimal + + def __init__(self, precision, scale, **kwargs): + super(GAEDecimalProperty, self).__init__(self, **kwargs) + d = '1.' + for x in range(scale): + d += '0' + self.round = decimal.Decimal(d) + + def get_value_for_datastore(self, model_instance): + value = super(GAEDecimalProperty, self).get_value_for_datastore(model_instance) + if value: + return str(value) + else: + return None + + def make_value_from_datastore(self, value): + if value: + return decimal.Decimal(value).quantize(self.round) + else: + return None + + def validate(self, value): + value = super(GAEDecimalProperty, self).validate(value) + if value is None or isinstance(value, decimal.Decimal): + return value + elif isinstance(value, basestring): + return decimal.Decimal(value) + raise gae.BadValueError("Property %s must be a Decimal or string." % self.name) + +################################################################################### +# class that handles connection pooling (all adapters derived form this one) +################################################################################### + +class ConnectionPool(object): + + pools = {} + + @staticmethod + def set_folder(folder): + thread.folder = folder + + # ## this allows gluon to commit/rollback all dbs in this thread + + @staticmethod + def close_all_instances(action): + """ to close cleanly databases in a multithreaded environment """ + if not hasattr(thread,'instances'): + return + while thread.instances: + instance = thread.instances.pop() + getattr(instance,action)() + # ## if you want pools, recycle this connection + really = True + if instance.pool_size: + sql_locker.acquire() + pool = ConnectionPool.pools[instance.uri] + if len(pool) < instance.pool_size: + pool.append(instance.connection) + really = False + sql_locker.release() + if really: + getattr(instance,'close')() + return + + def find_or_make_work_folder(self): + """ this actually does not make the folder. it has to be there """ + if hasattr(thread,'folder'): + self.folder = thread.folder + else: + self.folder = thread.folder = '' + + # Creating the folder if it does not exist + if False and self.folder and not os.path.exists(self.folder): + os.mkdir(self.folder) + + def pool_connection(self, f): + if not self.pool_size: + self.connection = f() + else: + uri = self.uri + sql_locker.acquire() + if not uri in ConnectionPool.pools: + ConnectionPool.pools[uri] = [] + if ConnectionPool.pools[uri]: + self.connection = ConnectionPool.pools[uri].pop() + sql_locker.release() + else: + sql_locker.release() + self.connection = f() + if not hasattr(thread,'instances'): + thread.instances = [] + thread.instances.append(self) + + +################################################################################### +# this is a generic adapter that does nothing; all others are derived form this one +################################################################################### + +class BaseAdapter(ConnectionPool): + + driver = None + maxcharlength = INFINITY + commit_on_alter_table = False + support_distributed_transaction = False + uploads_in_blob = False + types = { + 'boolean': 'CHAR(1)', + 'string': 'CHAR(%(length)s)', + 'text': 'TEXT', + 'password': 'CHAR(%(length)s)', + 'blob': 'BLOB', + 'upload': 'CHAR(%(length)s)', + 'integer': 'INTEGER', + 'double': 'DOUBLE', + 'decimal': 'DOUBLE', + 'date': 'DATE', + 'time': 'TIME', + 'datetime': 'TIMESTAMP', + 'id': 'INTEGER PRIMARY KEY AUTOINCREMENT', + 'reference': 'INTEGER REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s', + 'list:integer': 'TEXT', + 'list:string': 'TEXT', + 'list:reference': 'TEXT', + } + + def integrity_error(self): + return self.driver.IntegrityError + + def file_exists(self, filename): + """ + to be used ONLY for files that on GAE may not be on filesystem + """ + return os.path.exists(filename) + + def file_open(self, filename, mode='rb', lock=True): + """ + to be used ONLY for files that on GAE may not be on filesystem + """ + fileobj = open(filename,mode) + if have_portalocker and lock: + if mode in ('r','rb'): + portalocker.lock(fileobj,portalocker.LOCK_SH) + elif mode in ('w','wb','a'): + portalocker.lock(fileobj,portalocker.LOCK_EX) + else: + fileobj.close() + raise RuntimeError, "Unsupported file_open mode" + return fileobj + + def file_close(self, fileobj, unlock=True): + """ + to be used ONLY for files that on GAE may not be on filesystem + """ + if fileobj: + if have_portalocker and unlock: + portalocker.unlock(fileobj) + fileobj.close() + + def file_delete(self, filename): + os.unlink(filename) + + def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8', + credential_decoder=lambda x:x, driver_args={}, + adapter_args={}): + self.db = db + self.dbengine = "None" + self.uri = uri + self.pool_size = pool_size + self.folder = folder + self.db_codec = db_codec + class Dummy(object): + lastrowid = 1 + def __getattr__(self, value): + return lambda *a, **b: [] + self.connection = Dummy() + self.cursor = Dummy() + + def sequence_name(self,tablename): + return '%s_sequence' % tablename + + def trigger_name(self,tablename): + return '%s_sequence' % tablename + + + def create_table(self, table, migrate=True, fake_migrate=False, polymodel=None): + fields = [] + sql_fields = {} + sql_fields_aux = {} + TFK = {} + tablename = table._tablename + sortable = 0 + for field in table: + sortable += 1 + k = field.name + if isinstance(field.type,SQLCustomType): + ftype = field.type.native or field.type.type + elif field.type.startswith('reference'): + referenced = field.type[10:].strip() + constraint_name = self.constraint_name(tablename, field.name) + if hasattr(table,'_primarykey'): + rtablename,rfieldname = referenced.split('.') + rtable = table._db[rtablename] + rfield = rtable[rfieldname] + # must be PK reference or unique + if rfieldname in rtable._primarykey or rfield.unique: + ftype = self.types[rfield.type[:9]] % dict(length=rfield.length) + # multicolumn primary key reference? + if not rfield.unique and len(rtable._primarykey)>1 : + # then it has to be a table level FK + if rtablename not in TFK: + TFK[rtablename] = {} + TFK[rtablename][rfieldname] = field.name + else: + ftype = ftype + \ + self.types['reference FK'] %dict(\ + constraint_name=constraint_name, + table_name=tablename, + field_name=field.name, + foreign_key='%s (%s)'%(rtablename, rfieldname), + on_delete_action=field.ondelete) + else: + # make a guess here for circular references + id_fieldname = referenced in table._db and table._db[referenced]._id.name or 'id' + ftype = self.types[field.type[:9]]\ + % dict(table_name=tablename, + field_name=field.name, + constraint_name=constraint_name, + foreign_key=referenced + ('(%s)' % id_fieldname), + on_delete_action=field.ondelete) + elif field.type.startswith('list:reference'): + ftype = self.types[field.type[:14]] + elif field.type.startswith('decimal'): + precision, scale = map(int,field.type[8:-1].split(',')) + ftype = self.types[field.type[:7]] % \ + dict(precision=precision,scale=scale) + elif not field.type in self.types: + raise SyntaxError, 'Field: unknown field type: %s for %s' % \ + (field.type, field.name) + else: + ftype = self.types[field.type]\ + % dict(length=field.length) + if not field.type.startswith('id') and not field.type.startswith('reference'): + if field.notnull: + ftype += ' NOT NULL' + else: + ftype += self.ALLOW_NULL() + if field.unique: + ftype += ' UNIQUE' + + # add to list of fields + sql_fields[field.name] = dict(sortable=sortable, + type=str(field.type), + sql=ftype) + + if isinstance(field.default,(str,int,float)): + # caveat: sql_fields and sql_fields_aux differ for default values + # sql_fields is used to trigger migrations and sql_fields_aux + # are used for create table + # the reason is that we do not want to trigger a migration simply + # because a default value changes + not_null = self.NOT_NULL(field.default,field.type) + ftype = ftype.replace('NOT NULL',not_null) + sql_fields_aux[field.name] = dict(sql=ftype) + + fields.append('%s %s' % (field.name, ftype)) + other = ';' + + # backend-specific extensions to fields + if self.dbengine == 'mysql': + if not hasattr(table, "_primarykey"): + fields.append('PRIMARY KEY(%s)' % table._id.name) + other = ' ENGINE=InnoDB CHARACTER SET utf8;' + + fields = ',\n '.join(fields) + for rtablename in TFK: + rfields = TFK[rtablename] + pkeys = table._db[rtablename]._primarykey + fkeys = [ rfields[k] for k in pkeys ] + fields = fields + ',\n ' + \ + self.types['reference TFK'] %\ + dict(table_name=tablename, + field_name=', '.join(fkeys), + foreign_table=rtablename, + foreign_key=', '.join(pkeys), + on_delete_action=field.ondelete) + + if hasattr(table,'_primarykey'): + query = '''CREATE TABLE %s(\n %s,\n %s) %s''' % \ + (tablename, fields, self.PRIMARY_KEY(', '.join(table._primarykey)),other) + else: + query = '''CREATE TABLE %s(\n %s\n)%s''' % \ + (tablename, fields, other) + + if self.uri.startswith('sqlite:///'): + path_encoding = sys.getfilesystemencoding() or locale.getdefaultlocale()[1] or 'utf8' + dbpath = self.uri[9:self.uri.rfind('/')].decode('utf8').encode(path_encoding) + else: + dbpath = self.folder + + if not migrate: + return query + elif self.uri.startswith('sqlite:memory'): + table._dbt = None + elif isinstance(migrate, str): + table._dbt = os.path.join(dbpath, migrate) + else: + table._dbt = os.path.join(dbpath, '%s_%s.table' \ + % (table._db._uri_hash, tablename)) + if table._dbt: + table._loggername = os.path.join(dbpath, 'sql.log') + logfile = self.file_open(table._loggername, 'a') + else: + logfile = None + if not table._dbt or not self.file_exists(table._dbt): + if table._dbt: + logfile.write('timestamp: %s\n' + % datetime.datetime.today().isoformat()) + logfile.write(query + '\n') + if not fake_migrate: + self.create_sequence_and_triggers(query,table) + table._db.commit() + if table._dbt: + tfile = self.file_open(table._dbt, 'w') + cPickle.dump(sql_fields, tfile) + self.file_close(tfile) + if fake_migrate: + logfile.write('faked!\n') + else: + logfile.write('success!\n') + else: + tfile = self.file_open(table._dbt, 'r') + try: + sql_fields_old = cPickle.load(tfile) + except EOFError: + self.file_close(tfile) + self.file_close(logfile) + raise RuntimeError, 'File %s appears corrupted' % table._dbt + self.file_close(tfile) + if sql_fields != sql_fields_old: + self.migrate_table(table, + sql_fields, sql_fields_old, + sql_fields_aux, logfile, + fake_migrate=fake_migrate) + self.file_close(logfile) + return query + + def migrate_table( + self, + table, + sql_fields, + sql_fields_old, + sql_fields_aux, + logfile, + fake_migrate=False, + ): + tablename = table._tablename + def fix(item): + k,v=item + if not isinstance(v,dict): + v=dict(type='unkown',sql=v) + return k.lower(),v + ### make sure all field names are lower case to avoid conflicts + sql_fields = dict(map(fix,sql_fields.items())) + sql_fields_old = dict(map(fix,sql_fields_old.items())) + sql_fields_aux = dict(map(fix,sql_fields_aux.items())) + + keys = sql_fields.keys() + for key in sql_fields_old: + if not key in keys: + keys.append(key) + if self.dbengine == 'mssql': + new_add = '; ALTER TABLE %s ADD ' % tablename + else: + new_add = ', ADD ' + + metadata_change = False + sql_fields_current = copy.copy(sql_fields_old) + for key in keys: + query = None + if not key in sql_fields_old: + sql_fields_current[key] = sql_fields[key] + query = ['ALTER TABLE %s ADD %s %s;' % \ + (tablename, key, + sql_fields_aux[key]['sql'].replace(', ', new_add))] + metadata_change = True + elif self.dbengine == 'sqlite': + if key in sql_fields: + sql_fields_current[key] = sql_fields[key] + metadata_change = True + elif not key in sql_fields: + del sql_fields_current[key] + if not self.dbengine in ('firebird',): + query = ['ALTER TABLE %s DROP COLUMN %s;' % (tablename, key)] + else: + query = ['ALTER TABLE %s DROP %s;' % (tablename, key)] + metadata_change = True + elif sql_fields[key]['sql'] != sql_fields_old[key]['sql'] \ + and not isinstance(table[key].type, SQLCustomType) \ + and not (table[key].type.startswith('reference') and \ + sql_fields[key]['sql'].startswith('INT,') and \ + sql_fields_old[key]['sql'].startswith('INT NOT NULL,')): + sql_fields_current[key] = sql_fields[key] + t = tablename + tt = sql_fields_aux[key]['sql'].replace(', ', new_add) + if not self.dbengine in ('firebird',): + query = ['ALTER TABLE %s ADD %s__tmp %s;' % (t, key, tt), + 'UPDATE %s SET %s__tmp=%s;' % (t, key, key), + 'ALTER TABLE %s DROP COLUMN %s;' % (t, key), + 'ALTER TABLE %s ADD %s %s;' % (t, key, tt), + 'UPDATE %s SET %s=%s__tmp;' % (t, key, key), + 'ALTER TABLE %s DROP COLUMN %s__tmp;' % (t, key)] + else: + query = ['ALTER TABLE %s ADD %s__tmp %s;' % (t, key, tt), + 'UPDATE %s SET %s__tmp=%s;' % (t, key, key), + 'ALTER TABLE %s DROP %s;' % (t, key), + 'ALTER TABLE %s ADD %s %s;' % (t, key, tt), + 'UPDATE %s SET %s=%s__tmp;' % (t, key, key), + 'ALTER TABLE %s DROP %s__tmp;' % (t, key)] + metadata_change = True + elif sql_fields[key]['type'] != sql_fields_old[key]['type']: + sql_fields_current[key] = sql_fields[key] + metadata_change = True + + if query: + logfile.write('timestamp: %s\n' + % datetime.datetime.today().isoformat()) + table._db['_lastsql'] = '\n'.join(query) + for sub_query in query: + logfile.write(sub_query + '\n') + if not fake_migrate: + self.execute(sub_query) + # caveat. mysql, oracle and firebird do not allow multiple alter table + # in one transaction so we must commit partial transactions and + # update table._dbt after alter table. + if table._db._adapter.commit_on_alter_table: + table._db.commit() + tfile = self.file_open(table._dbt, 'w') + cPickle.dump(sql_fields_current, tfile) + self.file_close(tfile) + logfile.write('success!\n') + else: + logfile.write('faked!\n') + elif metadata_change: + tfile = self.file_open(table._dbt, 'w') + cPickle.dump(sql_fields_current, tfile) + self.file_close(tfile) + + if metadata_change and \ + not (query and self.dbengine in ('mysql','oracle','firebird')): + table._db.commit() + tfile = self.file_open(table._dbt, 'w') + cPickle.dump(sql_fields_current, tfile) + self.file_close(tfile) + + def LOWER(self,first): + return 'LOWER(%s)' % self.expand(first) + + def UPPER(self,first): + return 'UPPER(%s)' % self.expand(first) + + def EXTRACT(self,first,what): + return "EXTRACT(%s FROM %s)" % (what, self.expand(first)) + + def AGGREGATE(self,first,what): + return "%s(%s)" % (what,self.expand(first)) + + def JOIN(self): + return 'JOIN' + + def LEFT_JOIN(self): + return 'LEFT JOIN' + + def RANDOM(self): + return 'Random()' + + def NOT_NULL(self,default,field_type): + return 'NOT NULL DEFAULT %s' % self.represent(default,field_type) + + def COALESCE_ZERO(self,first): + return 'COALESCE(%s,0)' % self.expand(first) + + def ALLOW_NULL(self): + return '' + + def SUBSTRING(self,field,parameters): + return 'SUBSTR(%s,%s,%s)' % (self.expand(field), parameters[0], parameters[1]) + + def PRIMARY_KEY(self,key): + return 'PRIMARY KEY(%s)' % key + + def _drop(self,table,mode): + return ['DROP TABLE %s;' % table] + + def drop(self, table, mode=''): + if table._dbt: + logfile = self.file_open(table._loggername, 'a') + queries = self._drop(table, mode) + for query in queries: + if table._dbt: + logfile.write(query + '\n') + self.execute(query) + table._db.commit() + del table._db[table._tablename] + del table._db.tables[table._db.tables.index(table._tablename)] + table._db._update_referenced_by(table._tablename) + if table._dbt: + self.file_delete(table._dbt) + logfile.write('success!\n') + + def _insert(self,table,fields): + keys = ','.join(f.name for f,v in fields) + values = ','.join(self.expand(v,f.type) for f,v in fields) + return 'INSERT INTO %s(%s) VALUES (%s);' % (table, keys, values) + + def insert(self,table,fields): + query = self._insert(table,fields) + try: + self.execute(query) + except Exception, e: + if isinstance(e,self.integrity_error_class()): + return None + raise e + if hasattr(table,'_primarykey'): + return dict([(k[0].name, k[1]) for k in fields \ + if k[0].name in table._primarykey]) + id = self.lastrowid(table) + if not isinstance(id,int): + return id + rid = Reference(id) + (rid._table, rid._record) = (table, None) + return rid + + def bulk_insert(self,table,items): + return [self.insert(table,item) for item in items] + + def NOT(self,first): + return '(NOT %s)' % self.expand(first) + + def AND(self,first,second): + return '(%s AND %s)' % (self.expand(first),self.expand(second)) + + def OR(self,first,second): + return '(%s OR %s)' % (self.expand(first),self.expand(second)) + + def BELONGS(self,first,second): + if isinstance(second,str): + return '(%s IN (%s))' % (self.expand(first),second[:-1]) + elif second==[] or second==(): + return '(0)' + items =','.join(self.expand(item,first.type) for item in second) + return '(%s IN (%s))' % (self.expand(first),items) + + def LIKE(self,first,second): + return '(%s LIKE %s)' % (self.expand(first),self.expand(second,'string')) + + def STARTSWITH(self,first,second): + return '(%s LIKE %s)' % (self.expand(first),self.expand(second+'%','string')) + + def ENDSWITH(self,first,second): + return '(%s LIKE %s)' % (self.expand(first),self.expand('%'+second,'string')) + + def CONTAINS(self,first,second): + if first.type in ('string','text'): + key = '%'+str(second).replace('%','%%')+'%' + elif first.type.startswith('list:'): + key = '%|'+str(second).replace('|','||').replace('%','%%')+'|%' + return '(%s LIKE %s)' % (self.expand(first),self.expand(key,'string')) + + def EQ(self,first,second=None): + if second is None: + return '(%s IS NULL)' % self.expand(first) + return '(%s = %s)' % (self.expand(first),self.expand(second,first.type)) + + def NE(self,first,second=None): + if second is None: + return '(%s IS NOT NULL)' % self.expand(first) + return '(%s <> %s)' % (self.expand(first),self.expand(second,first.type)) + + def LT(self,first,second=None): + return '(%s < %s)' % (self.expand(first),self.expand(second,first.type)) + + def LE(self,first,second=None): + return '(%s <= %s)' % (self.expand(first),self.expand(second,first.type)) + + def GT(self,first,second=None): + return '(%s > %s)' % (self.expand(first),self.expand(second,first.type)) + + def GE(self,first,second=None): + return '(%s >= %s)' % (self.expand(first),self.expand(second,first.type)) + + def ADD(self,first,second): + return '(%s + %s)' % (self.expand(first),self.expand(second,first.type)) + + def SUB(self,first,second): + return '(%s - %s)' % (self.expand(first),self.expand(second,first.type)) + + def MUL(self,first,second): + return '(%s * %s)' % (self.expand(first),self.expand(second,first.type)) + + def DIV(self,first,second): + return '(%s / %s)' % (self.expand(first),self.expand(second,first.type)) + + def MOD(self,first,second): + return '(%s %% %s)' % (self.expand(first),self.expand(second,first.type)) + + def AS(self,first,second): + return '%s AS %s' % (self.expand(first),second) + + def ON(self,first,second): + return '%s ON %s' % (self.expand(first),self.expand(second)) + + def INVERT(self,first): + return '%s DESC' % self.expand(first) + + def COMMA(self,first,second): + return '%s, %s' % (self.expand(first),self.expand(second)) + + def expand(self,expression,field_type=None): + if isinstance(expression,Field): + return str(expression) + elif isinstance(expression, (Expression, Query)): + if not expression.second is None: + return expression.op(expression.first, expression.second) + elif not expression.first is None: + return expression.op(expression.first) + else: + return expression.op() + elif field_type: + return self.represent(expression,field_type) + elif isinstance(expression,(list,tuple)): + return ','.join([self.represent(item,field_type) for item in expression]) + else: + return str(expression) + + def alias(self,table,alias): + """ + given a table object, makes a new table object + with alias name. + """ + other = copy.copy(table) + other['_ot'] = other._tablename + other['ALL'] = SQLALL(other) + other['_tablename'] = alias + for fieldname in other.fields: + other[fieldname] = copy.copy(other[fieldname]) + other[fieldname]._tablename = alias + other[fieldname].tablename = alias + other[fieldname].table = other + table._db[alias] = other + return other + + def _truncate(self,table,mode = ''): + tablename = table._tablename + return ['TRUNCATE TABLE %s %s;' % (tablename, mode or '')] + + def truncate(self,table,mode= ' '): + # Prepare functions "write_to_logfile" and "close_logfile" + if table._dbt: + logfile = self.file_open(table._loggername, 'a') + else: + class Logfile(object): + def write(self, value): + pass + def close(self): + pass + logfile = Logfile() + + try: + queries = table._db._adapter._truncate(table, mode) + for query in queries: + logfile.write(query + '\n') + self.execute(query) + table._db.commit() + logfile.write('success!\n') + finally: + logfile.close() + + def _update(self,tablename,query,fields): + if query: + sql_w = ' WHERE ' + self.expand(query) + else: + sql_w = '' + sql_v = ','.join(['%s=%s' % (field.name, self.expand(value,field.type)) for (field,value) in fields]) + return 'UPDATE %s SET %s%s;' % (tablename, sql_v, sql_w) + + def update(self,tablename,query,fields): + sql = self._update(tablename,query,fields) + self.execute(sql) + try: + return self.cursor.rowcount + except: + return None + + def _delete(self,tablename, query): + if query: + sql_w = ' WHERE ' + self.expand(query) + else: + sql_w = '' + return 'DELETE FROM %s%s;' % (tablename, sql_w) + + def delete(self,tablename,query): + sql = self._delete(tablename,query) + ### special code to handle CASCADE in SQLite + db = self.db + table = db[tablename] + if self.dbengine=='sqlite' and table._referenced_by: + deleted = [x[table._id.name] for x in db(query).select(table._id)] + ### end special code to handle CASCADE in SQLite + self.execute(sql) + try: + counter = self.cursor.rowcount + except: + counter = None + ### special code to handle CASCADE in SQLite + if self.dbengine=='sqlite' and counter: + for tablename,fieldname in table._referenced_by: + f = db[tablename][fieldname] + if f.type=='reference '+table._tablename and f.ondelete=='CASCADE': + db(db[tablename][fieldname].belongs(deleted)).delete() + ### end special code to handle CASCADE in SQLite + return counter + + def get_table(self,query): + tablenames = self.tables(query) + if len(tablenames)==1: + return tablenames[0] + elif len(tablenames)<1: + raise RuntimeError, "No table selected" + else: + raise RuntimeError, "Too many tables selected" + + def _select(self, query, fields, attributes): + for key in set(attributes.keys())-set(('orderby','groupby','limitby', + 'required','cache','left', + 'distinct','having', 'join')): + raise SyntaxError, 'invalid select attribute: %s' % key + # ## if not fields specified take them all from the requested tables + new_fields = [] + for item in fields: + if isinstance(item,SQLALL): + new_fields += item.table + else: + new_fields.append(item) + fields = new_fields + tablenames = self.tables(query) + query = self.filter_tenant(query,tablenames) + if not fields: + for table in tablenames: + for field in self.db[table]: + fields.append(field) + else: + for field in fields: + if isinstance(field,basestring) and table_field.match(field): + tn,fn = field.split('.') + field = self.db[tn][fn] + for tablename in self.tables(field): + if not tablename in tablenames: + tablenames.append(tablename) + if len(tablenames) < 1: + raise SyntaxError, 'Set: no tables selected' + sql_f = ', '.join(map(self.expand,fields)) + self._colnames = [c.strip() for c in sql_f.split(', ')] + if query: + sql_w = ' WHERE ' + self.expand(query) + else: + sql_w = '' + sql_o = '' + sql_s = '' + left = attributes.get('left', False) + inner_join = attributes.get('join', False) + distinct = attributes.get('distinct', False) + groupby = attributes.get('groupby', False) + orderby = attributes.get('orderby', False) + having = attributes.get('having', False) + limitby = attributes.get('limitby', False) + if distinct is True: + sql_s += 'DISTINCT' + elif distinct: + sql_s += 'DISTINCT ON (%s)' % distinct + if inner_join: + icommand = self.JOIN() + if not isinstance(inner_join, (tuple, list)): + inner_join = [inner_join] + ijoint = [t._tablename for t in inner_join if not isinstance(t,Expression)] + ijoinon = [t for t in inner_join if isinstance(t, Expression)] + ijoinont = [t.first._tablename for t in ijoinon] + iexcluded = [t for t in tablenames if not t in ijoint + ijoinont] + if left: + join = attributes['left'] + command = self.LEFT_JOIN() + if not isinstance(join, (tuple, list)): + join = [join] + joint = [t._tablename for t in join if not isinstance(t,Expression)] + joinon = [t for t in join if isinstance(t, Expression)] + #patch join+left patch (solves problem with ordering in left joins) + tables_to_merge={} + [tables_to_merge.update(dict.fromkeys(self.tables(t))) for t in joinon] + joinont = [t.first._tablename for t in joinon] + [tables_to_merge.pop(t) for t in joinont if t in tables_to_merge] + important_tablenames = joint + joinont + tables_to_merge.keys() + excluded = [t for t in tablenames if not t in important_tablenames ] + def alias(t): + return str(self.db[t]) + if inner_join and not left: + sql_t = ', '.join(alias(t) for t in iexcluded) + for t in ijoinon: + sql_t += ' %s %s' % (icommand, str(t)) + elif not inner_join and left: + sql_t = ', '.join([alias(t) for t in excluded + tables_to_merge.keys()]) + if joint: + sql_t += ' %s %s' % (command, ','.join([t for t in joint])) + for t in joinon: + sql_t += ' %s %s' % (command, str(t)) + elif inner_join and left: + sql_t = ','.join([alias(t) for t in excluded + \ + tables_to_merge.keys() if t in iexcluded ]) + for t in ijoinon: + sql_t += ' %s %s' % (icommand, str(t)) + if joint: + sql_t += ' %s %s' % (command, ','.join([t for t in joint])) + for t in joinon: + sql_t += ' %s %s' % (command, str(t)) + else: + sql_t = ', '.join(alias(t) for t in tablenames) + if groupby: + if isinstance(groupby, (list, tuple)): + groupby = xorify(groupby) + sql_o += ' GROUP BY %s' % self.expand(groupby) + if having: + sql_o += ' HAVING %s' % attributes['having'] + if orderby: + if isinstance(orderby, (list, tuple)): + orderby = xorify(orderby) + if str(orderby) == '<random>': + sql_o += ' ORDER BY %s' % self.RANDOM() + else: + sql_o += ' ORDER BY %s' % self.expand(orderby) + if limitby: + if not orderby and tablenames: + sql_o += ' ORDER BY %s' % ', '.join(['%s.%s'%(t,x) for t in tablenames for x in ((hasattr(self.db[t],'_primarykey') and self.db[t]._primarykey) or [self.db[t]._id.name])]) + # oracle does not support limitby + return self.select_limitby(sql_s, sql_f, sql_t, sql_w, sql_o, limitby) + + def select_limitby(self, sql_s, sql_f, sql_t, sql_w, sql_o, limitby): + if limitby: + (lmin, lmax) = limitby + sql_o += ' LIMIT %i OFFSET %i' % (lmax - lmin, lmin) + return 'SELECT %s %s FROM %s%s%s;' % (sql_s, sql_f, sql_t, sql_w, sql_o) + + def select(self,query,fields,attributes): + """ + Always returns a Rows object, even if it may be empty + """ + def response(sql): + self.execute(sql) + return self.cursor.fetchall() + sql = self._select(query,fields,attributes) + if attributes.get('cache', None): + (cache_model, time_expire) = attributes['cache'] + del attributes['cache'] + key = self.uri + '/' + sql + key = (key<=200) and key or hashlib.md5(key).hexdigest() + rows = cache_model(key, lambda: response(sql), time_expire) + else: + rows = response(sql) + if isinstance(rows,tuple): + rows = list(rows) + limitby = attributes.get('limitby',None) or (0,) + rows = self.rowslice(rows,limitby[0],None) + return self.parse(rows,self._colnames) + + def _count(self,query,distinct=None): + tablenames = self.tables(query) + if query: + sql_w = ' WHERE ' + self.expand(query) + else: + sql_w = '' + sql_t = ','.join(tablenames) + if distinct: + if isinstance(distinct,(list,tuple)): + distinct = xorify(distinct) + sql_d = self.expand(distinct) + return 'SELECT count(DISTINCT %s) FROM %s%s' % (sql_d, sql_t, sql_w) + return 'SELECT count(*) FROM %s%s' % (sql_t, sql_w) + + def count(self,query,distinct=None): + self.execute(self._count(query,distinct)) + return self.cursor.fetchone()[0] + + + def tables(self,query): + tables = set() + if isinstance(query, Field): + tables.add(query.tablename) + elif isinstance(query, (Expression, Query)): + if query.first!=None: + tables = tables.union(self.tables(query.first)) + if query.second!=None: + tables = tables.union(self.tables(query.second)) + return list(tables) + + def commit(self): + return self.connection.commit() + + def rollback(self): + return self.connection.rollback() + + def close(self): + return self.connection.close() + + def distributed_transaction_begin(self,key): + return + + def prepare(self,key): + self.connection.prepare() + + def commit_prepared(self,key): + self.connection.commit() + + def rollback_prepared(self,key): + self.connection.rollback() + + def concat_add(self,table): + return ', ADD ' + + def constraint_name(self, table, fieldname): + return '%s_%s__constraint' % (table,fieldname) + + def create_sequence_and_triggers(self, query, table, **args): + self.execute(query) + + def log_execute(self,*a,**b): + self.db._lastsql = a[0] + t0 = time.time() + ret = self.cursor.execute(*a,**b) + self.db._timings.append((a[0],time.time()-t0)) + return ret + + def execute(self,*a,**b): + return self.log_execute(*a, **b) + + def represent(self, obj, fieldtype): + if isinstance(obj,CALLABLETYPES): + obj = obj() + if isinstance(fieldtype, SQLCustomType): + return fieldtype.encoder(obj) + if isinstance(obj, (Expression, Field)): + return str(obj) + if fieldtype.startswith('list:'): + if not obj: + obj = [] + if not isinstance(obj, (list, tuple)): + obj = [obj] + if isinstance(obj, (list, tuple)): + obj = bar_encode(obj) + if obj is None: + return 'NULL' + if obj == '' and not fieldtype[:2] in ['st', 'te', 'pa', 'up']: + return 'NULL' + r = self.represent_exceptions(obj,fieldtype) + if r != None: + return r + if fieldtype == 'boolean': + if obj and not str(obj)[:1].upper() in ['F', '0']: + return "'T'" + else: + return "'F'" + if fieldtype == 'id' or fieldtype == 'integer': + return str(int(obj)) + if fieldtype.startswith('decimal'): + return str(obj) + elif fieldtype.startswith('reference'): # reference + if fieldtype.find('.')>0: + return repr(obj) + elif isinstance(obj, (Row, Reference)): + return str(obj['id']) + return str(int(obj)) + elif fieldtype == 'double': + return repr(float(obj)) + if isinstance(obj, unicode): + obj = obj.encode(self.db_codec) + if fieldtype == 'blob': + obj = base64.b64encode(str(obj)) + elif fieldtype == 'date': + if isinstance(obj, (datetime.date, datetime.datetime)): + obj = obj.isoformat()[:10] + else: + obj = str(obj) + elif fieldtype == 'datetime': + if isinstance(obj, datetime.datetime): + obj = obj.isoformat()[:19].replace('T',' ') + elif isinstance(obj, datetime.date): + obj = obj.isoformat()[:10]+' 00:00:00' + else: + obj = str(obj) + elif fieldtype == 'time': + if isinstance(obj, datetime.time): + obj = obj.isoformat()[:10] + else: + obj = str(obj) + if not isinstance(obj,str): + obj = str(obj) + try: + obj.decode(self.db_codec) + except: + obj = obj.decode('latin1').encode(self.db_codec) + return "'%s'" % obj.replace("'", "''") + + def represent_exceptions(self, obj, fieldtype): + return None + + def lastrowid(self,table): + return None + + def integrity_error_class(self): + return type(None) + + def rowslice(self,rows,minimum=0,maximum=None): + """ by default this function does nothing, overload when db does not do slicing """ + return rows + + def parse(self, rows, colnames, blob_decode=True): + db = self.db + virtualtables = [] + new_rows = [] + for (i,row) in enumerate(rows): + new_row = Row() + for j,colname in enumerate(colnames): + value = row[j] + if not table_field.match(colnames[j]): + if not '_extra' in new_row: + new_row['_extra'] = Row() + new_row['_extra'][colnames[j]] = value + select_as_parser = re.compile("\s+AS\s+(\S+)") + new_column_name = select_as_parser.search(colnames[j]) + if not new_column_name is None: + column_name = new_column_name.groups(0) + setattr(new_row,column_name[0],value) + continue + (tablename, fieldname) = colname.split('.') + table = db[tablename] + field = table[fieldname] + field_type = field.type + if field.type != 'blob' and isinstance(value, str): + try: + value = value.decode(db._db_codec) + except Exception: + pass + if isinstance(value, unicode): + value = value.encode('utf-8') + if not tablename in new_row: + colset = new_row[tablename] = Row() + if tablename not in virtualtables: + virtualtables.append(tablename) + else: + colset = new_row[tablename] + + if isinstance(field_type, SQLCustomType): + colset[fieldname] = field_type.decoder(value) + # field_type = field_type.type + elif not isinstance(field_type, str) or value is None: + colset[fieldname] = value + elif isinstance(field_type, str) and \ + field_type.startswith('reference'): + referee = field_type[10:].strip() + if not '.' in referee: + colset[fieldname] = rid = Reference(value) + (rid._table, rid._record) = (db[referee], None) + else: ### reference not by id + colset[fieldname] = value + elif field_type == 'boolean': + if value == True or str(value)[:1].lower() == 't': + colset[fieldname] = True + else: + colset[fieldname] = False + elif field_type == 'date' \ + and (not isinstance(value, datetime.date)\ + or isinstance(value, datetime.datetime)): + (y, m, d) = map(int, str(value)[:10].strip().split('-')) + colset[fieldname] = datetime.date(y, m, d) + elif field_type == 'time' \ + and not isinstance(value, datetime.time): + time_items = map(int,str(value)[:8].strip().split(':')[:3]) + if len(time_items) == 3: + (h, mi, s) = time_items + else: + (h, mi, s) = time_items + [0] + colset[fieldname] = datetime.time(h, mi, s) + elif field_type == 'datetime'\ + and not isinstance(value, datetime.datetime): + (y, m, d) = map(int,str(value)[:10].strip().split('-')) + time_items = map(int,str(value)[11:19].strip().split(':')[:3]) + if len(time_items) == 3: + (h, mi, s) = time_items + else: + (h, mi, s) = time_items + [0] + colset[fieldname] = datetime.datetime(y, m, d, h, mi, s) + elif field_type == 'blob' and blob_decode: + colset[fieldname] = base64.b64decode(str(value)) + elif field_type.startswith('decimal'): + decimals = int(field_type[8:-1].split(',')[-1]) + if self.dbengine == 'sqlite': + value = ('%.' + str(decimals) + 'f') % value + if not isinstance(value, decimal.Decimal): + value = decimal.Decimal(str(value)) + colset[fieldname] = value + elif field_type.startswith('list:integer'): + if not self.dbengine=='google:datastore': + colset[fieldname] = bar_decode_integer(value) + else: + colset[fieldname] = value + elif field_type.startswith('list:reference'): + if not self.dbengine=='google:datastore': + colset[fieldname] = bar_decode_integer(value) + else: + colset[fieldname] = value + elif field_type.startswith('list:string'): + if not self.dbengine=='google:datastore': + colset[fieldname] = bar_decode_string(value) + else: + colset[fieldname] = value + else: + colset[fieldname] = value + if field_type == 'id': + id = colset[field.name] + colset.update_record = lambda _ = (colset, table, id), **a: update_record(_, a) + colset.delete_record = lambda t = table, i = id: t._db(t._id==i).delete() + for (referee_table, referee_name) in \ + table._referenced_by: + s = db[referee_table][referee_name] + if not referee_table in colset: + # for backward compatibility + colset[referee_table] = Set(db, s == id) + ### add new feature? + ### colset[referee_table+'_by_'+refree_name] = Set(db, s == id) + colset['id'] = id + new_rows.append(new_row) + rowsobj = Rows(db, new_rows, colnames, rawrows=rows) + for tablename in virtualtables: + for item in db[tablename].virtualfields: + try: + rowsobj = rowsobj.setvirtualfields(**{tablename:item}) + except KeyError: + # to avoid breaking virtualfields when partial select + pass + return rowsobj + + def filter_tenant(self,query,tablenames): + fieldname = self.db._request_tenant + for tablename in tablenames: + table = self.db[tablename] + if fieldname in table: + default = table[fieldname].default + if default!=None: + query = query&(table[fieldname]==default) + return query + +################################################################################### +# List of all the available adapters, they all extend BaseAdapter +################################################################################### + +class SQLiteAdapter(BaseAdapter): + + driver = globals().get('sqlite3',None) + + def EXTRACT(self,field,what): + return "web2py_extract('%s',%s)" % (what,self.expand(field)) + + @staticmethod + def web2py_extract(lookup, s): + table = { + 'year': (0, 4), + 'month': (5, 7), + 'day': (8, 10), + 'hour': (11, 13), + 'minute': (14, 16), + 'second': (17, 19), + } + try: + (i, j) = table[lookup] + return int(s[i:j]) + except: + return None + + def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8', + credential_decoder=lambda x:x, driver_args={}, + adapter_args={}): + self.db = db + self.dbengine = "sqlite" + self.uri = uri + self.pool_size = pool_size + self.folder = folder + self.db_codec = db_codec + self.find_or_make_work_folder() + path_encoding = sys.getfilesystemencoding() or locale.getdefaultlocale()[1] or 'utf8' + if uri.startswith('sqlite:memory'): + dbpath = ':memory:' + else: + dbpath = uri.split('://')[1] + if dbpath[0] != '/': + dbpath = os.path.join(self.folder.decode(path_encoding).encode('utf8'),dbpath) + if not 'check_same_thread' in driver_args: + driver_args['check_same_thread'] = False + def connect(dbpath=dbpath, driver_args=driver_args): + return self.driver.Connection(dbpath, **driver_args) + self.pool_connection(connect) + self.cursor = self.connection.cursor() + self.connection.create_function('web2py_extract', 2, SQLiteAdapter.web2py_extract) + + def _truncate(self,table,mode = ''): + tablename = table._tablename + return ['DELETE FROM %s;' % tablename, + "DELETE FROM sqlite_sequence WHERE name='%s';" % tablename] + + def lastrowid(self,table): + return self.cursor.lastrowid + + +class JDBCSQLiteAdapter(SQLiteAdapter): + + driver = globals().get('zxJDBC',None) + + def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8', + credential_decoder=lambda x:x, driver_args={}, + adapter_args={}): + self.db = db + self.dbengine = "sqlite" + self.uri = uri + self.pool_size = pool_size + self.folder = folder + self.db_codec = db_codec + self.find_or_make_work_folder() + path_encoding = sys.getfilesystemencoding() or locale.getdefaultlocale()[1] or 'utf8' + if uri.startswith('sqlite:memory'): + dbpath = ':memory:' + else: + dbpath = uri.split('://')[1] + if dbpath[0] != '/': + dbpath = os.path.join(self.folder.decode(path_encoding).encode('utf8'),dbpath) + def connect(dbpath=dbpath,driver_args=driver_args): + return self.driver.connect(java.sql.DriverManager.getConnection('jdbc:sqlite:'+dbpath),**driver_args) + self.pool_connection(connect) + self.cursor = self.connection.cursor() + # FIXME http://www.zentus.com/sqlitejdbc/custom_functions.html for UDFs + # self.connection.create_function('web2py_extract', 2, SQLiteAdapter.web2py_extract) + + def execute(self,a): + return self.log_execute(a[:-1]) + + +class MySQLAdapter(BaseAdapter): + + driver = globals().get('pymysql',None) + maxcharlength = 255 + commit_on_alter_table = True + support_distributed_transaction = True + types = { + 'boolean': 'CHAR(1)', + 'string': 'VARCHAR(%(length)s)', + 'text': 'LONGTEXT', + 'password': 'VARCHAR(%(length)s)', + 'blob': 'LONGBLOB', + 'upload': 'VARCHAR(%(length)s)', + 'integer': 'INT', + 'double': 'DOUBLE', + 'decimal': 'NUMERIC(%(precision)s,%(scale)s)', + 'date': 'DATE', + 'time': 'TIME', + 'datetime': 'DATETIME', + 'id': 'INT AUTO_INCREMENT NOT NULL', + 'reference': 'INT, INDEX %(field_name)s__idx (%(field_name)s), FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s', + 'list:integer': 'LONGTEXT', + 'list:string': 'LONGTEXT', + 'list:reference': 'LONGTEXT', + } + + def RANDOM(self): + return 'RAND()' + + def SUBSTRING(self,field,parameters): + return 'SUBSTRING(%s,%s,%s)' % (self.expand(field), parameters[0], parameters[1]) + + def _drop(self,table,mode): + # breaks db integrity but without this mysql does not drop table + return ['SET FOREIGN_KEY_CHECKS=0;','DROP TABLE %s;' % table,'SET FOREIGN_KEY_CHECKS=1;'] + + def distributed_transaction_begin(self,key): + self.execute('XA START;') + + def prepare(self,key): + self.execute("XA END;") + self.execute("XA PREPARE;") + + def commit_prepared(self,ley): + self.execute("XA COMMIT;") + + def rollback_prepared(self,key): + self.execute("XA ROLLBACK;") + + def concat_add(self,table): + return '; ALTER TABLE %s ADD ' % table + + def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8', + credential_decoder=lambda x:x, driver_args={}, + adapter_args={}): + self.db = db + self.dbengine = "mysql" + self.uri = uri + self.pool_size = pool_size + self.folder = folder + self.db_codec = db_codec + self.find_or_make_work_folder() + uri = uri.split('://')[1] + m = re.compile('^(?P<user>[^:@]+)(\:(?P<password>[^@]*))?@(?P<host>[^\:/]+)(\:(?P<port>[0-9]+))?/(?P<db>[^?]+)(\?set_encoding=(?P<charset>\w+))?$').match(uri) + if not m: + raise SyntaxError, \ + "Invalid URI string in DAL: %s" % self.uri + user = credential_decoder(m.group('user')) + if not user: + raise SyntaxError, 'User required' + password = credential_decoder(m.group('password')) + if not password: + password = '' + host = m.group('host') + if not host: + raise SyntaxError, 'Host name required' + db = m.group('db') + if not db: + raise SyntaxError, 'Database name required' + port = int(m.group('port') or '3306') + charset = m.group('charset') or 'utf8' + driver_args.update(dict(db=db, + user=credential_decoder(user), + passwd=credential_decoder(password), + host=host, + port=port, + charset=charset)) + def connect(driver_args=driver_args): + return self.driver.connect(**driver_args) + self.pool_connection(connect) + self.cursor = self.connection.cursor() + self.execute('SET FOREIGN_KEY_CHECKS=1;') + self.execute("SET sql_mode='NO_BACKSLASH_ESCAPES';") + + def lastrowid(self,table): + self.execute('select last_insert_id();') + return int(self.cursor.fetchone()[0]) + + +class PostgreSQLAdapter(BaseAdapter): + + driver = globals().get('psycopg2',None) + + support_distributed_transaction = True + types = { + 'boolean': 'CHAR(1)', + 'string': 'VARCHAR(%(length)s)', + 'text': 'TEXT', + 'password': 'VARCHAR(%(length)s)', + 'blob': 'BYTEA', + 'upload': 'VARCHAR(%(length)s)', + 'integer': 'INTEGER', + 'double': 'FLOAT8', + 'decimal': 'NUMERIC(%(precision)s,%(scale)s)', + 'date': 'DATE', + 'time': 'TIME', + 'datetime': 'TIMESTAMP', + 'id': 'SERIAL PRIMARY KEY', + 'reference': 'INTEGER REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s', + 'list:integer': 'TEXT', + 'list:string': 'TEXT', + 'list:reference': 'TEXT', + } + + def sequence_name(self,table): + return '%s_id_Seq' % table + + def RANDOM(self): + return 'RANDOM()' + + def distributed_transaction_begin(self,key): + return + + def prepare(self,key): + self.execute("PREPARE TRANSACTION '%s';" % key) + + def commit_prepared(self,key): + self.execute("COMMIT PREPARED '%s';" % key) + + def rollback_prepared(self,key): + self.execute("ROLLBACK PREPARED '%s';" % key) + + def create_sequence_and_triggers(self, query, table, **args): + # following lines should only be executed if table._sequence_name does not exist + # self.execute('CREATE SEQUENCE %s;' % table._sequence_name) + # self.execute("ALTER TABLE %s ALTER COLUMN %s SET DEFAULT NEXTVAL('%s');" \ + # % (table._tablename, table._fieldname, table._sequence_name)) + self.execute(query) + + def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8', + credential_decoder=lambda x:x, driver_args={}, + adapter_args={}): + self.db = db + self.dbengine = "postgres" + self.uri = uri + self.pool_size = pool_size + self.folder = folder + self.db_codec = db_codec + self.find_or_make_work_folder() + uri = uri.split('://')[1] + m = re.compile('^(?P<user>[^:@]+)(\:(?P<password>[^@]*))?@(?P<host>[^\:@/]+)(\:(?P<port>[0-9]+))?/(?P<db>[^\?]+)(\?sslmode=(?P<sslmode>.+))?$').match(uri) + if not m: + raise SyntaxError, "Invalid URI string in DAL" + user = credential_decoder(m.group('user')) + if not user: + raise SyntaxError, 'User required' + password = credential_decoder(m.group('password')) + if not password: + password = '' + host = m.group('host') + if not host: + raise SyntaxError, 'Host name required' + db = m.group('db') + if not db: + raise SyntaxError, 'Database name required' + port = m.group('port') or '5432' + sslmode = m.group('sslmode') + if sslmode: + msg = ("dbname='%s' user='%s' host='%s'" + "port=%s password='%s' sslmode='%s'") \ + % (db, user, host, port, password, sslmode) + else: + msg = ("dbname='%s' user='%s' host='%s'" + "port=%s password='%s'") \ + % (db, user, host, port, password) + def connect(msg=msg,driver_args=driver_args): + return self.driver.connect(msg,**driver_args) + self.pool_connection(connect) + self.connection.set_client_encoding('UTF8') + self.cursor = self.connection.cursor() + self.execute('BEGIN;') + self.execute("SET CLIENT_ENCODING TO 'UNICODE';") + self.execute("SET standard_conforming_strings=on;") + + def lastrowid(self,table): + self.execute("select currval('%s')" % table._sequence_name) + return int(self.cursor.fetchone()[0]) + + def LIKE(self,first,second): + return '(%s ILIKE %s)' % (self.expand(first),self.expand(second,'string')) + + def STARTSWITH(self,first,second): + return '(%s ILIKE %s)' % (self.expand(first),self.expand(second+'%','string')) + + def ENDSWITH(self,first,second): + return '(%s ILIKE %s)' % (self.expand(first),self.expand('%'+second,'string')) + + def CONTAINS(self,first,second): + if first.type in ('string','text'): + key = '%'+str(second).replace('%','%%')+'%' + elif first.type.startswith('list:'): + key = '%|'+str(second).replace('|','||').replace('%','%%')+'|%' + return '(%s ILIKE %s)' % (self.expand(first),self.expand(key,'string')) + +class JDBCPostgreSQLAdapter(PostgreSQLAdapter): + + def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8', + credential_decoder=lambda x:x, driver_args={}, + adapter_args={}): + self.db = db + self.dbengine = "postgres" + self.uri = uri + self.pool_size = pool_size + self.folder = folder + self.db_codec = db_codec + self.find_or_make_work_folder() + uri = uri.split('://')[1] + m = re.compile('^(?P<user>[^:@]+)(\:(?P<password>[^@]*))?@(?P<host>[^\:/]+)(\:(?P<port>[0-9]+))?/(?P<db>.+)$').match(uri) + if not m: + raise SyntaxError, "Invalid URI string in DAL" + user = credential_decoder(m.group('user')) + if not user: + raise SyntaxError, 'User required' + password = credential_decoder(m.group('password')) + if not password: + password = '' + host = m.group('host') + if not host: + raise SyntaxError, 'Host name required' + db = m.group('db') + if not db: + raise SyntaxError, 'Database name required' + port = m.group('port') or '5432' + msg = ('jdbc:postgresql://%s:%s/%s' % (host, port, db), user, password) + def connect(msg=msg,driver_args=driver_args): + return self.driver.connect(*msg,**driver_args) + self.pool_connection(connect) + self.connection.set_client_encoding('UTF8') + self.cursor = self.connection.cursor() + self.execute('BEGIN;') + self.execute("SET CLIENT_ENCODING TO 'UNICODE';") + + +class OracleAdapter(BaseAdapter): + + driver = globals().get('cx_Oracle',None) + + commit_on_alter_table = False + types = { + 'boolean': 'CHAR(1)', + 'string': 'VARCHAR2(%(length)s)', + 'text': 'CLOB', + 'password': 'VARCHAR2(%(length)s)', + 'blob': 'CLOB', + 'upload': 'VARCHAR2(%(length)s)', + 'integer': 'INT', + 'double': 'FLOAT', + 'decimal': 'NUMERIC(%(precision)s,%(scale)s)', + 'date': 'DATE', + 'time': 'CHAR(8)', + 'datetime': 'DATE', + 'id': 'NUMBER PRIMARY KEY', + 'reference': 'NUMBER, CONSTRAINT %(constraint_name)s FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s', + 'list:integer': 'CLOB', + 'list:string': 'CLOB', + 'list:reference': 'CLOB', + } + + def sequence_name(self,tablename): + return '%s_sequence' % tablename + + def trigger_name(self,tablename): + return '%s_trigger' % tablename + + def LEFT_JOIN(self): + return 'LEFT OUTER JOIN' + + def RANDOM(self): + return 'dbms_random.value' + + def NOT_NULL(self,default,field_type): + return 'DEFAULT %s NOT NULL' % self.represent(default,field_type) + + def _drop(self,table,mode): + sequence_name = table._sequence_name + return ['DROP TABLE %s %s;' % (table, mode), 'DROP SEQUENCE %s;' % sequence_name] + + def select_limitby(self, sql_s, sql_f, sql_t, sql_w, sql_o, limitby): + if limitby: + (lmin, lmax) = limitby + if len(sql_w) > 1: + sql_w_row = sql_w + ' AND w_row > %i' % lmin + else: + sql_w_row = 'WHERE w_row > %i' % lmin + return 'SELECT %s %s FROM (SELECT w_tmp.*, ROWNUM w_row FROM (SELECT %s FROM %s%s%s) w_tmp WHERE ROWNUM<=%i) %s %s %s;' % (sql_s, sql_f, sql_f, sql_t, sql_w, sql_o, lmax, sql_t, sql_w_row, sql_o) + return 'SELECT %s %s FROM %s%s%s;' % (sql_s, sql_f, sql_t, sql_w, sql_o) + + def constraint_name(self, tablename, fieldname): + constraint_name = BaseAdapter.constraint_name(self, tablename, fieldname) + if len(constraint_name)>30: + constraint_name = '%s_%s__constraint' % (tablename[:10], fieldname[:7]) + return constraint_name + + def represent_exceptions(self, obj, fieldtype): + if fieldtype == 'blob': + obj = base64.b64encode(str(obj)) + return ":CLOB('%s')" % obj + elif fieldtype == 'date': + if isinstance(obj, (datetime.date, datetime.datetime)): + obj = obj.isoformat()[:10] + else: + obj = str(obj) + return "to_date('%s','yyyy-mm-dd')" % obj + elif fieldtype == 'datetime': + if isinstance(obj, datetime.datetime): + obj = obj.isoformat()[:19].replace('T',' ') + elif isinstance(obj, datetime.date): + obj = obj.isoformat()[:10]+' 00:00:00' + else: + obj = str(obj) + return "to_date('%s','yyyy-mm-dd hh24:mi:ss')" % obj + return None + + def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8', + credential_decoder=lambda x:x, driver_args={}, + adapter_args={}): + self.db = db + self.dbengine = "oracle" + self.uri = uri + self.pool_size = pool_size + self.folder = folder + self.db_codec = db_codec + self.find_or_make_work_folder() + uri = uri.split('://')[1] + if not 'threaded' in driver_args: + driver_args['threaded']=True + def connect(uri=uri,driver_args=driver_args): + return self.driver.connect(uri,**driver_args) + self.pool_connection(connect) + self.cursor = self.connection.cursor() + self.execute("ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS';") + self.execute("ALTER SESSION SET NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS';") + oracle_fix = re.compile("[^']*('[^']*'[^']*)*\:(?P<clob>CLOB\('([^']+|'')*'\))") + + def execute(self, command): + args = [] + i = 1 + while True: + m = self.oracle_fix.match(command) + if not m: + break + command = command[:m.start('clob')] + str(i) + command[m.end('clob'):] + args.append(m.group('clob')[6:-2].replace("''", "'")) + i += 1 + return self.log_execute(command[:-1], args) + + def create_sequence_and_triggers(self, query, table, **args): + tablename = table._tablename + sequence_name = table._sequence_name + trigger_name = table._trigger_name + self.execute(query) + self.execute('CREATE SEQUENCE %s START WITH 1 INCREMENT BY 1 NOMAXVALUE;' % sequence_name) + self.execute('CREATE OR REPLACE TRIGGER %s BEFORE INSERT ON %s FOR EACH ROW BEGIN SELECT %s.nextval INTO :NEW.id FROM DUAL; END;\n' % (trigger_name, tablename, sequence_name)) + + def lastrowid(self,table): + sequence_name = table._sequence_name + self.execute('SELECT %s.currval FROM dual;' % sequence_name) + return int(self.cursor.fetchone()[0]) + + +class MSSQLAdapter(BaseAdapter): + + driver = globals().get('pyodbc',None) + + types = { + 'boolean': 'BIT', + 'string': 'VARCHAR(%(length)s)', + 'text': 'TEXT', + 'password': 'VARCHAR(%(length)s)', + 'blob': 'IMAGE', + 'upload': 'VARCHAR(%(length)s)', + 'integer': 'INT', + 'double': 'FLOAT', + 'decimal': 'NUMERIC(%(precision)s,%(scale)s)', + 'date': 'DATETIME', + 'time': 'CHAR(8)', + 'datetime': 'DATETIME', + 'id': 'INT IDENTITY PRIMARY KEY', + 'reference': 'INT NULL, CONSTRAINT %(constraint_name)s FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s', + 'reference FK': ', CONSTRAINT FK_%(constraint_name)s FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s', + 'reference TFK': ' CONSTRAINT FK_%(foreign_table)s_PK FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_table)s (%(foreign_key)s) ON DELETE %(on_delete_action)s', + 'list:integer': 'TEXT', + 'list:string': 'TEXT', + 'list:reference': 'TEXT', + } + + def EXTRACT(self,field,what): + return "DATEPART(%s,%s)" % (what, self.expand(field)) + + def LEFT_JOIN(self): + return 'LEFT OUTER JOIN' + + def RANDOM(self): + return 'NEWID()' + + def ALLOW_NULL(self): + return ' NULL' + + def SUBSTRING(self,field,parameters): + return 'SUBSTRING(%s,%s,%s)' % (self.expand(field), parameters[0], parameters[1]) + + def PRIMARY_KEY(self,key): + return 'PRIMARY KEY CLUSTERED (%s)' % key + + def select_limitby(self, sql_s, sql_f, sql_t, sql_w, sql_o, limitby): + if limitby: + (lmin, lmax) = limitby + sql_s += ' TOP %i' % lmax + return 'SELECT %s %s FROM %s%s%s;' % (sql_s, sql_f, sql_t, sql_w, sql_o) + + def represent_exceptions(self, obj, fieldtype): + if fieldtype == 'boolean': + if obj and not str(obj)[0].upper() == 'F': + return '1' + else: + return '0' + return None + + def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8', + credential_decoder=lambda x:x, driver_args={}, + adapter_args={}, fake_connect=False): + self.db = db + self.dbengine = "mssql" + self.uri = uri + self.pool_size = pool_size + self.folder = folder + self.db_codec = db_codec + self.find_or_make_work_folder() + # ## read: http://bytes.com/groups/python/460325-cx_oracle-utf8 + uri = uri.split('://')[1] + if '@' not in uri: + try: + m = re.compile('^(?P<dsn>.+)$').match(uri) + if not m: + raise SyntaxError, \ + 'Parsing uri string(%s) has no result' % self.uri + dsn = m.group('dsn') + if not dsn: + raise SyntaxError, 'DSN required' + except SyntaxError, e: + logger.error('NdGpatch error') + raise e + cnxn = 'DSN=%s' % dsn + else: + m = re.compile('^(?P<user>[^:@]+)(\:(?P<password>[^@]*))?@(?P<host>[^\:/]+)(\:(?P<port>[0-9]+))?/(?P<db>[^\?]+)(\?(?P<urlargs>.*))?$').match(uri) + if not m: + raise SyntaxError, \ + "Invalid URI string in DAL: %s" % uri + user = credential_decoder(m.group('user')) + if not user: + raise SyntaxError, 'User required' + password = credential_decoder(m.group('password')) + if not password: + password = '' + host = m.group('host') + if not host: + raise SyntaxError, 'Host name required' + db = m.group('db') + if not db: + raise SyntaxError, 'Database name required' + port = m.group('port') or '1433' + # Parse the optional url name-value arg pairs after the '?' + # (in the form of arg1=value1&arg2=value2&...) + # Default values (drivers like FreeTDS insist on uppercase parameter keys) + argsdict = { 'DRIVER':'{SQL Server}' } + urlargs = m.group('urlargs') or '' + argpattern = re.compile('(?P<argkey>[^=]+)=(?P<argvalue>[^&]*)') + for argmatch in argpattern.finditer(urlargs): + argsdict[str(argmatch.group('argkey')).upper()] = argmatch.group('argvalue') + urlargs = ';'.join(['%s=%s' % (ak, av) for (ak, av) in argsdict.items()]) + cnxn = 'SERVER=%s;PORT=%s;DATABASE=%s;UID=%s;PWD=%s;%s' \ + % (host, port, db, user, password, urlargs) + def connect(cnxn=cnxn,driver_args=driver_args): + return self.driver.connect(cnxn,**driver_args) + if not fake_connect: + self.pool_connection(connect) + self.cursor = self.connection.cursor() + + def lastrowid(self,table): + #self.execute('SELECT @@IDENTITY;') + self.execute('SELECT SCOPE_IDENTITY();') + return int(self.cursor.fetchone()[0]) + + def integrity_error_class(self): + return pyodbc.IntegrityError + + def rowslice(self,rows,minimum=0,maximum=None): + if maximum is None: + return rows[minimum:] + return rows[minimum:maximum] + + +class MSSQL2Adapter(MSSQLAdapter): + types = { + 'boolean': 'CHAR(1)', + 'string': 'NVARCHAR(%(length)s)', + 'text': 'NTEXT', + 'password': 'NVARCHAR(%(length)s)', + 'blob': 'IMAGE', + 'upload': 'NVARCHAR(%(length)s)', + 'integer': 'INT', + 'double': 'FLOAT', + 'decimal': 'NUMERIC(%(precision)s,%(scale)s)', + 'date': 'DATETIME', + 'time': 'CHAR(8)', + 'datetime': 'DATETIME', + 'id': 'INT IDENTITY PRIMARY KEY', + 'reference': 'INT, CONSTRAINT %(constraint_name)s FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s', + 'reference FK': ', CONSTRAINT FK_%(constraint_name)s FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s', + 'reference TFK': ' CONSTRAINT FK_%(foreign_table)s_PK FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_table)s (%(foreign_key)s) ON DELETE %(on_delete_action)s', + 'list:integer': 'NTEXT', + 'list:string': 'NTEXT', + 'list:reference': 'NTEXT', + } + + def represent(self, obj, fieldtype): + value = BaseAdapter.represent(self, obj, fieldtype) + if (fieldtype == 'string' or fieldtype == 'text') and value[:1]=="'": + value = 'N'+value + return value + + def execute(self,a): + return self.log_execute(a.decode('utf8')) + + +class FireBirdAdapter(BaseAdapter): + + driver = globals().get('pyodbc',None) + + commit_on_alter_table = False + support_distributed_transaction = True + types = { + 'boolean': 'CHAR(1)', + 'string': 'VARCHAR(%(length)s)', + 'text': 'BLOB SUB_TYPE 1', + 'password': 'VARCHAR(%(length)s)', + 'blob': 'BLOB SUB_TYPE 0', + 'upload': 'VARCHAR(%(length)s)', + 'integer': 'INTEGER', + 'double': 'DOUBLE PRECISION', + 'decimal': 'DECIMAL(%(precision)s,%(scale)s)', + 'date': 'DATE', + 'time': 'TIME', + 'datetime': 'TIMESTAMP', + 'id': 'INTEGER PRIMARY KEY', + 'reference': 'INTEGER REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s', + 'list:integer': 'BLOB SUB_TYPE 1', + 'list:string': 'BLOB SUB_TYPE 1', + 'list:reference': 'BLOB SUB_TYPE 1', + } + + def sequence_name(self,tablename): + return 'genid_%s' % tablename + + def trigger_name(self,tablename): + return 'trg_id_%s' % tablename + + def RANDOM(self): + return 'RAND()' + + def NOT_NULL(self,default,field_type): + return 'DEFAULT %s NOT NULL' % self.represent(default,field_type) + + def SUBSTRING(self,field,parameters): + return 'SUBSTRING(%s from %s for %s)' % (self.expand(field), parameters[0], parameters[1]) + + def _drop(self,table,mode): + sequence_name = table._sequence_name + return ['DROP TABLE %s %s;' % (table, mode), 'DROP GENERATOR %s;' % sequence_name] + + def select_limitby(self, sql_s, sql_f, sql_t, sql_w, sql_o, limitby): + if limitby: + (lmin, lmax) = limitby + sql_s += ' FIRST %i SKIP %i' % (lmax - lmin, lmin) + return 'SELECT %s %s FROM %s%s%s;' % (sql_s, sql_f, sql_t, sql_w, sql_o) + + def _truncate(self,table,mode = ''): + return ['DELETE FROM %s;' % table._tablename, + 'SET GENERATOR %s TO 0;' % table._sequence_name] + + def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8', + credential_decoder=lambda x:x, driver_args={}, + adapter_args={}): + self.db = db + self.dbengine = "firebird" + self.uri = uri + self.pool_size = pool_size + self.folder = folder + self.db_codec = db_codec + self.find_or_make_work_folder() + uri = uri.split('://')[1] + m = re.compile('^(?P<user>[^:@]+)(\:(?P<password>[^@]*))?@(?P<host>[^\:/]+)(\:(?P<port>[0-9]+))?/(?P<db>.+?)(\?set_encoding=(?P<charset>\w+))?$').match(uri) + if not m: + raise SyntaxError, "Invalid URI string in DAL: %s" % uri + user = credential_decoder(m.group('user')) + if not user: + raise SyntaxError, 'User required' + password = credential_decoder(m.group('password')) + if not password: + password = '' + host = m.group('host') + if not host: + raise SyntaxError, 'Host name required' + port = int(m.group('port') or 3050) + db = m.group('db') + if not db: + raise SyntaxError, 'Database name required' + charset = m.group('charset') or 'UTF8' + driver_args.update(dict(dsn='%s/%s:%s' % (host,port,db), + user = credential_decoder(user), + password = credential_decoder(password), + charset = charset)) + if adapter_args.has_key('driver_name'): + if adapter_args['driver_name'] == 'kinterbasdb': + self.driver = kinterbasdb + elif adapter_args['driver_name'] == 'firebirdsql': + self.driver = firebirdsql + else: + self.driver = kinterbasdb + def connect(driver_args=driver_args): + return self.driver.connect(**driver_args) + self.pool_connection(connect) + self.cursor = self.connection.cursor() + + def create_sequence_and_triggers(self, query, table, **args): + tablename = table._tablename + sequence_name = table._sequence_name + trigger_name = table._trigger_name + self.execute(query) + self.execute('create generator %s;' % sequence_name) + self.execute('set generator %s to 0;' % sequence_name) + self.execute('create trigger %s for %s active before insert position 0 as\nbegin\nif(new.id is null) then\nbegin\nnew.id = gen_id(%s, 1);\nend\nend;' % (trigger_name, tablename, sequence_name)) + + def lastrowid(self,table): + sequence_name = table._sequence_name + self.execute('SELECT gen_id(%s, 0) FROM rdb$database' % sequence_name) + return int(self.cursor.fetchone()[0]) + + +class FireBirdEmbeddedAdapter(FireBirdAdapter): + + def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8', + credential_decoder=lambda x:x, driver_args={}, + adapter_args={}): + self.db = db + self.dbengine = "firebird" + self.uri = uri + self.pool_size = pool_size + self.folder = folder + self.db_codec = db_codec + self.find_or_make_work_folder() + uri = uri.split('://')[1] + m = re.compile('^(?P<user>[^:@]+)(\:(?P<password>[^@]*))?@(?P<path>[^\?]+)(\?set_encoding=(?P<charset>\w+))?$').match(uri) + if not m: + raise SyntaxError, \ + "Invalid URI string in DAL: %s" % self.uri + user = credential_decoder(m.group('user')) + if not user: + raise SyntaxError, 'User required' + password = credential_decoder(m.group('password')) + if not password: + password = '' + pathdb = m.group('path') + if not pathdb: + raise SyntaxError, 'Path required' + charset = m.group('charset') + if not charset: + charset = 'UTF8' + host = '' + driver_args.update(dict(host=host, + database=pathdb, + user=credential_decoder(user), + password=credential_decoder(password), + charset=charset)) + #def connect(driver_args=driver_args): + # return kinterbasdb.connect(**driver_args) + if adapter_args.has_key('driver_name'): + if adapter_args['driver_name'] == 'kinterbasdb': + self.driver = kinterbasdb + elif adapter_args['driver_name'] == 'firebirdsql': + self.driver = firebirdsql + else: + self.driver = kinterbasdb + def connect(driver_args=driver_args): + return self.driver.connect(**driver_args) + self.pool_connection(connect) + self.cursor = self.connection.cursor() + + +class InformixAdapter(BaseAdapter): + + driver = globals().get('informixdb',None) + + types = { + 'boolean': 'CHAR(1)', + 'string': 'VARCHAR(%(length)s)', + 'text': 'BLOB SUB_TYPE 1', + 'password': 'VARCHAR(%(length)s)', + 'blob': 'BLOB SUB_TYPE 0', + 'upload': 'VARCHAR(%(length)s)', + 'integer': 'INTEGER', + 'double': 'FLOAT', + 'decimal': 'NUMERIC(%(precision)s,%(scale)s)', + 'date': 'DATE', + 'time': 'CHAR(8)', + 'datetime': 'DATETIME', + 'id': 'SERIAL', + 'reference': 'INTEGER REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s', + 'reference FK': 'REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s CONSTRAINT FK_%(table_name)s_%(field_name)s', + 'reference TFK': 'FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_table)s (%(foreign_key)s) ON DELETE %(on_delete_action)s CONSTRAINT TFK_%(table_name)s_%(field_name)s', + 'list:integer': 'BLOB SUB_TYPE 1', + 'list:string': 'BLOB SUB_TYPE 1', + 'list:reference': 'BLOB SUB_TYPE 1', + } + + def RANDOM(self): + return 'Random()' + + def NOT_NULL(self,default,field_type): + return 'DEFAULT %s NOT NULL' % self.represent(default,field_type) + + def select_limitby(self, sql_s, sql_f, sql_t, sql_w, sql_o, limitby): + if limitby: + (lmin, lmax) = limitby + fetch_amt = lmax - lmin + dbms_version = int(self.connection.dbms_version.split('.')[0]) + if lmin and (dbms_version >= 10): + # Requires Informix 10.0+ + sql_s += ' SKIP %d' % (lmin, ) + if fetch_amt and (dbms_version >= 9): + # Requires Informix 9.0+ + sql_s += ' FIRST %d' % (fetch_amt, ) + return 'SELECT %s %s FROM %s%s%s;' % (sql_s, sql_f, sql_t, sql_w, sql_o) + + def represent_exceptions(self, obj, fieldtype): + if fieldtype == 'date': + if isinstance(obj, (datetime.date, datetime.datetime)): + obj = obj.isoformat()[:10] + else: + obj = str(obj) + return "to_date('%s','yyyy-mm-dd')" % obj + elif fieldtype == 'datetime': + if isinstance(obj, datetime.datetime): + obj = obj.isoformat()[:19].replace('T',' ') + elif isinstance(obj, datetime.date): + obj = obj.isoformat()[:10]+' 00:00:00' + else: + obj = str(obj) + return "to_date('%s','yyyy-mm-dd hh24:mi:ss')" % obj + return None + + def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8', + credential_decoder=lambda x:x, driver_args={}, + adapter_args={}): + self.db = db + self.dbengine = "informix" + self.uri = uri + self.pool_size = pool_size + self.folder = folder + self.db_codec = db_codec + self.find_or_make_work_folder() + uri = uri.split('://')[1] + m = re.compile('^(?P<user>[^:@]+)(\:(?P<password>[^@]*))?@(?P<host>[^\:/]+)(\:(?P<port>[0-9]+))?/(?P<db>.+)$').match(uri) + if not m: + raise SyntaxError, \ + "Invalid URI string in DAL: %s" % self.uri + user = credential_decoder(m.group('user')) + if not user: + raise SyntaxError, 'User required' + password = credential_decoder(m.group('password')) + if not password: + password = '' + host = m.group('host') + if not host: + raise SyntaxError, 'Host name required' + db = m.group('db') + if not db: + raise SyntaxError, 'Database name required' + user = credential_decoder(user) + password = credential_decoder(password) + dsn = '%s@%s' % (db,host) + driver_args.update(dict(user=user,password=password,autocommit=True)) + def connect(dsn=dsn,driver_args=driver_args): + return self.driver.connect(dsn,**driver_args) + self.pool_connection(connect) + self.cursor = self.connection.cursor() + + def execute(self,command): + if command[-1:]==';': + command = command[:-1] + return self.log_execute(command) + + def lastrowid(self,table): + return self.cursor.sqlerrd[1] + + def integrity_error_class(self): + return informixdb.IntegrityError + + +class DB2Adapter(BaseAdapter): + + driver = globals().get('pyodbc',None) + + types = { + 'boolean': 'CHAR(1)', + 'string': 'VARCHAR(%(length)s)', + 'text': 'CLOB', + 'password': 'VARCHAR(%(length)s)', + 'blob': 'BLOB', + 'upload': 'VARCHAR(%(length)s)', + 'integer': 'INT', + 'double': 'DOUBLE', + 'decimal': 'NUMERIC(%(precision)s,%(scale)s)', + 'date': 'DATE', + 'time': 'TIME', + 'datetime': 'TIMESTAMP', + 'id': 'INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL', + 'reference': 'INT, FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s', + 'reference FK': ', CONSTRAINT FK_%(constraint_name)s FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s', + 'reference TFK': ' CONSTRAINT FK_%(foreign_table)s_PK FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_table)s (%(foreign_key)s) ON DELETE %(on_delete_action)s', + 'list:integer': 'CLOB', + 'list:string': 'CLOB', + 'list:reference': 'CLOB', + } + + def LEFT_JOIN(self): + return 'LEFT OUTER JOIN' + + def RANDOM(self): + return 'RAND()' + + def select_limitby(self, sql_s, sql_f, sql_t, sql_w, sql_o, limitby): + if limitby: + (lmin, lmax) = limitby + sql_o += ' FETCH FIRST %i ROWS ONLY' % lmax + return 'SELECT %s %s FROM %s%s%s;' % (sql_s, sql_f, sql_t, sql_w, sql_o) + + def represent_exceptions(self, obj, fieldtype): + if fieldtype == 'blob': + obj = base64.b64encode(str(obj)) + return "BLOB('%s')" % obj + elif fieldtype == 'datetime': + if isinstance(obj, datetime.datetime): + obj = obj.isoformat()[:19].replace('T','-').replace(':','.') + elif isinstance(obj, datetime.date): + obj = obj.isoformat()[:10]+'-00.00.00' + return "'%s'" % obj + return None + + def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8', + credential_decoder=lambda x:x, driver_args={}, + adapter_args={}): + self.db = db + self.dbengine = "db2" + self.uri = uri + self.pool_size = pool_size + self.folder = folder + self.db_codec = db_codec + self.find_or_make_work_folder() + cnxn = uri.split('://', 1)[1] + def connect(cnxn=cnxn,driver_args=driver_args): + return self.driver.connect(cnxn,**driver_args) + self.pool_connection(connect) + self.cursor = self.connection.cursor() + + def execute(self,command): + if command[-1:]==';': + command = command[:-1] + return self.log_execute(command) + + def lastrowid(self,table): + self.execute('SELECT DISTINCT IDENTITY_VAL_LOCAL() FROM %s;' % table) + return int(self.cursor.fetchone()[0]) + + def rowslice(self,rows,minimum=0,maximum=None): + if maximum is None: + return rows[minimum:] + return rows[minimum:maximum] + + +class TeradataAdapter(DB2Adapter): + + driver = globals().get('pyodbc',None) + + types = { + 'boolean': 'CHAR(1)', + 'string': 'VARCHAR(%(length)s)', + 'text': 'CLOB', + 'password': 'VARCHAR(%(length)s)', + 'blob': 'BLOB', + 'upload': 'VARCHAR(%(length)s)', + 'integer': 'INT', + 'double': 'DOUBLE', + 'decimal': 'NUMERIC(%(precision)s,%(scale)s)', + 'date': 'DATE', + 'time': 'TIME', + 'datetime': 'TIMESTAMP', + 'id': 'INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL', + 'reference': 'INT, FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s', + 'reference FK': ', CONSTRAINT FK_%(constraint_name)s FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s', + 'reference TFK': ' CONSTRAINT FK_%(foreign_table)s_PK FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_table)s (%(foreign_key)s) ON DELETE %(on_delete_action)s', + 'list:integer': 'CLOB', + 'list:string': 'CLOB', + 'list:reference': 'CLOB', + } + + + def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8', + credential_decoder=lambda x:x, driver_args={}, + adapter_args={}): + self.db = db + self.dbengine = "teradata" + self.uri = uri + self.pool_size = pool_size + self.folder = folder + self.db_codec = db_codec + self.find_or_make_work_folder() + cnxn = uri.split('://', 1)[1] + def connect(cnxn=cnxn,driver_args=driver_args): + return self.driver.connect(cnxn,**driver_args) + self.pool_connection(connect) + self.cursor = self.connection.cursor() + + +INGRES_SEQNAME='ii***lineitemsequence' # NOTE invalid database object name + # (ANSI-SQL wants this form of name + # to be a delimited identifier) + +class IngresAdapter(BaseAdapter): + + driver = globals().get('ingresdbi',None) + + types = { + 'boolean': 'CHAR(1)', + 'string': 'VARCHAR(%(length)s)', + 'text': 'CLOB', + 'password': 'VARCHAR(%(length)s)', ## Not sure what this contains utf8 or nvarchar. Or even bytes? + 'blob': 'BLOB', + 'upload': 'VARCHAR(%(length)s)', ## FIXME utf8 or nvarchar... or blob? what is this type? + 'integer': 'INTEGER4', # or int8... + 'double': 'FLOAT8', + 'decimal': 'NUMERIC(%(precision)s,%(scale)s)', + 'date': 'ANSIDATE', + 'time': 'TIME WITHOUT TIME ZONE', + 'datetime': 'TIMESTAMP WITHOUT TIME ZONE', + 'id': 'integer4 not null unique with default next value for %s' % INGRES_SEQNAME, + 'reference': 'integer4, FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s', + 'reference FK': ', CONSTRAINT FK_%(constraint_name)s FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s', + 'reference TFK': ' CONSTRAINT FK_%(foreign_table)s_PK FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_table)s (%(foreign_key)s) ON DELETE %(on_delete_action)s', ## FIXME TODO + 'list:integer': 'CLOB', + 'list:string': 'CLOB', + 'list:reference': 'CLOB', + } + + def LEFT_JOIN(self): + return 'LEFT OUTER JOIN' + + def RANDOM(self): + return 'RANDOM()' + + def select_limitby(self, sql_s, sql_f, sql_t, sql_w, sql_o, limitby): + if limitby: + (lmin, lmax) = limitby + fetch_amt = lmax - lmin + if fetch_amt: + sql_s += ' FIRST %d ' % (fetch_amt, ) + if lmin: + # Requires Ingres 9.2+ + sql_o += ' OFFSET %d' % (lmin, ) + return 'SELECT %s %s FROM %s%s%s;' % (sql_s, sql_f, sql_t, sql_w, sql_o) + + def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8', + credential_decoder=lambda x:x, driver_args={}, + adapter_args={}): + self.db = db + self.dbengine = "ingres" + self.uri = uri + self.pool_size = pool_size + self.folder = folder + self.db_codec = db_codec + self.find_or_make_work_folder() + connstr = self._uri.split(':', 1)[1] + # Simple URI processing + connstr = connstr.lstrip() + while connstr.startswith('/'): + connstr = connstr[1:] + database_name=connstr # Assume only (local) dbname is passed in + vnode = '(local)' + servertype = 'ingres' + trace = (0, None) # No tracing + driver_args.update(dict(database=database_name, + vnode=vnode, + servertype=servertype, + trace=trace)) + def connect(driver_args=driver_args): + return self.driver.connect(**driver_args) + self.pool_connection(connect) + self.cursor = self.connection.cursor() + + def create_sequence_and_triggers(self, query, table, **args): + # post create table auto inc code (if needed) + # modify table to btree for performance.... + # Older Ingres releases could use rule/trigger like Oracle above. + if hasattr(table,'_primarykey'): + modify_tbl_sql = 'modify %s to btree unique on %s' % \ + (table._tablename, + ', '.join(["'%s'" % x for x in table.primarykey])) + self.execute(modify_tbl_sql) + else: + tmp_seqname='%s_iisq' % table._tablename + query=query.replace(INGRES_SEQNAME, tmp_seqname) + self.execute('create sequence %s' % tmp_seqname) + self.execute(query) + self.execute('modify %s to btree unique on %s' % (table._tablename, 'id')) + + + def lastrowid(self,table): + tmp_seqname='%s_iisq' % table + self.execute('select current value for %s' % tmp_seqname) + return int(self.cursor.fetchone()[0]) # don't really need int type cast here... + + def integrity_error_class(self): + return ingresdbi.IntegrityError + + +class IngresUnicodeAdapter(IngresAdapter): + types = { + 'boolean': 'CHAR(1)', + 'string': 'NVARCHAR(%(length)s)', + 'text': 'NCLOB', + 'password': 'NVARCHAR(%(length)s)', ## Not sure what this contains utf8 or nvarchar. Or even bytes? + 'blob': 'BLOB', + 'upload': 'VARCHAR(%(length)s)', ## FIXME utf8 or nvarchar... or blob? what is this type? + 'integer': 'INTEGER4', # or int8... + 'double': 'FLOAT8', + 'decimal': 'NUMERIC(%(precision)s,%(scale)s)', + 'date': 'ANSIDATE', + 'time': 'TIME WITHOUT TIME ZONE', + 'datetime': 'TIMESTAMP WITHOUT TIME ZONE', + 'id': 'integer4 not null unique with default next value for %s'% INGRES_SEQNAME, + 'reference': 'integer4, FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s', + 'reference FK': ', CONSTRAINT FK_%(constraint_name)s FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s', + 'reference TFK': ' CONSTRAINT FK_%(foreign_table)s_PK FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_table)s (%(foreign_key)s) ON DELETE %(on_delete_action)s', ## FIXME TODO + 'list:integer': 'NCLOB', + 'list:string': 'NCLOB', + 'list:reference': 'NCLOB', + } + +class SAPDBAdapter(BaseAdapter): + + driver = globals().get('sapdb',None) + support_distributed_transaction = False + types = { + 'boolean': 'CHAR(1)', + 'string': 'VARCHAR(%(length)s)', + 'text': 'LONG', + 'password': 'VARCHAR(%(length)s)', + 'blob': 'LONG', + 'upload': 'VARCHAR(%(length)s)', + 'integer': 'INT', + 'double': 'FLOAT', + 'decimal': 'FIXED(%(precision)s,%(scale)s)', + 'date': 'DATE', + 'time': 'TIME', + 'datetime': 'TIMESTAMP', + 'id': 'INT PRIMARY KEY', + 'reference': 'INT, FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s', + 'list:integer': 'LONG', + 'list:string': 'LONG', + 'list:reference': 'LONG', + } + + def sequence_name(self,table): + return '%s_id_Seq' % table + + def select_limitby(self, sql_s, sql_f, sql_t, sql_w, sql_o, limitby): + if limitby: + (lmin, lmax) = limitby + if len(sql_w) > 1: + sql_w_row = sql_w + ' AND w_row > %i' % lmin + else: + sql_w_row = 'WHERE w_row > %i' % lmin + return '%s %s FROM (SELECT w_tmp.*, ROWNO w_row FROM (SELECT %s FROM %s%s%s) w_tmp WHERE ROWNO=%i) %s %s %s;' % (sql_s, sql_f, sql_f, sql_t, sql_w, sql_o, lmax, sql_t, sql_w_row, sql_o) + return 'SELECT %s %s FROM %s%s%s;' % (sql_s, sql_f, sql_t, sql_w, sql_o) + + def create_sequence_and_triggers(self, query, table, **args): + # following lines should only be executed if table._sequence_name does not exist + self.execute('CREATE SEQUENCE %s;' % table._sequence_name) + self.execute("ALTER TABLE %s ALTER COLUMN %s SET DEFAULT NEXTVAL('%s');" \ + % (table._tablename, table._id.name, table._sequence_name)) + self.execute(query) + + def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8', + credential_decoder=lambda x:x, driver_args={}, + adapter_args={}): + self.db = db + self.dbengine = "sapdb" + self.uri = uri + self.pool_size = pool_size + self.folder = folder + self.db_codec = db_codec + self.find_or_make_work_folder() + uri = uri.split('://')[1] + m = re.compile('^(?P<user>[^:@]+)(\:(?P<password>[^@]*))?@(?P<host>[^\:@/]+)(\:(?P<port>[0-9]+))?/(?P<db>[^\?]+)(\?sslmode=(?P<sslmode>.+))?$').match(uri) + if not m: + raise SyntaxError, "Invalid URI string in DAL" + user = credential_decoder(m.group('user')) + if not user: + raise SyntaxError, 'User required' + password = credential_decoder(m.group('password')) + if not password: + password = '' + host = m.group('host') + if not host: + raise SyntaxError, 'Host name required' + db = m.group('db') + if not db: + raise SyntaxError, 'Database name required' + def connect(user=user,password=password,database=db, + host=host,driver_args=driver_args): + return self.driver.Connection(user,password,database, + host,**driver_args) + self.pool_connection(connect) + # self.connection.set_client_encoding('UTF8') + self.cursor = self.connection.cursor() + + def lastrowid(self,table): + self.execute("select %s.NEXTVAL from dual" % table._sequence_name) + return int(self.cursor.fetchone()[0]) + +class CubridAdapter(MySQLAdapter): + + driver = globals().get('cubriddb',None) + + def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8', + credential_decoder=lambda x:x, driver_args={}, + adapter_args={}): + self.db = db + self.dbengine = "cubrid" + self.uri = uri + self.pool_size = pool_size + self.folder = folder + self.db_codec = db_codec + self.find_or_make_work_folder() + uri = uri.split('://')[1] + m = re.compile('^(?P<user>[^:@]+)(\:(?P<password>[^@]*))?@(?P<host>[^\:/]+)(\:(?P<port>[0-9]+))?/(?P<db>[^?]+)(\?set_encoding=(?P<charset>\w+))?$').match(uri) + if not m: + raise SyntaxError, \ + "Invalid URI string in DAL: %s" % self.uri + user = credential_decoder(m.group('user')) + if not user: + raise SyntaxError, 'User required' + password = credential_decoder(m.group('password')) + if not password: + password = '' + host = m.group('host') + if not host: + raise SyntaxError, 'Host name required' + db = m.group('db') + if not db: + raise SyntaxError, 'Database name required' + port = int(m.group('port') or '30000') + charset = m.group('charset') or 'utf8' + user=credential_decoder(user), + passwd=credential_decoder(password), + def connect(host,port,db,user,passwd,driver_args=driver_args): + return self.driver.connect(host,port,db,user,passwd,**driver_args) + self.pool_connection(connect) + self.cursor = self.connection.cursor() + self.execute('SET FOREIGN_KEY_CHECKS=1;') + self.execute("SET sql_mode='NO_BACKSLASH_ESCAPES';") + + +######## GAE MySQL ########## + +class DatabaseStoredFile: + + web2py_filesystem = False + + def __init__(self,db,filename,mode): + if db._adapter.dbengine != 'mysql': + raise RuntimeError, "only MySQL can store metadata .table files in database for now" + self.db = db + self.filename = filename + self.mode = mode + if not self.web2py_filesystem: + self.db.executesql("CREATE TABLE IF NOT EXISTS web2py_filesystem (path VARCHAR(512), content LONGTEXT, PRIMARY KEY(path) ) ENGINE=InnoDB;") + DatabaseStoredFile.web2py_filesystem = True + self.p=0 + self.data = '' + if mode in ('r','rw','a'): + query = "SELECT content FROM web2py_filesystem WHERE path='%s'" % filename + rows = self.db.executesql(query) + if rows: + self.data = rows[0][0] + elif os.path.exists(filename): + datafile = open(filename, 'r') + try: + self.data = datafile.read() + finally: + datafile.close() + elif mode in ('r','rw'): + raise RuntimeError, "File %s does not exist" % filename + + def read(self, bytes): + data = self.data[self.p:self.p+bytes] + self.p += len(data) + return data + + def readline(self): + i = self.data.find('\n',self.p)+1 + if i>0: + data, self.p = self.data[self.p:i], i + else: + data, self.p = self.data[self.p:], len(self.data) + return data + + def write(self,data): + self.data += data + + def close(self): + self.db.executesql("DELETE FROM web2py_filesystem WHERE path='%s'" % self.filename) + query = "INSERT INTO web2py_filesystem(path,content) VALUES ('%s','%s')" % \ + (self.filename, self.data.replace("'","''")) + self.db.executesql(query) + self.db.commit() + + @staticmethod + def exists(db,filename): + if os.path.exists(filename): + return True + query = "SELECT path FROM web2py_filesystem WHERE path='%s'" % filename + if db.executesql(query): + return True + return False + + +class UseDatabaseStoredFile: + + def file_exists(self, filename): + return DatabaseStoredFile.exists(self.db,filename) + + def file_open(self, filename, mode='rb', lock=True): + return DatabaseStoredFile(self.db,filename,mode) + + def file_close(self, fileobj, unlock=True): + fileobj.close() + + def file_delete(self,filename): + query = "DELETE FROM web2py_filesystem WHERE path='%s'" % filename + self.db.executesql(query) + self.db.commit() + +class GoogleSQLAdapter(UseDatabaseStoredFile,MySQLAdapter): + + def __init__(self, db, uri='google:sql://realm:domain/database', pool_size=0, + folder=None, db_codec='UTF-8', check_reserved=None, + migrate=True, fake_migrate=False, + credential_decoder = lambda x:x, driver_args={}, + adapter_args={}): + + self.db = db + self.dbengine = "mysql" + self.uri = uri + self.pool_size = pool_size + self.folder = folder + self.db_codec = db_codec + self.folder = folder or '$HOME/'+thread.folder.split('/applications/',1)[1] + + m = re.compile('^(?P<instance>.*)/(?P<db>.*)$').match(self.uri[len('google:sql://'):]) + if not m: + raise SyntaxError, "Invalid URI string in SQLDB: %s" % self._uri + instance = credential_decoder(m.group('instance')) + db = credential_decoder(m.group('db')) + driver_args['instance'] = instance + if not migrate: + driver_args['database'] = db + def connect(driver_args=driver_args): + return rdbms.connect(**driver_args) + self.pool_connection(connect) + self.cursor = self.connection.cursor() + if migrate: + # self.execute('DROP DATABASE %s' % db) + self.execute('CREATE DATABASE IF NOT EXISTS %s' % db) + self.execute('USE %s' % db) + self.execute("SET FOREIGN_KEY_CHECKS=1;") + self.execute("SET sql_mode='NO_BACKSLASH_ESCAPES';") + +class NoSQLAdapter(BaseAdapter): + + @staticmethod + def to_unicode(obj): + if isinstance(obj, str): + return obj.decode('utf8') + elif not isinstance(obj, unicode): + return unicode(obj) + return obj + + def represent(self, obj, fieldtype): + if isinstance(obj,CALLABLETYPES): + obj = obj() + if isinstance(fieldtype, SQLCustomType): + return fieldtype.encoder(obj) + if isinstance(obj, (Expression, Field)): + raise SyntaxError, "non supported on GAE" + if self.dbengine=='google:datastore' in globals(): + if isinstance(fieldtype, gae.Property): + return obj + if fieldtype.startswith('list:'): + if not obj: + obj = [] + if not isinstance(obj, (list, tuple)): + obj = [obj] + if obj == '' and not fieldtype[:2] in ['st','te','pa','up']: + return None + if obj != None: + if isinstance(obj, list) and not fieldtype.startswith('list'): + obj = [self.represent(o, fieldtype) for o in obj] + elif fieldtype in ('integer','id'): + obj = long(obj) + elif fieldtype == 'double': + obj = float(obj) + elif fieldtype.startswith('reference'): + if isinstance(obj, (Row, Reference)): + obj = obj['id'] + obj = long(obj) + elif fieldtype == 'boolean': + if obj and not str(obj)[0].upper() == 'F': + obj = True + else: + obj = False + elif fieldtype == 'date': + if not isinstance(obj, datetime.date): + (y, m, d) = map(int,str(obj).strip().split('-')) + obj = datetime.date(y, m, d) + elif isinstance(obj,datetime.datetime): + (y, m, d) = (obj.year, obj.month, obj.day) + obj = datetime.date(y, m, d) + elif fieldtype == 'time': + if not isinstance(obj, datetime.time): + time_items = map(int,str(obj).strip().split(':')[:3]) + if len(time_items) == 3: + (h, mi, s) = time_items + else: + (h, mi, s) = time_items + [0] + obj = datetime.time(h, mi, s) + elif fieldtype == 'datetime': + if not isinstance(obj, datetime.datetime): + (y, m, d) = map(int,str(obj)[:10].strip().split('-')) + time_items = map(int,str(obj)[11:].strip().split(':')[:3]) + while len(time_items)<3: + time_items.append(0) + (h, mi, s) = time_items + obj = datetime.datetime(y, m, d, h, mi, s) + elif fieldtype == 'blob': + pass + elif fieldtype.startswith('list:string'): + return map(self.to_unicode,obj) + elif fieldtype.startswith('list:'): + return map(int,obj) + else: + obj = self.to_unicode(obj) + return obj + + def _insert(self,table,fields): + return 'insert %s in %s' % (fields, table) + + def _count(self,query,distinct=None): + return 'count %s' % repr(query) + + def _select(self,query,fields,attributes): + return 'select %s where %s' % (repr(fields), repr(query)) + + def _delete(self,tablename, query): + return 'delete %s where %s' % (repr(tablename),repr(query)) + + def _update(self,tablename,query,fields): + return 'update %s (%s) where %s' % (repr(tablename), + repr(fields),repr(query)) + + def commit(self): + """ + remember: no transactions on many NoSQL + """ + pass + + def rollback(self): + """ + remember: no transactions on many NoSQL + """ + pass + + def close(self): + """ + remember: no transactions on many NoSQL + """ + pass + + + # these functions should never be called! + def OR(self,first,second): raise SyntaxError, "Not supported" + def AND(self,first,second): raise SyntaxError, "Not supported" + def AS(self,first,second): raise SyntaxError, "Not supported" + def ON(self,first,second): raise SyntaxError, "Not supported" + def STARTSWITH(self,first,second=None): raise SyntaxError, "Not supported" + def ENDSWITH(self,first,second=None): raise SyntaxError, "Not supported" + def ADD(self,first,second): raise SyntaxError, "Not supported" + def SUB(self,first,second): raise SyntaxError, "Not supported" + def MUL(self,first,second): raise SyntaxError, "Not supported" + def DIV(self,first,second): raise SyntaxError, "Not supported" + def LOWER(self,first): raise SyntaxError, "Not supported" + def UPPER(self,first): raise SyntaxError, "Not supported" + def EXTRACT(self,first,what): raise SyntaxError, "Not supported" + def AGGREGATE(self,first,what): raise SyntaxError, "Not supported" + def LEFT_JOIN(self): raise SyntaxError, "Not supported" + def RANDOM(self): raise SyntaxError, "Not supported" + def SUBSTRING(self,field,parameters): raise SyntaxError, "Not supported" + def PRIMARY_KEY(self,key): raise SyntaxError, "Not supported" + def LIKE(self,first,second): raise SyntaxError, "Not supported" + def drop(self,table,mode): raise SyntaxError, "Not supported" + def alias(self,table,alias): raise SyntaxError, "Not supported" + def migrate_table(self,*a,**b): raise SyntaxError, "Not supported" + def distributed_transaction_begin(self,key): raise SyntaxError, "Not supported" + def prepare(self,key): raise SyntaxError, "Not supported" + def commit_prepared(self,key): raise SyntaxError, "Not supported" + def rollback_prepared(self,key): raise SyntaxError, "Not supported" + def concat_add(self,table): raise SyntaxError, "Not supported" + def constraint_name(self, table, fieldname): raise SyntaxError, "Not supported" + def create_sequence_and_triggers(self, query, table, **args): pass + def log_execute(self,*a,**b): raise SyntaxError, "Not supported" + def execute(self,*a,**b): raise SyntaxError, "Not supported" + def represent_exceptions(self, obj, fieldtype): raise SyntaxError, "Not supported" + def lastrowid(self,table): raise SyntaxError, "Not supported" + def integrity_error_class(self): raise SyntaxError, "Not supported" + def rowslice(self,rows,minimum=0,maximum=None): raise SyntaxError, "Not supported" + + +class GAEF(object): + def __init__(self,name,op,value,apply): + self.name=name=='id' and '__key__' or name + self.op=op + self.value=value + self.apply=apply + def __repr__(self): + return '(%s %s %s:%s)' % (self.name, self.op, repr(self.value), type(self.value)) + +class GoogleDatastoreAdapter(NoSQLAdapter): + uploads_in_blob = True + types = {} + + def file_exists(self, filename): pass + def file_open(self, filename, mode='rb', lock=True): pass + def file_close(self, fileobj, unlock=True): pass + + def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8', + credential_decoder=lambda x:x, driver_args={}, + adapter_args={}): + self.types.update({ + 'boolean': gae.BooleanProperty, + 'string': (lambda: gae.StringProperty(multiline=True)), + 'text': gae.TextProperty, + 'password': gae.StringProperty, + 'blob': gae.BlobProperty, + 'upload': gae.StringProperty, + 'integer': gae.IntegerProperty, + 'double': gae.FloatProperty, + 'decimal': GAEDecimalProperty, + 'date': gae.DateProperty, + 'time': gae.TimeProperty, + 'datetime': gae.DateTimeProperty, + 'id': None, + 'reference': gae.IntegerProperty, + 'list:string': (lambda: gae.StringListProperty(default=None)), + 'list:integer': (lambda: gae.ListProperty(int,default=None)), + 'list:reference': (lambda: gae.ListProperty(int,default=None)), + }) + self.db = db + self.uri = uri + self.dbengine = 'google:datastore' + self.folder = folder + db['_lastsql'] = '' + self.db_codec = 'UTF-8' + self.pool_size = 0 + match = re.compile('.*://(?P<namespace>.+)').match(uri) + if match: + namespace_manager.set_namespace(match.group('namespace')) + + def create_table(self,table,migrate=True,fake_migrate=False, polymodel=None): + myfields = {} + for k in table.fields: + if isinstance(polymodel,Table) and k in polymodel.fields(): + continue + field = table[k] + attr = {} + if isinstance(field.type, SQLCustomType): + ftype = self.types[field.type.native or field.type.type](**attr) + elif isinstance(field.type, gae.Property): + ftype = field.type + elif field.type.startswith('id'): + continue + elif field.type.startswith('decimal'): + precision, scale = field.type[7:].strip('()').split(',') + precision = int(precision) + scale = int(scale) + ftype = GAEDecimalProperty(precision, scale, **attr) + elif field.type.startswith('reference'): + if field.notnull: + attr = dict(required=True) + referenced = field.type[10:].strip() + ftype = self.types[field.type[:9]](table._db[referenced]) + elif field.type.startswith('list:reference'): + if field.notnull: + attr = dict(required=True) + referenced = field.type[15:].strip() + ftype = self.types[field.type[:14]](**attr) + elif field.type.startswith('list:'): + ftype = self.types[field.type](**attr) + elif not field.type in self.types\ + or not self.types[field.type]: + raise SyntaxError, 'Field: unknown field type: %s' % field.type + else: + ftype = self.types[field.type](**attr) + myfields[field.name] = ftype + if not polymodel: + table._tableobj = classobj(table._tablename, (gae.Model, ), myfields) + elif polymodel==True: + table._tableobj = classobj(table._tablename, (PolyModel, ), myfields) + elif isinstance(polymodel,Table): + table._tableobj = classobj(table._tablename, (polymodel._tableobj, ), myfields) + else: + raise SyntaxError, "polymodel must be None, True, a table or a tablename" + return None + + def expand(self,expression,field_type=None): + if isinstance(expression,Field): + if expression.type in ('text','blob'): + raise SyntaxError, 'AppEngine does not index by: %s' % expression.type + return expression.name + elif isinstance(expression, (Expression, Query)): + if not expression.second is None: + return expression.op(expression.first, expression.second) + elif not expression.first is None: + return expression.op(expression.first) + else: + return expression.op() + elif field_type: + return self.represent(expression,field_type) + elif isinstance(expression,(list,tuple)): + return ','.join([self.represent(item,field_type) for item in expression]) + else: + return str(expression) + + ### TODO from gql.py Expression + def AND(self,first,second): + a = self.expand(first) + b = self.expand(second) + if b[0].name=='__key__' and a[0].name!='__key__': + return b+a + return a+b + + def EQ(self,first,second=None): + if isinstance(second, Key): + return [GAEF(first.name,'=',second,lambda a,b:a==b)] + return [GAEF(first.name,'=',self.represent(second,first.type),lambda a,b:a==b)] + + def NE(self,first,second=None): + if first.type != 'id': + return [GAEF(first.name,'!=',self.represent(second,first.type),lambda a,b:a!=b)] + else: + second = Key.from_path(first._tablename, long(second)) + return [GAEF(first.name,'!=',second,lambda a,b:a!=b)] + + def LT(self,first,second=None): + if first.type != 'id': + return [GAEF(first.name,'<',self.represent(second,first.type),lambda a,b:a<b)] + else: + second = Key.from_path(first._tablename, long(second)) + return [GAEF(first.name,'<',second,lambda a,b:a<b)] + + def LE(self,first,second=None): + if first.type != 'id': + return [GAEF(first.name,'<=',self.represent(second,first.type),lambda a,b:a<=b)] + else: + second = Key.from_path(first._tablename, long(second)) + return [GAEF(first.name,'<=',second,lambda a,b:a<=b)] + + def GT(self,first,second=None): + if first.type != 'id' or second==0 or second == '0': + return [GAEF(first.name,'>',self.represent(second,first.type),lambda a,b:a>b)] + else: + second = Key.from_path(first._tablename, long(second)) + return [GAEF(first.name,'>',second,lambda a,b:a>b)] + + def GE(self,first,second=None): + if first.type != 'id': + return [GAEF(first.name,'>=',self.represent(second,first.type),lambda a,b:a>=b)] + else: + second = Key.from_path(first._tablename, long(second)) + return [GAEF(first.name,'>=',second,lambda a,b:a>=b)] + + def INVERT(self,first): + return '-%s' % first.name + + def COMMA(self,first,second): + return '%s, %s' % (self.expand(first),self.expand(second)) + + def BELONGS(self,first,second=None): + if not isinstance(second,(list, tuple)): + raise SyntaxError, "Not supported" + if first.type != 'id': + return [GAEF(first.name,'in',self.represent(second,first.type),lambda a,b:a in b)] + else: + second = [Key.from_path(first._tablename, i) for i in second] + return [GAEF(first.name,'in',second,lambda a,b:a in b)] + + def CONTAINS(self,first,second): + if not first.type.startswith('list:'): + raise SyntaxError, "Not supported" + return [GAEF(first.name,'=',self.expand(second,first.type[5:]),lambda a,b:a in b)] + + def NOT(self,first): + nops = { self.EQ: self.NE, + self.NE: self.EQ, + self.LT: self.GE, + self.GT: self.LE, + self.LE: self.GT, + self.GE: self.LT} + if not isinstance(first,Query): + raise SyntaxError, "Not suported" + nop = nops.get(first.op,None) + if not nop: + raise SyntaxError, "Not suported %s" % first.op.__name__ + first.op = nop + return self.expand(first) + + def truncate(self,table,mode): + self.db(table._id > 0).delete() + + def select_raw(self,query,fields=[],attributes={}): + new_fields = [] + for item in fields: + if isinstance(item,SQLALL): + new_fields += item.table + else: + new_fields.append(item) + fields = new_fields + if query: + tablename = self.get_table(query) + elif fields: + tablename = fields[0].tablename + query = fields[0].table._id>0 + else: + raise SyntaxError, "Unable to determine a tablename" + query = self.filter_tenant(query,[tablename]) + tableobj = self.db[tablename]._tableobj + items = tableobj.all() + filters = self.expand(query) + for filter in filters: + if filter.name=='__key__' and filter.op=='>' and filter.value==0: + continue + elif filter.name=='__key__' and filter.op=='=': + if filter.value==0: + items = [] + elif isinstance(filter.value, Key): + item = tableobj.get(filter.value) + items = (item and [item]) or [] + else: + item = tableobj.get_by_id(filter.value) + items = (item and [item]) or [] + elif isinstance(items,list): # i.e. there is a single record! + items = [i for i in items if filter.apply(getattr(item,filter.name), + filter.value)] + else: + if filter.name=='__key__': items.order('__key__') + items = items.filter('%s %s' % (filter.name,filter.op),filter.value) + if not isinstance(items,list): + if attributes.get('left', None): + raise SyntaxError, 'Set: no left join in appengine' + if attributes.get('groupby', None): + raise SyntaxError, 'Set: no groupby in appengine' + orderby = attributes.get('orderby', False) + if orderby: + ### THIS REALLY NEEDS IMPROVEMENT !!! + if isinstance(orderby, (list, tuple)): + orderby = xorify(orderby) + if isinstance(orderby,Expression): + orderby = self.expand(orderby) + orders = orderby.split(', ') + for order in orders: + order={'-id':'-__key__','id':'__key__'}.get(order,order) + items = items.order(order) + if attributes.get('limitby', None): + (lmin, lmax) = attributes['limitby'] + (limit, offset) = (lmax - lmin, lmin) + items = items.fetch(limit, offset=offset) + fields = self.db[tablename].fields + return (items, tablename, fields) + + def select(self,query,fields,attributes): + (items, tablename, fields) = self.select_raw(query,fields,attributes) + # self.db['_lastsql'] = self._select(query,fields,attributes) + rows = [ + [t=='id' and int(item.key().id()) or getattr(item, t) for t in fields] + for item in items] + colnames = ['%s.%s' % (tablename, t) for t in fields] + return self.parse(rows, colnames, False) + + + def count(self,query,distinct=None): + if distinct: + raise RuntimeError, "COUNT DISTINCT not supported" + (items, tablename, fields) = self.select_raw(query) + # self.db['_lastsql'] = self._count(query) + try: + return len(items) + except TypeError: + return items.count(limit=None) + + def delete(self,tablename, query): + """ + This function was changed on 2010-05-04 because according to + http://code.google.com/p/googleappengine/issues/detail?id=3119 + GAE no longer support deleting more than 1000 records. + """ + # self.db['_lastsql'] = self._delete(tablename,query) + (items, tablename, fields) = self.select_raw(query) + # items can be one item or a query + if not isinstance(items,list): + counter = items.count(limit=None) + leftitems = items.fetch(1000) + while len(leftitems): + gae.delete(leftitems) + leftitems = items.fetch(1000) + else: + counter = len(items) + gae.delete(items) + return counter + + def update(self,tablename,query,update_fields): + # self.db['_lastsql'] = self._update(tablename,query,update_fields) + (items, tablename, fields) = self.select_raw(query) + counter = 0 + for item in items: + for field, value in update_fields: + setattr(item, field.name, self.represent(value,field.type)) + item.put() + counter += 1 + logger.info(str(counter)) + return counter + + def insert(self,table,fields): + dfields=dict((f.name,self.represent(v,f.type)) for f,v in fields) + # table._db['_lastsql'] = self._insert(table,fields) + tmp = table._tableobj(**dfields) + tmp.put() + rid = Reference(tmp.key().id()) + (rid._table, rid._record) = (table, None) + return rid + + def bulk_insert(self,table,items): + parsed_items = [] + for item in items: + dfields=dict((f.name,self.represent(v,f.type)) for f,v in item) + parsed_items.append(table._tableobj(**dfields)) + gae.put(parsed_items) + return True + +def uuid2int(uuidv): + return uuid.UUID(uuidv).int + +def int2uuid(n): + return str(uuid.UUID(int=n)) + +class CouchDBAdapter(NoSQLAdapter): + uploads_in_blob = True + types = { + 'boolean': bool, + 'string': str, + 'text': str, + 'password': str, + 'blob': str, + 'upload': str, + 'integer': long, + 'double': float, + 'date': datetime.date, + 'time': datetime.time, + 'datetime': datetime.datetime, + 'id': long, + 'reference': long, + 'list:string': list, + 'list:integer': list, + 'list:reference': list, + } + + def file_exists(self, filename): pass + def file_open(self, filename, mode='rb', lock=True): pass + def file_close(self, fileobj, unlock=True): pass + + def expand(self,expression,field_type=None): + if isinstance(expression,Field): + if expression.type=='id': + return "%s._id" % expression.tablename + return BaseAdapter.expand(self,expression,field_type) + + def AND(self,first,second): + return '(%s && %s)' % (self.expand(first),self.expand(second)) + + def OR(self,first,second): + return '(%s || %s)' % (self.expand(first),self.expand(second)) + + def EQ(self,first,second): + if second is None: + return '(%s == null)' % self.expand(first) + return '(%s == %s)' % (self.expand(first),self.expand(second,first.type)) + + def NE(self,first,second): + if second is None: + return '(%s != null)' % self.expand(first) + return '(%s != %s)' % (self.expand(first),self.expand(second,first.type)) + + def COMMA(self,first,second): + return '%s + %s' % (self.expand(first),self.expand(second)) + + def represent(self, obj, fieldtype): + value = NoSQLAdapter.represent(self, obj, fieldtype) + if fieldtype=='id': + return repr(str(int(value))) + return repr(not isinstance(value,unicode) and value or value.encode('utf8')) + + def __init__(self,db,uri='couchdb://127.0.0.1:5984', + pool_size=0,folder=None,db_codec ='UTF-8', + credential_decoder=lambda x:x, driver_args={}, + adapter_args={}): + self.db = db + self.uri = uri + self.dbengine = 'couchdb' + self.folder = folder + db['_lastsql'] = '' + self.db_codec = 'UTF-8' + self.pool_size = pool_size + + url='http://'+uri[10:] + def connect(url=url,driver_args=driver_args): + return couchdb.Server(url,**driver_args) + self.pool_connection(connect) + + def create_table(self, table, migrate=True, fake_migrate=False, polymodel=None): + if migrate: + try: + self.connection.create(table._tablename) + except: + pass + + def insert(self,table,fields): + id = uuid2int(web2py_uuid()) + ctable = self.connection[table._tablename] + values = dict((k.name,NoSQLAdapter.represent(self,v,k.type)) for k,v in fields) + values['_id'] = str(id) + ctable.save(values) + return id + + def _select(self,query,fields,attributes): + if not isinstance(query,Query): + raise SyntaxError, "Not Supported" + for key in set(attributes.keys())-set(('orderby','groupby','limitby', + 'required','cache','left', + 'distinct','having')): + raise SyntaxError, 'invalid select attribute: %s' % key + new_fields=[] + for item in fields: + if isinstance(item,SQLALL): + new_fields += item.table + else: + new_fields.append(item) + def uid(fd): + return fd=='id' and '_id' or fd + def get(row,fd): + return fd=='id' and int(row['_id']) or row.get(fd,None) + fields = new_fields + tablename = self.get_table(query) + fieldnames = [f.name for f in (fields or self.db[tablename])] + colnames = ['%s.%s' % (tablename,k) for k in fieldnames] + fields = ','.join(['%s.%s' % (tablename,uid(f)) for f in fieldnames]) + fn="function(%(t)s){if(%(query)s)emit(%(order)s,[%(fields)s]);}" %\ + dict(t=tablename, + query=self.expand(query), + order='%s._id' % tablename, + fields=fields) + return fn, colnames + + def select(self,query,fields,attributes): + if not isinstance(query,Query): + raise SyntaxError, "Not Supported" + fn, colnames = self._select(query,fields,attributes) + tablename = colnames[0].split('.')[0] + ctable = self.connection[tablename] + rows = [cols['value'] for cols in ctable.query(fn)] + return self.parse(rows, colnames, False) + + def delete(self,tablename,query): + if not isinstance(query,Query): + raise SyntaxError, "Not Supported" + if query.first.type=='id' and query.op==self.EQ: + id = query.second + tablename = query.first.tablename + assert(tablename == query.first.tablename) + ctable = self.connection[tablename] + try: + del ctable[str(id)] + return 1 + except couchdb.http.ResourceNotFound: + return 0 + else: + tablename = self.get_table(query) + rows = self.select(query,[self.db[tablename]._id],{}) + ctable = self.connection[tablename] + for row in rows: + del ctable[str(row.id)] + return len(rows) + + def update(self,tablename,query,fields): + if not isinstance(query,Query): + raise SyntaxError, "Not Supported" + if query.first.type=='id' and query.op==self.EQ: + id = query.second + tablename = query.first.tablename + ctable = self.connection[tablename] + try: + doc = ctable[str(id)] + for key,value in fields: + doc[key.name] = NoSQLAdapter.represent(self,value,self.db[tablename][key.name].type) + ctable.save(doc) + return 1 + except couchdb.http.ResourceNotFound: + return 0 + else: + tablename = self.get_table(query) + rows = self.select(query,[self.db[tablename]._id],{}) + ctable = self.connection[tablename] + table = self.db[tablename] + for row in rows: + doc = ctable[str(row.id)] + for key,value in fields: + doc[key.name] = NoSQLAdapter.represent(self,value,table[key.name].type) + ctable.save(doc) + return len(rows) + + def count(self,query,distinct=None): + if distinct: + raise RuntimeError, "COUNT DISTINCT not supported" + if not isinstance(query,Query): + raise SyntaxError, "Not Supported" + tablename = self.get_table(query) + rows = self.select(query,[self.db[tablename]._id],{}) + return len(rows) + +def cleanup(text): + """ + validates that the given text is clean: only contains [0-9a-zA-Z_] + """ + + if re.compile('[^0-9a-zA-Z_]').findall(text): + raise SyntaxError, \ + 'only [0-9a-zA-Z_] allowed in table and field names, received %s' \ + % text + return text + + +class MongoDBAdapter(NoSQLAdapter): + uploads_in_blob = True + types = { + 'boolean': bool, + 'string': str, + 'text': str, + 'password': str, + 'blob': str, + 'upload': str, + 'integer': long, + 'double': float, + 'date': datetime.date, + 'time': datetime.time, + 'datetime': datetime.datetime, + 'id': long, + 'reference': long, + 'list:string': list, + 'list:integer': list, + 'list:reference': list, + } + + def __init__(self,db,uri='mongodb://127.0.0.1:5984/db', + pool_size=0,folder=None,db_codec ='UTF-8', + credential_decoder=lambda x:x, driver_args={}, + adapter_args={}): + self.db = db + self.uri = uri + self.dbengine = 'mongodb' + self.folder = folder + db['_lastsql'] = '' + self.db_codec = 'UTF-8' + self.pool_size = pool_size + + m = re.compile('^(?P<host>[^\:/]+)(\:(?P<port>[0-9]+))?/(?P<db>.+)$').match(self._uri[10:]) + if not m: + raise SyntaxError, "Invalid URI string in DAL: %s" % self._uri + host = m.group('host') + if not host: + raise SyntaxError, 'mongodb: host name required' + dbname = m.group('db') + if not dbname: + raise SyntaxError, 'mongodb: db name required' + port = m.group('port') or 27017 + driver_args.update(dict(host=host,port=port)) + def connect(dbname=dbname,driver_args=driver_args): + return pymongo.Connection(**driver_args)[dbname] + self.pool_connection(connect) + + def insert(self,table,fields): + ctable = self.connection[table._tablename] + values = dict((k,self.represent(v,table[k].type)) for k,v in fields) + ctable.insert(values) + return uuid2int(id) + + + def count(self,query): + raise RuntimeError, "Not implemented" + + def select(self,query,fields,attributes): + raise RuntimeError, "Not implemented" + + def delete(self,tablename, query): + raise RuntimeError, "Not implemented" + + def update(self,tablename,query,fields): + raise RuntimeError, "Not implemented" + + +######################################################################## +# end of adapters +######################################################################## + +ADAPTERS = { + 'sqlite': SQLiteAdapter, + 'sqlite:memory': SQLiteAdapter, + 'mysql': MySQLAdapter, + 'postgres': PostgreSQLAdapter, + 'oracle': OracleAdapter, + 'mssql': MSSQLAdapter, + 'mssql2': MSSQL2Adapter, + 'db2': DB2Adapter, + 'teradata': TeradataAdapter, + 'informix': InformixAdapter, + 'firebird': FireBirdAdapter, + 'firebird_embedded': FireBirdAdapter, + 'ingres': IngresAdapter, + 'ingresu': IngresUnicodeAdapter, + 'sapdb': SAPDBAdapter, + 'cubrid': CubridAdapter, + 'jdbc:sqlite': JDBCSQLiteAdapter, + 'jdbc:sqlite:memory': JDBCSQLiteAdapter, + 'jdbc:postgres': JDBCPostgreSQLAdapter, + 'gae': GoogleDatastoreAdapter, # discouraged, for backward compatibility + 'google:datastore': GoogleDatastoreAdapter, + 'google:sql': GoogleSQLAdapter, + 'couchdb': CouchDBAdapter, + 'mongodb': MongoDBAdapter, +} + + +def sqlhtml_validators(field): + """ + Field type validation, using web2py's validators mechanism. + + makes sure the content of a field is in line with the declared + fieldtype + """ + if not have_validators: + return [] + field_type, field_length = field.type, field.length + if isinstance(field_type, SQLCustomType): + if hasattr(field_type, 'validator'): + return field_type.validator + else: + field_type = field_type.type + elif not isinstance(field_type,str): + return [] + requires=[] + def ff(r,id): + row=r(id) + if not row: + return id + elif hasattr(r, '_format') and isinstance(r._format,str): + return r._format % row + elif hasattr(r, '_format') and callable(r._format): + return r._format(row) + else: + return id + if field_type == 'string': + requires.append(validators.IS_LENGTH(field_length)) + elif field_type == 'text': + requires.append(validators.IS_LENGTH(field_length)) + elif field_type == 'password': + requires.append(validators.IS_LENGTH(field_length)) + elif field_type == 'double': + requires.append(validators.IS_FLOAT_IN_RANGE(-1e100, 1e100)) + elif field_type == 'integer': + requires.append(validators.IS_INT_IN_RANGE(-1e100, 1e100)) + elif field_type.startswith('decimal'): + requires.append(validators.IS_DECIMAL_IN_RANGE(-10**10, 10**10)) + elif field_type == 'date': + requires.append(validators.IS_DATE()) + elif field_type == 'time': + requires.append(validators.IS_TIME()) + elif field_type == 'datetime': + requires.append(validators.IS_DATETIME()) + elif field.db and field_type.startswith('reference') and \ + field_type.find('.') < 0 and \ + field_type[10:] in field.db.tables: + referenced = field.db[field_type[10:]] + def repr_ref(id, r=referenced, f=ff): return f(r, id) + field.represent = field.represent or repr_ref + if hasattr(referenced, '_format') and referenced._format: + requires = validators.IS_IN_DB(field.db,referenced._id, + referenced._format) + if field.unique: + requires._and = validators.IS_NOT_IN_DB(field.db,field) + if field.tablename == field_type[10:]: + return validators.IS_EMPTY_OR(requires) + return requires + elif field.db and field_type.startswith('list:reference') and \ + field_type.find('.') < 0 and \ + field_type[15:] in field.db.tables: + referenced = field.db[field_type[15:]] + def list_ref_repr(ids, r=referenced, f=ff): + if not ids: + return None + refs = r._db(r._id.belongs(ids)).select(r._id) + return (refs and ', '.join(str(f(r,ref.id)) for ref in refs) or '') + field.represent = field.represent or list_ref_repr + if hasattr(referenced, '_format') and referenced._format: + requires = validators.IS_IN_DB(field.db,referenced._id, + referenced._format,multiple=True) + else: + requires = validators.IS_IN_DB(field.db,referenced._id, + multiple=True) + if field.unique: + requires._and = validators.IS_NOT_IN_DB(field.db,field) + return requires + elif field_type.startswith('list:'): + def repr_list(values): return', '.join(str(v) for v in (values or [])) + field.represent = field.represent or repr_list + if field.unique: + requires.insert(0,validators.IS_NOT_IN_DB(field.db,field)) + sff = ['in', 'do', 'da', 'ti', 'de', 'bo'] + if field.notnull and not field_type[:2] in sff: + requires.insert(0, validators.IS_NOT_EMPTY()) + elif not field.notnull and field_type[:2] in sff and requires: + requires[-1] = validators.IS_EMPTY_OR(requires[-1]) + return requires + + +def bar_escape(item): + return str(item).replace('|', '||') + +def bar_encode(items): + return '|%s|' % '|'.join(bar_escape(item) for item in items if str(item).strip()) + +def bar_decode_integer(value): + return [int(x) for x in value.split('|') if x.strip()] + +def bar_decode_string(value): + return [x.replace('||', '|') for x in string_unpack.split(value[1:-1]) if x.strip()] + + +class Row(dict): + + """ + a dictionary that lets you do d['a'] as well as d.a + this is only used to store a Row + """ + + def __getitem__(self, key): + key=str(key) + if key in self.get('_extra',{}): + return self._extra[key] + return dict.__getitem__(self, key) + + def __call__(self,key): + return self.__getitem__(key) + + def __setitem__(self, key, value): + dict.__setitem__(self, str(key), value) + + def __getattr__(self, key): + return self[key] + + def __setattr__(self, key, value): + self[key] = value + + def __repr__(self): + return '<Row ' + dict.__repr__(self) + '>' + + def __int__(self): + return dict.__getitem__(self,'id') + + def __eq__(self,other): + try: + return self.as_dict() == other.as_dict() + except AttributeError: + return False + + def __ne__(self,other): + return not (self == other) + + def __copy__(self): + return Row(dict(self)) + + def as_dict(self,datetime_to_str=False): + SERIALIZABLE_TYPES = (str,unicode,int,long,float,bool,list) + d = dict(self) + for k in copy.copy(d.keys()): + v=d[k] + if d[k] is None: + continue + elif isinstance(v,Row): + d[k]=v.as_dict() + elif isinstance(v,Reference): + d[k]=int(v) + elif isinstance(v,decimal.Decimal): + d[k]=float(v) + elif isinstance(v, (datetime.date, datetime.datetime, datetime.time)): + if datetime_to_str: + d[k] = v.isoformat().replace('T',' ')[:19] + elif not isinstance(v,SERIALIZABLE_TYPES): + del d[k] + return d + + +def Row_unpickler(data): + return Row(cPickle.loads(data)) + +def Row_pickler(data): + return Row_unpickler, (cPickle.dumps(data.as_dict(datetime_to_str=False)),) + +copy_reg.pickle(Row, Row_pickler, Row_unpickler) + + +################################################################################ +# Everything below should be independent on the specifics of the +# database and should for RDBMs and some NoSQL databases +################################################################################ + +class SQLCallableList(list): + def __call__(self): + return copy.copy(self) + + +class DAL(dict): + + """ + an instance of this class represents a database connection + + Example:: + + db = DAL('sqlite://test.db') + db.define_table('tablename', Field('fieldname1'), + Field('fieldname2')) + """ + + @staticmethod + def set_folder(folder): + """ + # ## this allows gluon to set a folder for this thread + # ## <<<<<<<<< Should go away as new DAL replaces old sql.py + """ + BaseAdapter.set_folder(folder) + + @staticmethod + def distributed_transaction_begin(*instances): + if not instances: + return + thread_key = '%s.%s' % (socket.gethostname(), threading.currentThread()) + keys = ['%s.%i' % (thread_key, i) for (i,db) in instances] + instances = enumerate(instances) + for (i, db) in instances: + if not db._adapter.support_distributed_transaction(): + raise SyntaxError, \ + 'distributed transaction not suported by %s' % db._dbname + for (i, db) in instances: + db._adapter.distributed_transaction_begin(keys[i]) + + @staticmethod + def distributed_transaction_commit(*instances): + if not instances: + return + instances = enumerate(instances) + thread_key = '%s.%s' % (socket.gethostname(), threading.currentThread()) + keys = ['%s.%i' % (thread_key, i) for (i,db) in instances] + for (i, db) in instances: + if not db._adapter.support_distributed_transaction(): + raise SyntaxError, \ + 'distributed transaction not suported by %s' % db._dbanme + try: + for (i, db) in instances: + db._adapter.prepare(keys[i]) + except: + for (i, db) in instances: + db._adapter.rollback_prepared(keys[i]) + raise RuntimeError, 'failure to commit distributed transaction' + else: + for (i, db) in instances: + db._adapter.commit_prepared(keys[i]) + return + + + def __init__(self, uri='sqlite://dummy.db', pool_size=0, folder=None, + db_codec='UTF-8', check_reserved=None, + migrate=True, fake_migrate=False, + migrate_enabled=True, fake_migrate_all=False, + decode_credentials=False, driver_args=None, + adapter_args={}, attempts=5, auto_import=False): + """ + Creates a new Database Abstraction Layer instance. + + Keyword arguments: + + :uri: string that contains information for connecting to a database. + (default: 'sqlite://dummy.db') + :pool_size: How many open connections to make to the database object. + :folder: <please update me> + :db_codec: string encoding of the database (default: 'UTF-8') + :check_reserved: list of adapters to check tablenames and column names + against sql reserved keywords. (Default None) + + * 'common' List of sql keywords that are common to all database types + such as "SELECT, INSERT". (recommended) + * 'all' Checks against all known SQL keywords. (not recommended) + <adaptername> Checks against the specific adapters list of keywords + (recommended) + * '<adaptername>_nonreserved' Checks against the specific adapters + list of nonreserved keywords. (if available) + :migrate (defaults to True) sets default migrate behavior for all tables + :fake_migrate (defaults to False) sets default fake_migrate behavior for all tables + :migrate_enabled (defaults to True). If set to False disables ALL migrations + :fake_migrate_all (defaults to False). If sets to True fake migrates ALL tables + :attempts (defaults to 5). Number of times to attempt connecting + """ + if not decode_credentials: + credential_decoder = lambda cred: cred + else: + credential_decoder = lambda cred: urllib.unquote(cred) + if folder: + self.set_folder(folder) + self._uri = uri + self._pool_size = pool_size + self._db_codec = db_codec + self._lastsql = '' + self._timings = [] + self._pending_references = {} + self._request_tenant = 'request_tenant' + self._common_fields = [] + if not str(attempts).isdigit() or attempts < 0: + attempts = 5 + if uri: + uris = isinstance(uri,(list,tuple)) and uri or [uri] + error = '' + connected = False + for k in range(attempts): + for uri in uris: + try: + if is_jdbc and not uri.startswith('jdbc:'): + uri = 'jdbc:'+uri + self._dbname = regex_dbname.match(uri).group() + if not self._dbname in ADAPTERS: + raise SyntaxError, "Error in URI '%s' or database not supported" % self._dbname + # notice that driver args or {} else driver_args defaults to {} global, not correct + args = (self,uri,pool_size,folder,db_codec,credential_decoder,driver_args or {}, adapter_args) + self._adapter = ADAPTERS[self._dbname](*args) + connected = True + break + except SyntaxError: + raise + except Exception, error: + sys.stderr.write('DEBUG_c: Exception %r' % ((Exception, error,),)) + if connected: + break + else: + time.sleep(1) + if not connected: + raise RuntimeError, "Failure to connect, tried %d times:\n%s" % (attempts, error) + else: + args = (self,'None',0,folder,db_codec) + self._adapter = BaseAdapter(*args) + migrate = fake_migrate = False + adapter = self._adapter + self._uri_hash = hashlib.md5(adapter.uri).hexdigest() + self.tables = SQLCallableList() + self.check_reserved = check_reserved + if self.check_reserved: + from reserved_sql_keywords import ADAPTERS as RSK + self.RSK = RSK + self._migrate = migrate + self._fake_migrate = fake_migrate + self._migrate_enabled = migrate_enabled + self._fake_migrate_all = fake_migrate_all + if auto_import: + self.import_table_definitions(adapter.folder) + + def import_table_definitions(self,path,migrate=False,fake_migrate=False): + pattern = os.path.join(path,self._uri_hash+'_*.table') + for filename in glob.glob(pattern): + tfile = self._adapter.file_open(filename, 'r') + try: + sql_fields = cPickle.load(tfile) + name = filename[len(pattern)-7:-6] + mf = [(value['sortable'],Field(key,type=value['type'])) \ + for key, value in sql_fields.items()] + mf.sort(lambda a,b: cmp(a[0],b[0])) + self.define_table(name,*[item[1] for item in mf], + **dict(migrate=migrate,fake_migrate=fake_migrate)) + finally: + self._adapter.file_close(tfile) + + def check_reserved_keyword(self, name): + """ + Validates ``name`` against SQL keywords + Uses self.check_reserve which is a list of + operators to use. + self.check_reserved + ['common', 'postgres', 'mysql'] + self.check_reserved + ['all'] + """ + for backend in self.check_reserved: + if name.upper() in self.RSK[backend]: + raise SyntaxError, 'invalid table/column name "%s" is a "%s" reserved SQL keyword' % (name, backend.upper()) + + def __contains__(self, tablename): + if self.has_key(tablename): + return True + else: + return False + + def parse_as_rest(self,patterns,args,vars,query=None,nested_select=True): + """ + EXAMPLE: + +db.define_table('person',Field('name'),Field('info')) +db.define_table('pet',Field('person',db.person),Field('name'),Field('info')) + +@request.restful() +def index(): + def GET(*kargs,**kvars): + patterns = [ + "/persons[person]", + "/{person.name.startswith}", + "/{person.name}/:field", + "/{person.name}/pets[pet.person]", + "/{person.name}/pet[pet.person]/{pet.name}", + "/{person.name}/pet[pet.person]/{pet.name}/:field" + ] + parser = db.parse_as_rest(patterns,kargs,kvars) + if parser.status == 200: + return dict(content=parser.response) + else: + raise HTTP(parser.status,parser.error) + def POST(table_name,**kvars): + if table_name == 'person': + return db.person.validate_and_insert(**kvars) + elif table_name == 'pet': + return db.pet.validate_and_insert(**kvars) + else: + raise HTTP(400) + return locals() + """ + + db = self + re1 = re.compile('^{[^\.]+\.[^\.]+(\.(lt|gt|le|ge|eq|ne|contains|startswith|year|month|day|hour|minute|second))?(\.not)?}$') + re2 = re.compile('^.+\[.+\]$') + + def auto_table(table,base='',depth=0): + patterns = [] + for field in db[table].fields: + if base: + tag = '%s/%s' % (base,field.replace('_','-')) + else: + tag = '/%s/%s' % (table.replace('_','-'),field.replace('_','-')) + f = db[table][field] + if not f.readable: continue + if f.type=='id' or 'slug' in field or f.type.startswith('reference'): + tag += '/{%s.%s}' % (table,field) + patterns.append(tag) + patterns.append(tag+'/:field') + elif f.type.startswith('boolean'): + tag += '/{%s.%s}' % (table,field) + patterns.append(tag) + patterns.append(tag+'/:field') + elif f.type.startswith('double') or f.type.startswith('integer'): + tag += '/{%s.%s.ge}/{%s.%s.lt}' % (table,field,table,field) + patterns.append(tag) + patterns.append(tag+'/:field') + elif f.type.startswith('list:'): + tag += '/{%s.%s.contains}' % (table,field) + patterns.append(tag) + patterns.append(tag+'/:field') + elif f.type in ('date','datetime'): + tag+= '/{%s.%s.year}' % (table,field) + patterns.append(tag) + patterns.append(tag+'/:field') + tag+='/{%s.%s.month}' % (table,field) + patterns.append(tag) + patterns.append(tag+'/:field') + tag+='/{%s.%s.day}' % (table,field) + patterns.append(tag) + patterns.append(tag+'/:field') + if f.type in ('datetime','time'): + tag+= '/{%s.%s.hour}' % (table,field) + patterns.append(tag) + patterns.append(tag+'/:field') + tag+='/{%s.%s.minute}' % (table,field) + patterns.append(tag) + patterns.append(tag+'/:field') + tag+='/{%s.%s.second}' % (table,field) + patterns.append(tag) + patterns.append(tag+'/:field') + if depth>0: + for rtable,rfield in db[table]._referenced_by: + tag+='/%s[%s.%s]' % (rtable,rtable,rfield) + patterns.append(tag) + patterns += auto_table(rtable,base=tag,depth=depth-1) + return patterns + + if patterns=='auto': + patterns=[] + for table in db.tables: + if not table.startswith('auth_'): + patterns += auto_table(table,base='',depth=1) + else: + i = 0 + while i<len(patterns): + pattern = patterns[i] + tokens = pattern.split('/') + if tokens[-1].startswith(':auto') and re2.match(tokens[-1]): + new_patterns = auto_table(tokens[-1][tokens[-1].find('[')+1:-1],'/'.join(tokens[:-1])) + patterns = patterns[:i]+new_patterns+patterns[i+1:] + i += len(new_patterns) + else: + i += 1 + if '/'.join(args) == 'patterns': + return Row({'status':200,'pattern':'list', + 'error':None,'response':patterns}) + for pattern in patterns: + otable=table=None + dbset=db(query) + i=0 + tags = pattern[1:].split('/') + # print pattern + if len(tags)!=len(args): + continue + for tag in tags: + # print i, tag, args[i] + if re1.match(tag): + # print 're1:'+tag + tokens = tag[1:-1].split('.') + table, field = tokens[0], tokens[1] + if not otable or table == otable: + if len(tokens)==2 or tokens[2]=='eq': + query = db[table][field]==args[i] + elif tokens[2]=='ne': + query = db[table][field]!=args[i] + elif tokens[2]=='lt': + query = db[table][field]<args[i] + elif tokens[2]=='gt': + query = db[table][field]>args[i] + elif tokens[2]=='ge': + query = db[table][field]>=args[i] + elif tokens[2]=='le': + query = db[table][field]<=args[i] + elif tokens[2]=='year': + query = db[table][field].year()==args[i] + elif tokens[2]=='month': + query = db[table][field].month()==args[i] + elif tokens[2]=='day': + query = db[table][field].day()==args[i] + elif tokens[2]=='hour': + query = db[table][field].hour()==args[i] + elif tokens[2]=='minute': + query = db[table][field].minutes()==args[i] + elif tokens[2]=='second': + query = db[table][field].seconds()==args[i] + elif tokens[2]=='startswith': + query = db[table][field].startswith(args[i]) + elif tokens[2]=='contains': + query = db[table][field].contains(args[i]) + else: + raise RuntimeError, "invalid pattern: %s" % pattern + if len(tokens)==4 and tokens[3]=='not': + query = ~query + elif len(tokens)>=4: + raise RuntimeError, "invalid pattern: %s" % pattern + dbset=dbset(query) + else: + raise RuntimeError, "missing relation in pattern: %s" % pattern + elif otable and re2.match(tag) and args[i]==tag[:tag.find('[')]: + # print 're2:'+tag + ref = tag[tag.find('[')+1:-1] + if '.' in ref: + table,field = ref.split('.') + # print table,field + if nested_select: + try: + dbset=db(db[table][field].belongs(dbset._select(db[otable]._id))) + except ValueError: + return Row({'status':400,'pattern':pattern, + 'error':'invalid path','response':None}) + else: + items = [item.id for item in dbset.select(db[otable]._id)] + dbset=db(db[table][field].belongs(items)) + else: + dbset=dbset(db[ref]) + elif tag==':field' and table: + # # print 're3:'+tag + field = args[i] + if not field in db[table]: break + try: + item = dbset.select(db[table][field],limitby=(0,1)).first() + except ValueError: + return Row({'status':400,'pattern':pattern, + 'error':'invalid path','response':None}) + if not item: + return Row({'status':404,'pattern':pattern, + 'error':'record not found','response':None}) + else: + return Row({'status':200,'response':item[field], + 'pattern':pattern}) + elif tag != args[i]: + break + otable = table + i += 1 + if i==len(tags) and table: + otable,ofield = vars.get('order','%s.%s' % (table,field)).split('.',1) + try: + if otable[:1]=='~': orderby = ~db[otable[1:]][ofield] + else: orderby = db[otable][ofield] + except KeyError: + return Row({'status':400,'error':'invalid orderby','response':None}) + fields = [field for field in db[table] if field.readable] + count = dbset.count() + try: + limits = (int(vars.get('min',0)),int(vars.get('max',1000))) + if limits[0]<0 or limits[1]<limits[0]: raise ValueError + except ValueError: + Row({'status':400,'error':'invalid limits','response':None}) + if count > limits[1]-limits[0]: + Row({'status':400,'error':'too many records','response':None}) + try: + response = dbset.select(limitby=limits,orderby=orderby,*fields) + except ValueError: + return Row({'status':400,'pattern':pattern, + 'error':'invalid path','response':None}) + return Row({'status':200,'response':response,'pattern':pattern}) + return Row({'status':400,'error':'no matching pattern','response':None}) + + + def define_table( + self, + tablename, + *fields, + **args + ): + + for key in args: + if key not in [ + 'migrate', + 'primarykey', + 'fake_migrate', + 'format', + 'trigger_name', + 'sequence_name', + 'polymodel']: + raise SyntaxError, 'invalid table "%s" attribute: %s' % (tablename, key) + migrate = self._migrate_enabled and args.get('migrate',self._migrate) + fake_migrate = self._fake_migrate_all or args.get('fake_migrate',self._fake_migrate) + format = args.get('format',None) + trigger_name = args.get('trigger_name', None) + sequence_name = args.get('sequence_name', None) + primarykey=args.get('primarykey',None) + polymodel=args.get('polymodel',None) + if not isinstance(tablename,str): + raise SyntaxError, "missing table name" + tablename = cleanup(tablename) + lowertablename = tablename.lower() + + if tablename.startswith('_') or hasattr(self,lowertablename) or \ + regex_python_keywords.match(tablename): + raise SyntaxError, 'invalid table name: %s' % tablename + elif lowertablename in self.tables: + raise SyntaxError, 'table already defined: %s' % tablename + elif self.check_reserved: + self.check_reserved_keyword(tablename) + + if self._common_fields: + fields = [f for f in fields] + [f for f in self._common_fields] + + t = self[tablename] = Table(self, tablename, *fields, + **dict(primarykey=primarykey, + trigger_name=trigger_name, + sequence_name=sequence_name)) + # db magic + if self._uri in (None,'None'): + return t + + t._create_references() + + if migrate or self._adapter.dbengine=='google:datastore': + try: + sql_locker.acquire() + self._adapter.create_table(t,migrate=migrate, + fake_migrate=fake_migrate, + polymodel=polymodel) + finally: + sql_locker.release() + else: + t._dbt = None + self.tables.append(tablename) + t._format = format + return t + + def __iter__(self): + for tablename in self.tables: + yield self[tablename] + + def __getitem__(self, key): + return dict.__getitem__(self, str(key)) + + def __setitem__(self, key, value): + dict.__setitem__(self, str(key), value) + + def __getattr__(self, key): + return self[key] + + def __setattr__(self, key, value): + if key[:1]!='_' and key in self: + raise SyntaxError, \ + 'Object %s exists and cannot be redefined' % key + self[key] = value + + def __repr__(self): + return '<DAL ' + dict.__repr__(self) + '>' + + def __call__(self, query=None): + if isinstance(query,Table): + query = query._id>0 + elif isinstance(query,Field): + query = query!=None + return Set(self, query) + + def commit(self): + self._adapter.commit() + + def rollback(self): + self._adapter.rollback() + + def executesql(self, query, placeholders=None, as_dict=False): + """ + placeholders is optional and will always be None when using DAL + if using raw SQL with placeholders, placeholders may be + a sequence of values to be substituted in + or, *if supported by the DB driver*, a dictionary with keys + matching named placeholders in your SQL. + + Added 2009-12-05 "as_dict" optional argument. Will always be + None when using DAL. If using raw SQL can be set to True + and the results cursor returned by the DB driver will be + converted to a sequence of dictionaries keyed with the db + field names. Tested with SQLite but should work with any database + since the cursor.description used to get field names is part of the + Python dbi 2.0 specs. Results returned with as_dict = True are + the same as those returned when applying .to_list() to a DAL query. + + [{field1: value1, field2: value2}, {field1: value1b, field2: value2b}] + + --bmeredyk + """ + if placeholders: + self._adapter.execute(query, placeholders) + else: + self._adapter.execute(query) + if as_dict: + if not hasattr(self._adapter.cursor,'description'): + raise RuntimeError, "database does not support executesql(...,as_dict=True)" + # Non-DAL legacy db query, converts cursor results to dict. + # sequence of 7-item sequences. each sequence tells about a column. + # first item is always the field name according to Python Database API specs + columns = self._adapter.cursor.description + # reduce the column info down to just the field names + fields = [f[0] for f in columns] + # will hold our finished resultset in a list + data = self._adapter.cursor.fetchall() + # convert the list for each row into a dictionary so it's + # easier to work with. row['field_name'] rather than row[0] + return [dict(zip(fields,row)) for row in data] + # see if any results returned from database + try: + return self._adapter.cursor.fetchall() + except: + return None + + def _update_referenced_by(self, other): + for tablename in self.tables: + by = self[tablename]._referenced_by + by[:] = [item for item in by if not item[0] == other] + + def export_to_csv_file(self, ofile, *args, **kwargs): + for table in self.tables: + ofile.write('TABLE %s\r\n' % table) + self(self[table]._id > 0).select().export_to_csv_file(ofile, *args, **kwargs) + ofile.write('\r\n\r\n') + ofile.write('END') + + def import_from_csv_file(self, ifile, id_map={}, null='<NULL>', + unique='uuid', *args, **kwargs): + for line in ifile: + line = line.strip() + if not line: + continue + elif line == 'END': + return + elif not line.startswith('TABLE ') or not line[6:] in self.tables: + raise SyntaxError, 'invalid file format' + else: + tablename = line[6:] + self[tablename].import_from_csv_file(ifile, id_map, null, + unique, *args, **kwargs) + + +class SQLALL(object): + """ + Helper class providing a comma-separated string having all the field names + (prefixed by table name and '.') + + normally only called from within gluon.sql + """ + + def __init__(self, table): + self.table = table + + def __str__(self): + return ', '.join([str(field) for field in self.table]) + + +class Reference(int): + + def __allocate(self): + if not self._record: + self._record = self._table[int(self)] + if not self._record: + raise RuntimeError, "Using a recursive select but encountered a broken reference: %s %d"%(self._table, int(self)) + + def __getattr__(self, key): + if key == 'id': + return int(self) + self.__allocate() + return self._record.get(key, None) + + def __setattr__(self, key, value): + if key.startswith('_'): + int.__setattr__(self, key, value) + return + self.__allocate() + self._record[key] = value + + def __getitem__(self, key): + if key == 'id': + return int(self) + self.__allocate() + return self._record.get(key, None) + + def __setitem__(self,key,value): + self.__allocate() + self._record[key] = value + + +def Reference_unpickler(data): + return marshal.loads(data) + +def Reference_pickler(data): + try: + marshal_dump = marshal.dumps(int(data)) + except AttributeError: + marshal_dump = 'i%s' % struct.pack('<i', int(data)) + return (Reference_unpickler, (marshal_dump,)) + +copy_reg.pickle(Reference, Reference_pickler, Reference_unpickler) + + +class Table(dict): + + """ + an instance of this class represents a database table + + Example:: + + db = DAL(...) + db.define_table('users', Field('name')) + db.users.insert(name='me') # print db.users._insert(...) to see SQL + db.users.drop() + """ + + def __init__( + self, + db, + tablename, + *fields, + **args + ): + """ + Initializes the table and performs checking on the provided fields. + + Each table will have automatically an 'id'. + + If a field is of type Table, the fields (excluding 'id') from that table + will be used instead. + + :raises SyntaxError: when a supplied field is of incorrect type. + """ + self._tablename = tablename + self._sequence_name = args.get('sequence_name',None) or \ + db and db._adapter.sequence_name(tablename) + self._trigger_name = args.get('trigger_name',None) or \ + db and db._adapter.trigger_name(tablename) + + primarykey = args.get('primarykey', None) + fieldnames,newfields=set(),[] + if primarykey: + if not isinstance(primarykey,list): + raise SyntaxError, \ + "primarykey must be a list of fields from table '%s'" \ + % tablename + self._primarykey = primarykey + elif not [f for f in fields if isinstance(f,Field) and f.type=='id']: + field = Field('id', 'id') + newfields.append(field) + fieldnames.add('id') + self._id = field + for field in fields: + if not isinstance(field, (Field, Table)): + raise SyntaxError, \ + 'define_table argument is not a Field or Table: %s' % field + elif isinstance(field, Field) and not field.name in fieldnames: + if hasattr(field, '_db'): + field = copy.copy(field) + newfields.append(field) + fieldnames.add(field.name) + if field.type=='id': + self._id = field + elif isinstance(field, Table): + table = field + for field in table: + if not field.name in fieldnames and not field.type=='id': + newfields.append(copy.copy(field)) + fieldnames.add(field.name) + else: + # let's ignore new fields with duplicated names!!! + pass + fields = newfields + self._db = db + tablename = tablename + self.fields = SQLCallableList() + self.virtualfields = [] + fields = list(fields) + + if db and self._db._adapter.uploads_in_blob==True: + for field in fields: + if isinstance(field, Field) and field.type == 'upload'\ + and field.uploadfield is True: + tmp = field.uploadfield = '%s_blob' % field.name + fields.append(self._db.Field(tmp, 'blob', default='')) + + lower_fieldnames = set() + reserved = dir(Table) + ['fields'] + for field in fields: + if db and db.check_reserved: + db.check_reserved_keyword(field.name) + elif field.name in reserved: + raise SyntaxError, "field name %s not allowed" % field.name + + if field.name.lower() in lower_fieldnames: + raise SyntaxError, "duplicate field %s in table %s" \ + % (field.name, tablename) + else: + lower_fieldnames.add(field.name.lower()) + + self.fields.append(field.name) + self[field.name] = field + if field.type == 'id': + self['id'] = field + field.tablename = field._tablename = tablename + field.table = field._table = self + field.db = field._db = self._db + if self._db and field.type!='text' and \ + self._db._adapter.maxcharlength < field.length: + field.length = self._db._adapter.maxcharlength + if field.requires == DEFAULT: + field.requires = sqlhtml_validators(field) + self.ALL = SQLALL(self) + + if hasattr(self,'_primarykey'): + for k in self._primarykey: + if k not in self.fields: + raise SyntaxError, \ + "primarykey must be a list of fields from table '%s " % tablename + else: + self[k].notnull = True + + def _validate(self,**vars): + errors = Row() + for key,value in vars.items(): + value,error = self[key].validate(value) + if error: + errors[key] = error + return errors + + def _create_references(self): + pr = self._db._pending_references + self._referenced_by = [] + for fieldname in self.fields: + field=self[fieldname] + if isinstance(field.type,str) and field.type[:10] == 'reference ': + ref = field.type[10:].strip() + if not ref.split(): + raise SyntaxError, 'Table: reference to nothing: %s' %ref + refs = ref.split('.') + rtablename = refs[0] + if not rtablename in self._db: + pr[rtablename] = pr.get(rtablename,[]) + [field] + continue + rtable = self._db[rtablename] + if len(refs)==2: + rfieldname = refs[1] + if not hasattr(rtable,'_primarykey'): + raise SyntaxError,\ + 'keyed tables can only reference other keyed tables (for now)' + if rfieldname not in rtable.fields: + raise SyntaxError,\ + "invalid field '%s' for referenced table '%s' in table '%s'" \ + % (rfieldname, rtablename, self._tablename) + rtable._referenced_by.append((self._tablename, field.name)) + for referee in pr.get(self._tablename,[]): + self._referenced_by.append((referee._tablename,referee.name)) + + def _filter_fields(self, record, id=False): + return dict([(k, v) for (k, v) in record.items() if k + in self.fields and (self[k].type!='id' or id)]) + + def _build_query(self,key): + """ for keyed table only """ + query = None + for k,v in key.iteritems(): + if k in self._primarykey: + if query: + query = query & (self[k] == v) + else: + query = (self[k] == v) + else: + raise SyntaxError, \ + 'Field %s is not part of the primary key of %s' % \ + (k,self._tablename) + return query + + def __getitem__(self, key): + if not key: + return None + elif isinstance(key, dict): + """ for keyed table """ + query = self._build_query(key) + rows = self._db(query).select() + if rows: + return rows[0] + return None + elif str(key).isdigit(): + return self._db(self._id == key).select(limitby=(0,1)).first() + elif key: + return dict.__getitem__(self, str(key)) + + def __call__(self, key=DEFAULT, **kwargs): + if key!=DEFAULT: + if isinstance(key, Query): + record = self._db(key).select(limitby=(0,1)).first() + elif not str(key).isdigit(): + record = None + else: + record = self._db(self._id == key).select(limitby=(0,1)).first() + if record: + for k,v in kwargs.items(): + if record[k]!=v: return None + return record + elif kwargs: + query = reduce(lambda a,b:a&b,[self[k]==v for k,v in kwargs.items()]) + return self._db(query).select(limitby=(0,1)).first() + else: + return None + + def __setitem__(self, key, value): + if isinstance(key, dict) and isinstance(value, dict): + """ option for keyed table """ + if set(key.keys()) == set(self._primarykey): + value = self._filter_fields(value) + kv = {} + kv.update(value) + kv.update(key) + if not self.insert(**kv): + query = self._build_query(key) + self._db(query).update(**self._filter_fields(value)) + else: + raise SyntaxError,\ + 'key must have all fields from primary key: %s'%\ + (self._primarykey) + elif str(key).isdigit(): + if key == 0: + self.insert(**self._filter_fields(value)) + elif not self._db(self._id == key)\ + .update(**self._filter_fields(value)): + raise SyntaxError, 'No such record: %s' % key + else: + if isinstance(key, dict): + raise SyntaxError,\ + 'value must be a dictionary: %s' % value + dict.__setitem__(self, str(key), value) + + def __delitem__(self, key): + if isinstance(key, dict): + query = self._build_query(key) + if not self._db(query).delete(): + raise SyntaxError, 'No such record: %s' % key + elif not str(key).isdigit() or not self._db(self._id == key).delete(): + raise SyntaxError, 'No such record: %s' % key + + def __getattr__(self, key): + return self[key] + + def __setattr__(self, key, value): + if key in self: + raise SyntaxError, 'Object exists and cannot be redefined: %s' % key + self[key] = value + + def __iter__(self): + for fieldname in self.fields: + yield self[fieldname] + + def __repr__(self): + return '<Table ' + dict.__repr__(self) + '>' + + def __str__(self): + if self.get('_ot', None): + return '%s AS %s' % (self._ot, self._tablename) + return self._tablename + + def _drop(self, mode = ''): + return self._db._adapter._drop(self, mode) + + def drop(self, mode = ''): + return self._db._adapter.drop(self,mode) + + def _listify(self,fields,update=False): + new_fields = [] + new_fields_names = [] + for name in fields: + if not name in self.fields: + if name != 'id': + raise SyntaxError, 'Field %s does not belong to the table' % name + else: + new_fields.append((self[name],fields[name])) + new_fields_names.append(name) + for ofield in self: + if not ofield.name in new_fields_names: + if not update and ofield.default!=None: + new_fields.append((ofield,ofield.default)) + elif update and ofield.update!=None: + new_fields.append((ofield,ofield.update)) + for ofield in self: + if not ofield.name in new_fields_names and ofield.compute: + try: + new_fields.append((ofield,ofield.compute(Row(fields)))) + except KeyError: + pass + if not update and ofield.required and not ofield.name in new_fields_names: + raise SyntaxError,'Table: missing required field: %s' % ofield.name + return new_fields + + def _insert(self, **fields): + return self._db._adapter._insert(self,self._listify(fields)) + + def insert(self, **fields): + return self._db._adapter.insert(self,self._listify(fields)) + + def validate_and_insert(self,**fields): + response = Row() + response.errors = self._validate(**fields) + if not response.errors: + response.id = self.insert(**fields) + else: + response.id = None + return response + + def update_or_insert(self, key=DEFAULT, **values): + if key==DEFAULT: + record = self(**values) + else: + record = self(key) + if record: + record.update_record(**values) + newid = None + else: + newid = self.insert(**values) + return newid + + def bulk_insert(self, items): + """ + here items is a list of dictionaries + """ + items = [self._listify(item) for item in items] + return self._db._adapter.bulk_insert(self,items) + + def _truncate(self, mode = None): + return self._db._adapter._truncate(self, mode) + + def truncate(self, mode = None): + return self._db._adapter.truncate(self, mode) + + def import_from_csv_file( + self, + csvfile, + id_map=None, + null='<NULL>', + unique='uuid', + *args, **kwargs + ): + """ + import records from csv file. Column headers must have same names as + table fields. field 'id' is ignored. If column names read 'table.file' + the 'table.' prefix is ignored. + 'unique' argument is a field which must be unique + (typically a uuid field) + """ + + delimiter = kwargs.get('delimiter', ',') + quotechar = kwargs.get('quotechar', '"') + quoting = kwargs.get('quoting', csv.QUOTE_MINIMAL) + + reader = csv.reader(csvfile, delimiter=delimiter, quotechar=quotechar, quoting=quoting) + colnames = None + if isinstance(id_map, dict): + if not self._tablename in id_map: + id_map[self._tablename] = {} + id_map_self = id_map[self._tablename] + + def fix(field, value, id_map): + if value == null: + value = None + elif field.type=='blob': + value = base64.b64decode(value) + elif field.type=='double': + if not value.strip(): + value = None + else: + value = float(value) + elif field.type=='integer': + if not value.strip(): + value = None + else: + value = int(value) + elif field.type.startswith('list:string'): + value = bar_decode_string(value) + elif field.type.startswith('list:reference'): + ref_table = field.type[10:].strip() + value = [id_map[ref_table][int(v)] \ + for v in bar_decode_string(value)] + elif field.type.startswith('list:'): + value = bar_decode_integer(value) + elif id_map and field.type.startswith('reference'): + try: + value = id_map[field.type[9:].strip()][value] + except KeyError: + pass + return (field.name, value) + + def is_id(colname): + if colname in self: + return self[colname].type == 'id' + else: + return False + + for line in reader: + if not line: + break + if not colnames: + colnames = [x.split('.',1)[-1] for x in line][:len(line)] + cols, cid = [], [] + for i,colname in enumerate(colnames): + if is_id(colname): + cid = i + else: + cols.append(i) + if colname == unique: + unique_idx = i + else: + items = [fix(self[colnames[i]], line[i], id_map) \ + for i in cols if colnames[i] in self.fields] + # Validation. Check for duplicate of 'unique' &, + # if present, update instead of insert. + if not unique or unique not in colnames: + new_id = self.insert(**dict(items)) + else: + unique_value = line[unique_idx] + query = self._db[self][unique] == unique_value + record = self._db(query).select().first() + if record: + record.update_record(**dict(items)) + new_id = record[self._id.name] + else: + new_id = self.insert(**dict(items)) + if id_map and cid != []: + id_map_self[line[cid]] = new_id + + def with_alias(self, alias): + return self._db._adapter.alias(self,alias) + + def on(self, query): + return Expression(self._db,self._db._adapter.ON,self,query) + + + +class Expression(object): + + def __init__( + self, + db, + op, + first=None, + second=None, + type=None, + ): + + self.db = db + self.op = op + self.first = first + self.second = second + ### self._tablename = first._tablename ## CHECK + if not type and first and hasattr(first,'type'): + self.type = first.type + else: + self.type = type + + def sum(self): + return Expression(self.db, self.db._adapter.AGGREGATE, self, 'SUM', self.type) + + def max(self): + return Expression(self.db, self.db._adapter.AGGREGATE, self, 'MAX', self.type) + + def min(self): + return Expression(self.db, self.db._adapter.AGGREGATE, self, 'MIN', self.type) + + def len(self): + return Expression(self.db, self.db._adapter.AGGREGATE, self, 'LENGTH', 'integer') + + def lower(self): + return Expression(self.db, self.db._adapter.LOWER, self, None, self.type) + + def upper(self): + return Expression(self.db, self.db._adapter.UPPER, self, None, self.type) + + def year(self): + return Expression(self.db, self.db._adapter.EXTRACT, self, 'year', 'integer') + + def month(self): + return Expression(self.db, self.db._adapter.EXTRACT, self, 'month', 'integer') + + def day(self): + return Expression(self.db, self.db._adapter.EXTRACT, self, 'day', 'integer') + + def hour(self): + return Expression(self.db, self.db._adapter.EXTRACT, self, 'hour', 'integer') + + def minutes(self): + return Expression(self.db, self.db._adapter.EXTRACT, self, 'minute', 'integer') + + def coalesce_zero(self): + return Expression(self.db, self.db._adapter.COALESCE_ZERO, self, None, self.type) + + def seconds(self): + return Expression(self.db, self.db._adapter.EXTRACT, self, 'second', 'integer') + + def __getslice__(self, start, stop): + if start < 0: + pos0 = '(%s - %d)' % (self.len(), abs(start) - 1) + else: + pos0 = start + 1 + + if stop < 0: + length = '(%s - %d - %s)' % (self.len(), abs(stop) - 1, pos0) + elif stop == sys.maxint: + length = self.len() + else: + length = '(%s - %s)' % (stop + 1, pos0) + return Expression(self.db,self.db._adapter.SUBSTRING, + self, (pos0, length), self.type) + + def __getitem__(self, i): + return self[i:i + 1] + + def __str__(self): + return self.db._adapter.expand(self,self.type) + + def __or__(self, other): # for use in sortby + return Expression(self.db,self.db._adapter.COMMA,self,other,self.type) + + def __invert__(self): + if hasattr(self,'_op') and self.op == self.db._adapter.INVERT: + return self.first + return Expression(self.db,self.db._adapter.INVERT,self,type=self.type) + + def __add__(self, other): + return Expression(self.db,self.db._adapter.ADD,self,other,self.type) + + def __sub__(self, other): + if self.type == 'integer': + result_type = 'integer' + elif self.type in ['date','time','datetime','double']: + result_type = 'double' + else: + raise SyntaxError, "subtraction operation not supported for type" + return Expression(self.db,self.db._adapter.SUB,self,other, + result_type) + def __mul__(self, other): + return Expression(self.db,self.db._adapter.MUL,self,other,self.type) + + def __div__(self, other): + return Expression(self.db,self.db._adapter.DIV,self,other,self.type) + + def __mod__(self, other): + return Expression(self.db,self.db._adapter.MOD,self,other,self.type) + + def __eq__(self, value): + return Query(self.db, self.db._adapter.EQ, self, value) + + def __ne__(self, value): + return Query(self.db, self.db._adapter.NE, self, value) + + def __lt__(self, value): + return Query(self.db, self.db._adapter.LT, self, value) + + def __le__(self, value): + return Query(self.db, self.db._adapter.LE, self, value) + + def __gt__(self, value): + return Query(self.db, self.db._adapter.GT, self, value) + + def __ge__(self, value): + return Query(self.db, self.db._adapter.GE, self, value) + + def like(self, value): + return Query(self.db, self.db._adapter.LIKE, self, value) + + def belongs(self, value): + return Query(self.db, self.db._adapter.BELONGS, self, value) + + def startswith(self, value): + if not self.type in ('string', 'text'): + raise SyntaxError, "startswith used with incompatible field type" + return Query(self.db, self.db._adapter.STARTSWITH, self, value) + + def endswith(self, value): + if not self.type in ('string', 'text'): + raise SyntaxError, "endswith used with incompatible field type" + return Query(self.db, self.db._adapter.ENDSWITH, self, value) + + def contains(self, value): + if not self.type in ('string', 'text') and not self.type.startswith('list:'): + raise SyntaxError, "contains used with incompatible field type" + return Query(self.db, self.db._adapter.CONTAINS, self, value) + + def with_alias(self,alias): + return Expression(self.db,self.db._adapter.AS,self,alias,self.type) + + # for use in both Query and sortby + + +class SQLCustomType(object): + """ + allows defining of custom SQL types + + Example:: + + decimal = SQLCustomType( + type ='double', + native ='integer', + encoder =(lambda x: int(float(x) * 100)), + decoder = (lambda x: Decimal("0.00") + Decimal(str(float(x)/100)) ) + ) + + db.define_table( + 'example', + Field('value', type=decimal) + ) + + :param type: the web2py type (default = 'string') + :param native: the backend type + :param encoder: how to encode the value to store it in the backend + :param decoder: how to decode the value retrieved from the backend + :param validator: what validators to use ( default = None, will use the + default validator for type) + """ + + def __init__( + self, + type='string', + native=None, + encoder=None, + decoder=None, + validator=None, + _class=None, + ): + + self.type = type + self.native = native + self.encoder = encoder or (lambda x: x) + self.decoder = decoder or (lambda x: x) + self.validator = validator + self._class = _class or type + + def startswith(self, dummy=None): + return False + + def __getslice__(self, a=0, b=100): + return None + + def __getitem__(self, i): + return None + + def __str__(self): + return self._class + + +class Field(Expression): + + """ + an instance of this class represents a database field + + example:: + + a = Field(name, 'string', length=32, default=None, required=False, + requires=IS_NOT_EMPTY(), ondelete='CASCADE', + notnull=False, unique=False, + uploadfield=True, widget=None, label=None, comment=None, + uploadfield=True, # True means store on disk, + # 'a_field_name' means store in this field in db + # False means file content will be discarded. + writable=True, readable=True, update=None, authorize=None, + autodelete=False, represent=None, uploadfolder=None, + uploadseparate=False # upload to separate directories by uuid_keys + # first 2 character and tablename.fieldname + # False - old behavior + # True - put uploaded file in + # <uploaddir>/<tablename>.<fieldname>/uuid_key[:2] + # directory) + + to be used as argument of DAL.define_table + + allowed field types: + string, boolean, integer, double, text, blob, + date, time, datetime, upload, password + + strings must have a length of Adapter.maxcharlength by default (512 or 255 for mysql) + fields should have a default or they will be required in SQLFORMs + the requires argument is used to validate the field input in SQLFORMs + + """ + + def __init__( + self, + fieldname, + type='string', + length=None, + default=DEFAULT, + required=False, + requires=DEFAULT, + ondelete='CASCADE', + notnull=False, + unique=False, + uploadfield=True, + widget=None, + label=None, + comment=None, + writable=True, + readable=True, + update=None, + authorize=None, + autodelete=False, + represent=None, + uploadfolder=None, + uploadseparate=False, + compute=None, + custom_store=None, + custom_retrieve=None, + custom_delete=None, + ): + self.db = None + self.op = None + self.first = None + self.second = None + if not isinstance(fieldname,str): + raise SyntaxError, "missing field name" + if fieldname.startswith(':'): + fieldname,readable,writable=fieldname[1:],False,False + elif fieldname.startswith('.'): + fieldname,readable,writable=fieldname[1:],False,False + if '=' in fieldname: + fieldname,default = fieldname.split('=',1) + self.name = fieldname = cleanup(fieldname) + if hasattr(Table,fieldname) or fieldname[0] == '_' or \ + regex_python_keywords.match(fieldname): + raise SyntaxError, 'Field: invalid field name: %s' % fieldname + if isinstance(type, Table): + type = 'reference ' + type._tablename + self.type = type # 'string', 'integer' + self.length = (length is None) and MAXCHARLENGTH or length + if default==DEFAULT: + self.default = update or None + else: + self.default = default + self.required = required # is this field required + self.ondelete = ondelete.upper() # this is for reference fields only + self.notnull = notnull + self.unique = unique + self.uploadfield = uploadfield + self.uploadfolder = uploadfolder + self.uploadseparate = uploadseparate + self.widget = widget + self.label = label or ' '.join(item.capitalize() for item in fieldname.split('_')) + self.comment = comment + self.writable = writable + self.readable = readable + self.update = update + self.authorize = authorize + self.autodelete = autodelete + if not represent and type in ('list:integer','list:string'): + represent=lambda x: ', '.join(str(y) for y in x or []) + self.represent = represent + self.compute = compute + self.isattachment = True + self.custom_store = custom_store + self.custom_retrieve = custom_retrieve + self.custom_delete = custom_delete + if self.label is None: + self.label = ' '.join([x.capitalize() for x in + fieldname.split('_')]) + if requires is None: + self.requires = [] + else: + self.requires = requires + + def store(self, file, filename=None, path=None): + if self.custom_store: + return self.custom_store(file,filename,path) + if not filename: + filename = file.name + filename = os.path.basename(filename.replace('/', os.sep)\ + .replace('\\', os.sep)) + m = re.compile('\.(?P<e>\w{1,5})$').search(filename) + extension = m and m.group('e') or 'txt' + uuid_key = web2py_uuid().replace('-', '')[-16:] + encoded_filename = base64.b16encode(filename).lower() + newfilename = '%s.%s.%s.%s' % \ + (self._tablename, self.name, uuid_key, encoded_filename) + newfilename = newfilename[:200] + '.' + extension + if isinstance(self.uploadfield,Field): + blob_uploadfield_name = self.uploadfield.uploadfield + keys={self.uploadfield.name: newfilename, + blob_uploadfield_name: file.read()} + self.uploadfield.table.insert(**keys) + elif self.uploadfield == True: + if path: + pass + elif self.uploadfolder: + path = self.uploadfolder + elif self.db._adapter.folder: + path = os.path.join(self.db._adapter.folder, '..', 'uploads') + else: + raise RuntimeError, "you must specify a Field(...,uploadfolder=...)" + if self.uploadseparate: + path = os.path.join(path,"%s.%s" % (self._tablename, self.name),uuid_key[:2]) + if not os.path.exists(path): + os.makedirs(path) + pathfilename = os.path.join(path, newfilename) + dest_file = open(pathfilename, 'wb') + try: + shutil.copyfileobj(file, dest_file) + finally: + dest_file.close() + return newfilename + + def retrieve(self, name, path=None): + if self.custom_retrieve: + return self.custom_retrieve(name, path) + import http + if self.authorize or isinstance(self.uploadfield, str): + row = self.db(self == name).select().first() + if not row: + raise http.HTTP(404) + if self.authorize and not self.authorize(row): + raise http.HTTP(403) + try: + m = regex_content.match(name) + if not m or not self.isattachment: + raise TypeError, 'Can\'t retrieve %s' % name + filename = base64.b16decode(m.group('name'), True) + filename = regex_cleanup_fn.sub('_', filename) + except (TypeError, AttributeError): + filename = name + if isinstance(self.uploadfield, str): # ## if file is in DB + return (filename, cStringIO.StringIO(row[self.uploadfield] or '')) + elif isinstance(self.uploadfield,Field): + blob_uploadfield_name = self.uploadfield.uploadfield + query = self.uploadfield == name + data = self.uploadfield.table(query)[blob_uploadfield_name] + return (filename, cStringIO.StringIO(data)) + else: + # ## if file is on filesystem + if path: + pass + elif self.uploadfolder: + path = self.uploadfolder + else: + path = os.path.join(self.db._adapter.folder, '..', 'uploads') + if self.uploadseparate: + t = m.group('table') + f = m.group('field') + u = m.group('uuidkey') + path = os.path.join(path,"%s.%s" % (t,f),u[:2]) + return (filename, open(os.path.join(path, name), 'rb')) + + def formatter(self, value): + if value is None or not self.requires: + return value + if not isinstance(self.requires, (list, tuple)): + requires = [self.requires] + elif isinstance(self.requires, tuple): + requires = list(self.requires) + else: + requires = copy.copy(self.requires) + requires.reverse() + for item in requires: + if hasattr(item, 'formatter'): + value = item.formatter(value) + return value + + def validate(self, value): + if not self.requires: + return (value, None) + requires = self.requires + if not isinstance(requires, (list, tuple)): + requires = [requires] + for validator in requires: + (value, error) = validator(value) + if error: + return (value, error) + return (value, None) + + def count(self): + return Expression(self.db, self.db._adapter.AGGREGATE, self, 'COUNT', 'integer') + + def __nonzero__(self): + return True + + def __str__(self): + try: + return '%s.%s' % (self.tablename, self.name) + except: + return '<no table>.%s' % self.name + + +class Query(object): + + """ + a query object necessary to define a set. + it can be stored or can be passed to DAL.__call__() to obtain a Set + + Example:: + + query = db.users.name=='Max' + set = db(query) + records = set.select() + + """ + + def __init__( + self, + db, + op, + first=None, + second=None, + ): + self.db = db + self.op = op + self.first = first + self.second = second + + def __str__(self): + return self.db._adapter.expand(self) + + def __and__(self, other): + return Query(self.db,self.db._adapter.AND,self,other) + + def __or__(self, other): + return Query(self.db,self.db._adapter.OR,self,other) + + def __invert__(self): + if self.op==self.db._adapter.NOT: + return self.first + return Query(self.db,self.db._adapter.NOT,self) + + +regex_quotes = re.compile("'[^']*'") + + +def xorify(orderby): + if not orderby: + return None + orderby2 = orderby[0] + for item in orderby[1:]: + orderby2 = orderby2 | item + return orderby2 + + +class Set(object): + + """ + a Set represents a set of records in the database, + the records are identified by the query=Query(...) object. + normally the Set is generated by DAL.__call__(Query(...)) + + given a set, for example + set = db(db.users.name=='Max') + you can: + set.update(db.users.name='Massimo') + set.delete() # all elements in the set + set.select(orderby=db.users.id, groupby=db.users.name, limitby=(0,10)) + and take subsets: + subset = set(db.users.id<5) + """ + + def __init__(self, db, query): + self.db = db + self._db = db # for backward compatibility + self.query = query + + def __call__(self, query): + if isinstance(query,Table): + query = query._id>0 + elif isinstance(query,Field): + query = query!=None + if self.query: + return Set(self.db, self.query & query) + else: + return Set(self.db, query) + + def _count(self,distinct=None): + return self.db._adapter._count(self.query,distinct) + + def _select(self, *fields, **attributes): + return self.db._adapter._select(self.query,fields,attributes) + + def _delete(self): + tablename=self.db._adapter.get_table(self.query) + return self.db._adapter._delete(tablename,self.query) + + def _update(self, **update_fields): + tablename = self.db._adapter.get_table(self.query) + fields = self.db[tablename]._listify(update_fields,update=True) + return self.db._adapter._update(tablename,self.query,fields) + + def isempty(self): + return not self.select(limitby=(0,1)) + + def count(self,distinct=None): + return self.db._adapter.count(self.query,distinct) + + def select(self, *fields, **attributes): + return self.db._adapter.select(self.query,fields,attributes) + + def delete(self): + tablename=self.db._adapter.get_table(self.query) + self.delete_uploaded_files() + return self.db._adapter.delete(tablename,self.query) + + def update(self, **update_fields): + tablename = self.db._adapter.get_table(self.query) + fields = self.db[tablename]._listify(update_fields,update=True) + if not fields: + raise SyntaxError, "No fields to update" + self.delete_uploaded_files(update_fields) + return self.db._adapter.update(tablename,self.query,fields) + + def validate_and_update(self, **update_fields): + tablename = self.db._adapter.get_table(self.query) + response = Row() + response.errors = self.db[tablename]._validate(**update_fields) + fields = self.db[tablename]._listify(update_fields,update=True) + if not fields: + raise SyntaxError, "No fields to update" + self.delete_uploaded_files(update_fields) + if not response.errors: + response.updated = self.db._adapter.update(tablename,self.query,fields) + else: + response.updated = None + return response + + def delete_uploaded_files(self, upload_fields=None): + table = self.db[self.db._adapter.tables(self.query)[0]] + # ## mind uploadfield==True means file is not in DB + if upload_fields: + fields = upload_fields.keys() + else: + fields = table.fields + fields = [f for f in fields if table[f].type == 'upload' + and table[f].uploadfield == True + and table[f].autodelete] + if not fields: + return + for record in self.select(*[table[f] for f in fields]): + for fieldname in fields: + field = table[fieldname] + oldname = record.get(fieldname, None) + if not oldname: + continue + if upload_fields and oldname == upload_fields[fieldname]: + continue + if field.custom_delete: + field.custom_delete(oldname) + else: + uploadfolder = field.uploadfolder + if not uploadfolder: + uploadfolder = os.path.join(self.db._adapter.folder, '..', 'uploads') + if field.uploadseparate: + items = oldname.split('.') + uploadfolder = os.path.join(uploadfolder, + "%s.%s" % (items[0], items[1]), + items[2][:2]) + oldpath = os.path.join(uploadfolder, oldname) + if os.path.exists(oldpath): + os.unlink(oldpath) + +def update_record(pack, a={}): + (colset, table, id) = pack + b = a or dict(colset) + c = dict([(k,v) for (k,v) in b.items() if k in table.fields and table[k].type!='id']) + table._db(table._id==id).update(**c) + for (k, v) in c.items(): + colset[k] = v + + +class Rows(object): + + """ + A wrapper for the return value of a select. It basically represents a table. + It has an iterator and each row is represented as a dictionary. + """ + + # ## TODO: this class still needs some work to care for ID/OID + + def __init__( + self, + db=None, + records=[], + colnames=[], + compact=True, + rawrows=None + ): + self.db = db + self.records = records + self.colnames = colnames + self.compact = compact + self.response = rawrows + + def setvirtualfields(self,**keyed_virtualfields): + if not keyed_virtualfields: + return self + for row in self.records: + for (tablename,virtualfields) in keyed_virtualfields.items(): + attributes = dir(virtualfields) + virtualfields.__dict__.update(row) + if not tablename in row: + box = row[tablename] = Row() + else: + box = row[tablename] + for attribute in attributes: + if attribute[0] != '_': + method = getattr(virtualfields,attribute) + if hasattr(method,'im_func') and method.im_func.func_code.co_argcount: + box[attribute]=method() + return self + + def __and__(self,other): + if self.colnames!=other.colnames: raise Exception, 'Cannot & incompatible Rows objects' + records = self.records+other.records + return Rows(self.db,records,self.colnames) + + def __or__(self,other): + if self.colnames!=other.colnames: raise Exception, 'Cannot | incompatible Rows objects' + records = self.records + records += [record for record in other.records \ + if not record in records] + return Rows(self.db,records,self.colnames) + + def __nonzero__(self): + if len(self.records): + return 1 + return 0 + + def __len__(self): + return len(self.records) + + def __getslice__(self, a, b): + return Rows(self.db,self.records[a:b],self.colnames) + + def __getitem__(self, i): + row = self.records[i] + keys = row.keys() + if self.compact and len(keys) == 1 and keys[0] != '_extra': + return row[row.keys()[0]] + return row + + def __iter__(self): + """ + iterator over records + """ + + for i in xrange(len(self)): + yield self[i] + + def __str__(self): + """ + serializes the table into a csv file + """ + + s = cStringIO.StringIO() + self.export_to_csv_file(s) + return s.getvalue() + + def first(self): + if not self.records: + return None + return self[0] + + def last(self): + if not self.records: + return None + return self[-1] + + def find(self,f): + """ + returns a new Rows object, a subset of the original object, + filtered by the function f + """ + if not self.records: + return Rows(self.db, [], self.colnames) + records = [] + for i in range(0,len(self)): + row = self[i] + if f(row): + records.append(self.records[i]) + return Rows(self.db, records, self.colnames) + + def exclude(self, f): + """ + removes elements from the calling Rows object, filtered by the function f, + and returns a new Rows object containing the removed elements + """ + if not self.records: + return Rows(self.db, [], self.colnames) + removed = [] + i=0 + while i<len(self): + row = self[i] + if f(row): + removed.append(self.records[i]) + del self.records[i] + else: + i += 1 + return Rows(self.db, removed, self.colnames) + + def sort(self, f, reverse=False): + """ + returns a list of sorted elements (not sorted in place) + """ + return Rows(self.db,sorted(self,key=f,reverse=reverse),self.colnames) + + def as_list(self, + compact=True, + storage_to_dict=True, + datetime_to_str=True): + """ + returns the data as a list or dictionary. + :param storage_to_dict: when True returns a dict, otherwise a list(default True) + :param datetime_to_str: convert datetime fields as strings (default True) + """ + (oc, self.compact) = (self.compact, compact) + if storage_to_dict: + items = [item.as_dict(datetime_to_str) for item in self] + else: + items = [item for item in self] + self.compact = compact + return items + + + def as_dict(self, + key='id', + compact=True, + storage_to_dict=True, + datetime_to_str=True): + """ + returns the data as a dictionary of dictionaries (storage_to_dict=True) or records (False) + + :param key: the name of the field to be used as dict key, normally the id + :param compact: ? (default True) + :param storage_to_dict: when True returns a dict, otherwise a list(default True) + :param datetime_to_str: convert datetime fields as strings (default True) + """ + rows = self.as_list(compact, storage_to_dict, datetime_to_str) + if isinstance(key,str) and key.count('.')==1: + (table, field) = key.split('.') + return dict([(r[table][field],r) for r in rows]) + elif isinstance(key,str): + return dict([(r[key],r) for r in rows]) + else: + return dict([(key(r),r) for r in rows]) + + def export_to_csv_file(self, ofile, null='<NULL>', *args, **kwargs): + """ + export data to csv, the first line contains the column names + + :param ofile: where the csv must be exported to + :param null: how null values must be represented (default '<NULL>') + :param delimiter: delimiter to separate values (default ',') + :param quotechar: character to use to quote string values (default '"') + :param quoting: quote system, use csv.QUOTE_*** (default csv.QUOTE_MINIMAL) + :param represent: use the fields .represent value (default False) + :param colnames: list of column names to use (default self.colnames) + This will only work when exporting rows objects!!!! + DO NOT use this with db.export_to_csv() + """ + delimiter = kwargs.get('delimiter', ',') + quotechar = kwargs.get('quotechar', '"') + quoting = kwargs.get('quoting', csv.QUOTE_MINIMAL) + represent = kwargs.get('represent', False) + writer = csv.writer(ofile, delimiter=delimiter, + quotechar=quotechar, quoting=quoting) + colnames = kwargs.get('colnames', self.colnames) + # a proper csv starting with the column names + writer.writerow(colnames) + + def none_exception(value): + """ + returns a cleaned up value that can be used for csv export: + - unicode text is encoded as such + - None values are replaced with the given representation (default <NULL>) + """ + if value is None: + return null + elif isinstance(value, unicode): + return value.encode('utf8') + elif isinstance(value,Reference): + return int(value) + elif hasattr(value, 'isoformat'): + return value.isoformat()[:19].replace('T', ' ') + elif isinstance(value, (list,tuple)): # for type='list:..' + return bar_encode(value) + return value + + for record in self: + row = [] + for col in colnames: + if not table_field.match(col): + row.append(record._extra[col]) + else: + (t, f) = col.split('.') + field = self.db[t][f] + if isinstance(record.get(t, None), (Row,dict)): + value = record[t][f] + else: + value = record[f] + if field.type=='blob' and value!=None: + value = base64.b64encode(value) + elif represent and field.represent: + value = field.represent(value) + row.append(none_exception(value)) + writer.writerow(row) + + def xml(self): + """ + serializes the table using sqlhtml.SQLTABLE (if present) + """ + + import sqlhtml + return sqlhtml.SQLTABLE(self).xml() + + def json(self, mode='object', default=None): + """ + serializes the table to a JSON list of objects + """ + mode = mode.lower() + if not mode in ['object', 'array']: + raise SyntaxError, 'Invalid JSON serialization mode: %s' % mode + + def inner_loop(record, col): + (t, f) = col.split('.') + res = None + if not table_field.match(col): + res = record._extra[col] + else: + if isinstance(record.get(t, None), Row): + res = record[t][f] + else: + res = record[f] + if mode == 'object': + return (f, res) + else: + return res + + if mode == 'object': + items = [dict([inner_loop(record, col) for col in + self.colnames]) for record in self] + else: + items = [[inner_loop(record, col) for col in self.colnames] + for record in self] + if have_serializers: + return serializers.json(items,default=default or serializers.custom_json) + else: + import simplejson + return simplejson.dumps(items) + +def Rows_unpickler(data): + return cPickle.loads(data) + +def Rows_pickler(data): + return Rows_unpickler, \ + (cPickle.dumps(data.as_list(storage_to_dict=True, + datetime_to_str=False)),) + +copy_reg.pickle(Rows, Rows_pickler, Rows_unpickler) + + +################################################################################ +# dummy function used to define some doctests +################################################################################ + +def test_all(): + """ + + >>> if len(sys.argv)<2: db = DAL(\"sqlite://test.db\") + >>> if len(sys.argv)>1: db = DAL(sys.argv[1]) + >>> tmp = db.define_table('users',\ + Field('stringf', 'string', length=32, required=True),\ + Field('booleanf', 'boolean', default=False),\ + Field('passwordf', 'password', notnull=True),\ + Field('uploadf', 'upload'),\ + Field('blobf', 'blob'),\ + Field('integerf', 'integer', unique=True),\ + Field('doublef', 'double', unique=True,notnull=True),\ + Field('datef', 'date', default=datetime.date.today()),\ + Field('timef', 'time'),\ + Field('datetimef', 'datetime'),\ + migrate='test_user.table') + + Insert a field + + >>> db.users.insert(stringf='a', booleanf=True, passwordf='p', blobf='0A',\ + uploadf=None, integerf=5, doublef=3.14,\ + datef=datetime.date(2001, 1, 1),\ + timef=datetime.time(12, 30, 15),\ + datetimef=datetime.datetime(2002, 2, 2, 12, 30, 15)) + 1 + + Drop the table + + >>> db.users.drop() + + Examples of insert, select, update, delete + + >>> tmp = db.define_table('person',\ + Field('name'),\ + Field('birth','date'),\ + migrate='test_person.table') + >>> person_id = db.person.insert(name=\"Marco\",birth='2005-06-22') + >>> person_id = db.person.insert(name=\"Massimo\",birth='1971-12-21') + + commented len(db().select(db.person.ALL)) + commented 2 + + >>> me = db(db.person.id==person_id).select()[0] # test select + >>> me.name + 'Massimo' + >>> db(db.person.name=='Massimo').update(name='massimo') # test update + 1 + >>> db(db.person.name=='Marco').select().first().delete_record() # test delete + 1 + + Update a single record + + >>> me.update_record(name=\"Max\") + >>> me.name + 'Max' + + Examples of complex search conditions + + >>> len(db((db.person.name=='Max')&(db.person.birth<'2003-01-01')).select()) + 1 + >>> len(db((db.person.name=='Max')&(db.person.birth<datetime.date(2003,01,01))).select()) + 1 + >>> len(db((db.person.name=='Max')|(db.person.birth<'2003-01-01')).select()) + 1 + >>> me = db(db.person.id==person_id).select(db.person.name)[0] + >>> me.name + 'Max' + + Examples of search conditions using extract from date/datetime/time + + >>> len(db(db.person.birth.month()==12).select()) + 1 + >>> len(db(db.person.birth.year()>1900).select()) + 1 + + Example of usage of NULL + + >>> len(db(db.person.birth==None).select()) ### test NULL + 0 + >>> len(db(db.person.birth!=None).select()) ### test NULL + 1 + + Examples of search conditions using lower, upper, and like + + >>> len(db(db.person.name.upper()=='MAX').select()) + 1 + >>> len(db(db.person.name.like('%ax')).select()) + 1 + >>> len(db(db.person.name.upper().like('%AX')).select()) + 1 + >>> len(db(~db.person.name.upper().like('%AX')).select()) + 0 + + orderby, groupby and limitby + + >>> people = db().select(db.person.name, orderby=db.person.name) + >>> order = db.person.name|~db.person.birth + >>> people = db().select(db.person.name, orderby=order) + + >>> people = db().select(db.person.name, orderby=db.person.name, groupby=db.person.name) + + >>> people = db().select(db.person.name, orderby=order, limitby=(0,100)) + + Example of one 2 many relation + + >>> tmp = db.define_table('dog',\ + Field('name'),\ + Field('birth','date'),\ + Field('owner',db.person),\ + migrate='test_dog.table') + >>> db.dog.insert(name='Snoopy', birth=None, owner=person_id) + 1 + + A simple JOIN + + >>> len(db(db.dog.owner==db.person.id).select()) + 1 + + >>> len(db().select(db.person.ALL, db.dog.name,left=db.dog.on(db.dog.owner==db.person.id))) + 1 + + Drop tables + + >>> db.dog.drop() + >>> db.person.drop() + + Example of many 2 many relation and Set + + >>> tmp = db.define_table('author', Field('name'),\ + migrate='test_author.table') + >>> tmp = db.define_table('paper', Field('title'),\ + migrate='test_paper.table') + >>> tmp = db.define_table('authorship',\ + Field('author_id', db.author),\ + Field('paper_id', db.paper),\ + migrate='test_authorship.table') + >>> aid = db.author.insert(name='Massimo') + >>> pid = db.paper.insert(title='QCD') + >>> tmp = db.authorship.insert(author_id=aid, paper_id=pid) + + Define a Set + + >>> authored_papers = db((db.author.id==db.authorship.author_id)&(db.paper.id==db.authorship.paper_id)) + >>> rows = authored_papers.select(db.author.name, db.paper.title) + >>> for row in rows: print row.author.name, row.paper.title + Massimo QCD + + Example of search condition using belongs + + >>> set = (1, 2, 3) + >>> rows = db(db.paper.id.belongs(set)).select(db.paper.ALL) + >>> print rows[0].title + QCD + + Example of search condition using nested select + + >>> nested_select = db()._select(db.authorship.paper_id) + >>> rows = db(db.paper.id.belongs(nested_select)).select(db.paper.ALL) + >>> print rows[0].title + QCD + + Example of expressions + + >>> mynumber = db.define_table('mynumber', Field('x', 'integer')) + >>> db(mynumber.id>0).delete() + 0 + >>> for i in range(10): tmp = mynumber.insert(x=i) + >>> db(mynumber.id>0).select(mynumber.x.sum())[0](mynumber.x.sum()) + 45 + + >>> db(mynumber.x+2==5).select(mynumber.x + 2)[0](mynumber.x + 2) + 5 + + Output in csv + + >>> print str(authored_papers.select(db.author.name, db.paper.title)).strip() + author.name,paper.title\r + Massimo,QCD + + Delete all leftover tables + + >>> DAL.distributed_transaction_commit(db) + + >>> db.mynumber.drop() + >>> db.authorship.drop() + >>> db.author.drop() + >>> db.paper.drop() + """ +################################################################################ +# deprecated since the new DAL; here only for backward compatibility +################################################################################ + +SQLField = Field +SQLTable = Table +SQLXorable = Expression +SQLQuery = Query +SQLSet = Set +SQLRows = Rows +SQLStorage = Row +SQLDB = DAL +GQLDB = DAL +DAL.Field = Field # was necessary in gluon/globals.py session.connect +DAL.Table = Table # was necessary in gluon/globals.py session.connect + +################################################################################ +# run tests +################################################################################ + +if __name__ == '__main__': + import doctest + doctest.testmod() + + + ADDED gluon/debug.py Index: gluon/debug.py ================================================================== --- gluon/debug.py +++ gluon/debug.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +This file is part of the web2py Web Framework +Developed by Massimo Di Pierro <mdipierro@cs.depaul.edu>, +limodou <limodou@gmail.com> and srackham <srackham@gmail.com>. +License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) + +""" + +import logging +import pdb +import Queue +import sys + +logger = logging.getLogger("web2py") + +class Pipe(Queue.Queue): + def __init__(self, name, mode='r', *args, **kwargs): + self.__name = name + Queue.Queue.__init__(self, *args, **kwargs) + + def write(self, data): + logger.debug("debug %s writting %s" % (self.__name, data)) + self.put(data) + + def flush(self): + # mark checkpoint (complete message) + logger.debug("debug %s flushing..." % self.__name) + self.put(None) + # wait until it is processed + self.join() + logger.debug("debug %s flush done" % self.__name) + + def read(self, count=None, timeout=None): + logger.debug("debug %s reading..." % (self.__name, )) + data = self.get(block=True, timeout=timeout) + # signal that we are ready + self.task_done() + logger.debug("debug %s read %s" % (self.__name, data)) + return data + + def readline(self): + logger.debug("debug %s readline..." % (self.__name, )) + return self.read() + + +pipe_in = Pipe('in') +pipe_out = Pipe('out') + +debugger = pdb.Pdb(completekey=None, stdin=pipe_in, stdout=pipe_out,) + +def set_trace(): + "breakpoint shortcut (like pdb)" + logger.info("DEBUG: set_trace!") + debugger.set_trace(sys._getframe().f_back) + + +def stop_trace(): + "stop waiting for the debugger (called atexit)" + # this should prevent communicate is wait forever a command result + # and the main thread has finished + logger.info("DEBUG: stop_trace!") + pipe_out.write("debug finished!") + pipe_out.write(None) + #pipe_out.flush() + +def communicate(command=None): + "send command to debbuger, wait result" + if command is not None: + logger.info("DEBUG: sending command %s" % command) + pipe_in.write(command) + #pipe_in.flush() + result = [] + while True: + data = pipe_out.read() + if data is None: + break + result.append(data) + logger.info("DEBUG: result %s" % repr(result)) + return ''.join(result) + + + ADDED gluon/decoder.py Index: gluon/decoder.py ================================================================== --- gluon/decoder.py +++ gluon/decoder.py @@ -0,0 +1,75 @@ +import codecs, encodings + +"""Caller will hand this library a buffer and ask it to either convert +it or auto-detect the type. + +Based on http://code.activestate.com/recipes/52257/ + +Licensed under the PSF License +""" + +# None represents a potentially variable byte. "##" in the XML spec... +autodetect_dict={ # bytepattern : ("name", + (0x00, 0x00, 0xFE, 0xFF) : ("ucs4_be"), + (0xFF, 0xFE, 0x00, 0x00) : ("ucs4_le"), + (0xFE, 0xFF, None, None) : ("utf_16_be"), + (0xFF, 0xFE, None, None) : ("utf_16_le"), + (0x00, 0x3C, 0x00, 0x3F) : ("utf_16_be"), + (0x3C, 0x00, 0x3F, 0x00) : ("utf_16_le"), + (0x3C, 0x3F, 0x78, 0x6D): ("utf_8"), + (0x4C, 0x6F, 0xA7, 0x94): ("EBCDIC") + } + +def autoDetectXMLEncoding(buffer): + """ buffer -> encoding_name + The buffer should be at least 4 bytes long. + Returns None if encoding cannot be detected. + Note that encoding_name might not have an installed + decoder (e.g. EBCDIC) + """ + # a more efficient implementation would not decode the whole + # buffer at once but otherwise we'd have to decode a character at + # a time looking for the quote character...that's a pain + + encoding = "utf_8" # according to the XML spec, this is the default + # this code successively tries to refine the default + # whenever it fails to refine, it falls back to + # the last place encoding was set. + if len(buffer)>=4: + bytes = (byte1, byte2, byte3, byte4) = tuple(map(ord, buffer[0:4])) + enc_info = autodetect_dict.get(bytes, None) + if not enc_info: # try autodetection again removing potentially + # variable bytes + bytes = (byte1, byte2, None, None) + enc_info = autodetect_dict.get(bytes) + else: + enc_info = None + + if enc_info: + encoding = enc_info # we've got a guess... these are + #the new defaults + + # try to find a more precise encoding using xml declaration + secret_decoder_ring = codecs.lookup(encoding)[1] + (decoded,length) = secret_decoder_ring(buffer) + first_line = decoded.split("\n")[0] + if first_line and first_line.startswith(u"<?xml"): + encoding_pos = first_line.find(u"encoding") + if encoding_pos!=-1: + # look for double quote + quote_pos=first_line.find('"', encoding_pos) + + if quote_pos==-1: # look for single quote + quote_pos=first_line.find("'", encoding_pos) + + if quote_pos>-1: + quote_char,rest=(first_line[quote_pos], + first_line[quote_pos+1:]) + encoding=rest[:rest.find(quote_char)] + + return encoding + +def decoder(buffer): + encoding = autoDetectXMLEncoding(buffer) + return buffer.decode(encoding).encode('utf8') + ADDED gluon/fileutils.py Index: gluon/fileutils.py ================================================================== --- gluon/fileutils.py +++ gluon/fileutils.py @@ -0,0 +1,387 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +This file is part of the web2py Web Framework +Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> +License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) +""" + +import storage +import os +import re +import tarfile +import glob +import time +from http import HTTP +from gzip import open as gzopen +from settings import global_settings + + +__all__ = [ + 'read_file', + 'write_file', + 'readlines_file', + 'up', + 'abspath', + 'mktree', + 'listdir', + 'recursive_unlink', + 'cleanpath', + 'tar', + 'untar', + 'tar_compiled', + 'get_session', + 'check_credentials', + 'w2p_pack', + 'w2p_unpack', + 'w2p_pack_plugin', + 'w2p_unpack_plugin', + 'fix_newlines', + 'make_fake_file_like_object', + ] + +def read_file(filename, mode='r'): + "returns content from filename, making sure to close the file explicitly on exit." + f = open(filename, mode) + try: + return f.read() + finally: + f.close() + +def write_file(filename, value, mode='w'): + "writes <value> to filename, making sure to close the file explicitly on exit." + f = open(filename, mode) + try: + return f.write(value) + finally: + f.close() + +def readlines_file(filename, mode='r'): + "applies .split('\n') to the output of read_file()" + return read_file(filename, mode).split('\n') + +def abspath(*relpath, **base): + "convert relative path to absolute path based (by default) on applications_parent" + path = os.path.join(*relpath) + gluon = base.get('gluon', False) + if os.path.isabs(path): + return path + if gluon: + return os.path.join(global_settings.gluon_parent, path) + return os.path.join(global_settings.applications_parent, path) + + +def mktree(path): + head,tail =os.path.split(path) + if head: + if tail: mktree(head) + if not os.path.exists(head): + os.mkdir(head) + +def listdir( + path, + expression='^.+$', + drop=True, + add_dirs=False, + sort=True, + ): + """ + like os.listdir() but you can specify a regex pattern to filter files. + if add_dirs is True, the returned items will have the full path. + """ + if path[-1:] != os.path.sep: + path = path + os.path.sep + if drop: + n = len(path) + else: + n = 0 + regex = re.compile(expression) + items = [] + for (root, dirs, files) in os.walk(path, topdown=True): + for dir in dirs[:]: + if dir.startswith('.'): + dirs.remove(dir) + if add_dirs: + items.append(root[n:]) + for file in sorted(files): + if regex.match(file) and not file.startswith('.'): + items.append(os.path.join(root, file)[n:]) + if sort: + return sorted(items) + else: + return items + + +def recursive_unlink(f): + if os.path.isdir(f): + for s in os.listdir(f): + recursive_unlink(os.path.join(f,s)) + os.rmdir(f) + elif os.path.isfile(f): + os.unlink(f) + + +def cleanpath(path): + """ + turns any expression/path into a valid filename. replaces / with _ and + removes special characters. + """ + + items = path.split('.') + if len(items) > 1: + path = re.sub('[^\w\.]+', '_', '_'.join(items[:-1]) + '.' + + ''.join(items[-1:])) + else: + path = re.sub('[^\w\.]+', '_', ''.join(items[-1:])) + return path + + +def _extractall(filename, path='.', members=None): + if not hasattr(tarfile.TarFile, 'extractall'): + from tarfile import ExtractError + + class TarFile(tarfile.TarFile): + + def extractall(self, path='.', members=None): + """Extract all members from the archive to the current working + directory and set owner, modification time and permissions on + directories afterwards. `path' specifies a different directory + to extract to. `members' is optional and must be a subset of the + list returned by getmembers(). + """ + + directories = [] + if members is None: + members = self + for tarinfo in members: + if tarinfo.isdir(): + + # Extract directory with a safe mode, so that + # all files below can be extracted as well. + + try: + os.makedirs(os.path.join(path, + tarinfo.name), 0777) + except EnvironmentError: + pass + directories.append(tarinfo) + else: + self.extract(tarinfo, path) + + # Reverse sort directories. + + directories.sort(lambda a, b: cmp(a.name, b.name)) + directories.reverse() + + # Set correct owner, mtime and filemode on directories. + + for tarinfo in directories: + path = os.path.join(path, tarinfo.name) + try: + self.chown(tarinfo, path) + self.utime(tarinfo, path) + self.chmod(tarinfo, path) + except ExtractError, e: + if self.errorlevel > 1: + raise + else: + self._dbg(1, 'tarfile: %s' % e) + + + _cls = TarFile + else: + _cls = tarfile.TarFile + + tar = _cls(filename, 'r') + ret = tar.extractall(path, members) + tar.close() + return ret + +def tar(file, dir, expression='^.+$'): + """ + tars dir into file, only tars file that match expression + """ + + tar = tarfile.TarFile(file, 'w') + try: + for file in listdir(dir, expression, add_dirs=True): + tar.add(os.path.join(dir, file), file, False) + finally: + tar.close() + +def untar(file, dir): + """ + untar file into dir + """ + + _extractall(file, dir) + + +def w2p_pack(filename, path, compiled=False): + filename = abspath(filename) + path = abspath(path) + tarname = filename + '.tar' + if compiled: + tar_compiled(tarname, path, '^[\w\.\-]+$') + else: + tar(tarname, path, '^[\w\.\-]+$') + w2pfp = gzopen(filename, 'wb') + tarfp = open(tarname, 'rb') + w2pfp.write(tarfp.read()) + w2pfp.close() + tarfp.close() + os.unlink(tarname) + +def w2p_unpack(filename, path, delete_tar=True): + filename = abspath(filename) + path = abspath(path) + if filename[-4:] == '.w2p' or filename[-3:] == '.gz': + if filename[-4:] == '.w2p': + tarname = filename[:-4] + '.tar' + else: + tarname = filename[:-3] + '.tar' + fgzipped = gzopen(filename, 'rb') + tarfile = open(tarname, 'wb') + tarfile.write(fgzipped.read()) + tarfile.close() + fgzipped.close() + else: + tarname = filename + untar(tarname, path) + if delete_tar: + os.unlink(tarname) + + +def w2p_pack_plugin(filename, path, plugin_name): + """Pack the given plugin into a w2p file. + Will match files at: + <path>/*/plugin_[name].* + <path>/*/plugin_[name]/* + """ + filename = abspath(filename) + path = abspath(path) + if not filename.endswith('web2py.plugin.%s.w2p' % plugin_name): + raise Exception, "Not a web2py plugin name" + plugin_tarball = tarfile.open(filename, 'w:gz') + try: + app_dir = path + while app_dir[-1]=='/': + app_dir = app_dir[:-1] + files1=glob.glob(os.path.join(app_dir,'*/plugin_%s.*' % plugin_name)) + files2=glob.glob(os.path.join(app_dir,'*/plugin_%s/*' % plugin_name)) + for file in files1+files2: + plugin_tarball.add(file, arcname=file[len(app_dir)+1:]) + finally: + plugin_tarball.close() + + +def w2p_unpack_plugin(filename, path, delete_tar=True): + filename = abspath(filename) + path = abspath(path) + if not os.path.basename(filename).startswith('web2py.plugin.'): + raise Exception, "Not a web2py plugin" + w2p_unpack(filename,path,delete_tar) + + +def tar_compiled(file, dir, expression='^.+$'): + """ + used to tar a compiled application. + the content of models, views, controllers is not stored in the tar file. + """ + + tar = tarfile.TarFile(file, 'w') + for file in listdir(dir, expression, add_dirs=True): + filename = os.path.join(dir, file) + if os.path.islink(filename): + continue + if os.path.isfile(filename) and file[-4:] != '.pyc': + if file[:6] == 'models': + continue + if file[:5] == 'views': + continue + if file[:11] == 'controllers': + continue + if file[:7] == 'modules': + continue + tar.add(filename, file, False) + tar.close() + +def up(path): + return os.path.dirname(os.path.normpath(path)) + + +def get_session(request, other_application='admin'): + """ checks that user is authorized to access other_application""" + if request.application == other_application: + raise KeyError + try: + session_id = request.cookies['session_id_' + other_application].value + osession = storage.load_storage(os.path.join( + up(request.folder), other_application, 'sessions', session_id)) + except: + osession = storage.Storage() + return osession + + +def check_credentials(request, other_application='admin', expiration = 60*60): + """ checks that user is authorized to access other_application""" + if request.env.web2py_runtime_gae: + from google.appengine.api import users + if users.is_current_user_admin(): + return True + else: + login_html = '<a href="%s">Sign in with your google account</a>.' \ + % users.create_login_url(request.env.path_info) + raise HTTP(200, '<html><body>%s</body></html>' % login_html) + else: + dt = time.time() - expiration + s = get_session(request, other_application) + return (s.authorized and s.last_time and s.last_time > dt) + + +def fix_newlines(path): + regex = re.compile(r'''(\r +|\r| +)''') + for filename in listdir(path, '.*\.(py|html)$', drop=False): + rdata = read_file(filename, 'rb') + wdata = regex.sub('\n', rdata) + if wdata != rdata: + write_file(filename, wdata, 'wb') + +def copystream( + src, + dest, + size, + chunk_size=10 ** 5, + ): + """ + this is here because I think there is a bug in shutil.copyfileobj + """ + while size > 0: + if size < chunk_size: + data = src.read(size) + else: + data = src.read(chunk_size) + length = len(data) + if length > size: + (data, length) = (data[:size], size) + size -= length + if length == 0: + break + dest.write(data) + if length < chunk_size: + break + dest.seek(0) + return + + +def make_fake_file_like_object(): + class LogFile(object): + def write(self, value): + pass + def close(self): + pass + return LogFile() + ADDED gluon/globals.py Index: gluon/globals.py ================================================================== --- gluon/globals.py +++ gluon/globals.py @@ -0,0 +1,528 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +This file is part of the web2py Web Framework +Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> +License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) + +Contains the classes for the global used variables: + +- Request +- Response +- Session + +""" + +from storage import Storage, List +from streamer import streamer, stream_file_or_304_or_206, DEFAULT_CHUNK_SIZE +from xmlrpc import handler +from contenttype import contenttype +from html import xmlescape, TABLE, TR, PRE +from http import HTTP +from fileutils import up +from serializers import json, custom_json +import settings +from utils import web2py_uuid +from settings import global_settings + +import hashlib +import portalocker +import cPickle +import cStringIO +import datetime +import re +import Cookie +import os +import sys +import traceback +import threading + +regex_session_id = re.compile('^([\w\-]+/)?[\w\-\.]+$') + +__all__ = ['Request', 'Response', 'Session'] + +current = threading.local() # thread-local storage for request-scope globals + +class Request(Storage): + + """ + defines the request object and the default values of its members + + - env: environment variables, by gluon.main.wsgibase() + - cookies + - get_vars + - post_vars + - vars + - folder + - application + - function + - args + - extension + - now: datetime.datetime.today() + - restful() + """ + + def __init__(self): + self.wsgi = Storage() # hooks to environ and start_response + self.env = Storage() + self.cookies = Cookie.SimpleCookie() + self.get_vars = Storage() + self.post_vars = Storage() + self.vars = Storage() + self.folder = None + self.application = None + self.function = None + self.args = List() + self.extension = None + self.now = datetime.datetime.now() + self.is_restful = False + self.is_https = False + self.is_local = False + + def compute_uuid(self): + self.uuid = '%s/%s.%s.%s' % ( + self.application, + self.client.replace(':', '_'), + self.now.strftime('%Y-%m-%d.%H-%M-%S'), + web2py_uuid()) + return self.uuid + + def user_agent(self): + from gluon.contrib import user_agent_parser + session = current.session + session._user_agent = session._user_agent or \ + user_agent_parser.detect(self.env.http_user_agent) + return session._user_agent + + def restful(self): + def wrapper(action,self=self): + def f(_action=action,_self=self,*a,**b): + self.is_restful = True + method = _self.env.request_method + if len(_self.args) and '.' in _self.args[-1]: + _self.args[-1],_self.extension = _self.args[-1].rsplit('.',1) + if not method in ['GET','POST','DELETE','PUT']: + raise HTTP(400,"invalid method") + rest_action = _action().get(method,None) + if not rest_action: + raise HTTP(400,"method not supported") + try: + return rest_action(*_self.args,**_self.vars) + except TypeError, e: + exc_type, exc_value, exc_traceback = sys.exc_info() + if len(traceback.extract_tb(exc_traceback))==1: + raise HTTP(400,"invalid arguments") + else: + raise e + f.__doc__ = action.__doc__ + f.__name__ = action.__name__ + return f + return wrapper + + +class Response(Storage): + + """ + defines the response object and the default values of its members + response.write( ) can be used to write in the output html + """ + + def __init__(self): + self.status = 200 + self.headers = Storage() + self.headers['X-Powered-By'] = 'web2py' + self.body = cStringIO.StringIO() + self.session_id = None + self.cookies = Cookie.SimpleCookie() + self.postprocessing = [] + self.flash = '' # used by the default view layout + self.meta = Storage() # used by web2py_ajax.html + self.menu = [] # used by the default view layout + self.files = [] # used by web2py_ajax.html + self.generic_patterns = [] # patterns to allow generic views + self._vars = None + self._caller = lambda f: f() + self._view_environment = None + self._custom_commit = None + self._custom_rollback = None + + def write(self, data, escape=True): + if not escape: + self.body.write(str(data)) + else: + self.body.write(xmlescape(data)) + + def render(self, *a, **b): + from compileapp import run_view_in + if len(a) > 2: + raise SyntaxError, 'Response.render can be called with two arguments, at most' + elif len(a) == 2: + (view, self._vars) = (a[0], a[1]) + elif len(a) == 1 and isinstance(a[0], str): + (view, self._vars) = (a[0], {}) + elif len(a) == 1 and hasattr(a[0], 'read') and callable(a[0].read): + (view, self._vars) = (a[0], {}) + elif len(a) == 1 and isinstance(a[0], dict): + (view, self._vars) = (None, a[0]) + else: + (view, self._vars) = (None, {}) + self._vars.update(b) + self._view_environment.update(self._vars) + if view: + import cStringIO + (obody, oview) = (self.body, self.view) + (self.body, self.view) = (cStringIO.StringIO(), view) + run_view_in(self._view_environment) + page = self.body.getvalue() + self.body.close() + (self.body, self.view) = (obody, oview) + else: + run_view_in(self._view_environment) + page = self.body.getvalue() + return page + + def stream( + self, + stream, + chunk_size = DEFAULT_CHUNK_SIZE, + request=None, + ): + """ + if a controller function:: + + return response.stream(file, 100) + + the file content will be streamed at 100 bytes at the time + """ + + if isinstance(stream, (str, unicode)): + stream_file_or_304_or_206(stream, + chunk_size=chunk_size, + request=request, + headers=self.headers) + + # ## the following is for backward compatibility + + if hasattr(stream, 'name'): + filename = stream.name + else: + filename = None + keys = [item.lower() for item in self.headers] + if filename and not 'content-type' in keys: + self.headers['Content-Type'] = contenttype(filename) + if filename and not 'content-length' in keys: + try: + self.headers['Content-Length'] = \ + os.path.getsize(filename) + except OSError: + pass + if request and request.env.web2py_use_wsgi_file_wrapper: + wrapped = request.env.wsgi_file_wrapper(stream, chunk_size) + else: + wrapped = streamer(stream, chunk_size=chunk_size) + return wrapped + + def download(self, request, db, chunk_size = DEFAULT_CHUNK_SIZE, attachment=True): + """ + example of usage in controller:: + + def download(): + return response.download(request, db) + + downloads from http://..../download/filename + """ + + import contenttype as c + if not request.args: + raise HTTP(404) + name = request.args[-1] + items = re.compile('(?P<table>.*?)\.(?P<field>.*?)\..*')\ + .match(name) + if not items: + raise HTTP(404) + (t, f) = (items.group('table'), items.group('field')) + field = db[t][f] + try: + (filename, stream) = field.retrieve(name) + except IOError: + raise HTTP(404) + self.headers['Content-Type'] = c.contenttype(name) + if attachment: + self.headers['Content-Disposition'] = \ + "attachment; filename=%s" % filename + return self.stream(stream, chunk_size = chunk_size, request=request) + + def json(self, data, default=None): + return json(data, default = default or custom_json) + + def xmlrpc(self, request, methods): + """ + assuming:: + + def add(a, b): + return a+b + + if a controller function \"func\":: + + return response.xmlrpc(request, [add]) + + the controller will be able to handle xmlrpc requests for + the add function. Example:: + + import xmlrpclib + connection = xmlrpclib.ServerProxy('http://hostname/app/contr/func') + print connection.add(3, 4) + + """ + + return handler(request, self, methods) + + def toolbar(self): + from html import DIV, SCRIPT, BEAUTIFY, TAG, URL + BUTTON = TAG.button + admin = URL("admin","default","design", + args=current.request.application) + from gluon.dal import thread + dbstats = [TABLE(*[TR(PRE(row[0]),'%.2fms' % (row[1]*1000)) \ + for row in i.db._timings]) \ + for i in thread.instances] + u = web2py_uuid() + return DIV( + BUTTON('design',_onclick="document.location='%s'" % admin), + BUTTON('request',_onclick="jQuery('#request-%s').slideToggle()"%u), + DIV(BEAUTIFY(current.request),_class="hidden",_id="request-%s"%u), + BUTTON('session',_onclick="jQuery('#session-%s').slideToggle()"%u), + DIV(BEAUTIFY(current.session),_class="hidden",_id="session-%s"%u), + BUTTON('response',_onclick="jQuery('#response-%s').slideToggle()"%u), + DIV(BEAUTIFY(current.response),_class="hidden",_id="response-%s"%u), + BUTTON('db stats',_onclick="jQuery('#db-stats-%s').slideToggle()"%u), + DIV(BEAUTIFY(dbstats),_class="hidden",_id="db-stats-%s"%u), + SCRIPT("jQuery('.hidden').hide()") + ) + +class Session(Storage): + + """ + defines the session object and the default values of its members (None) + """ + + def connect( + self, + request, + response, + db=None, + tablename='web2py_session', + masterapp=None, + migrate=True, + separate = None, + check_client=False, + ): + """ + separate can be separate=lambda(session_name): session_name[-2:] + and it is used to determine a session prefix. + separate can be True and it is set to session_name[-2:] + """ + if separate == True: + separate = lambda session_name: session_name[-2:] + self._unlock(response) + if not masterapp: + masterapp = request.application + response.session_id_name = 'session_id_%s' % masterapp.lower() + + if not db: + if global_settings.db_sessions is True or masterapp in global_settings.db_sessions: + return + response.session_new = False + client = request.client.replace(':', '.') + if response.session_id_name in request.cookies: + response.session_id = \ + request.cookies[response.session_id_name].value + if regex_session_id.match(response.session_id): + response.session_filename = \ + os.path.join(up(request.folder), masterapp, + 'sessions', response.session_id) + else: + response.session_id = None + if response.session_id: + try: + response.session_file = \ + open(response.session_filename, 'rb+') + try: + portalocker.lock(response.session_file, + portalocker.LOCK_EX) + response.session_locked = True + self.update(cPickle.load(response.session_file)) + response.session_file.seek(0) + oc = response.session_filename.split('/')[-1].split('-')[0] + if check_client and client!=oc: + raise Exception, "cookie attack" + finally: + pass + #This causes admin login to break. Must find out why. + #self._close(response) + except: + response.session_id = None + if not response.session_id: + uuid = web2py_uuid() + response.session_id = '%s-%s' % (client, uuid) + if separate: + prefix = separate(response.session_id) + response.session_id = '%s/%s' % (prefix,response.session_id) + response.session_filename = \ + os.path.join(up(request.folder), masterapp, + 'sessions', response.session_id) + response.session_new = True + else: + if global_settings.db_sessions is not True: + global_settings.db_sessions.add(masterapp) + response.session_db = True + if response.session_file: + self._close(response) + if settings.global_settings.web2py_runtime_gae: + # in principle this could work without GAE + request.tickets_db = db + if masterapp == request.application: + table_migrate = migrate + else: + table_migrate = False + tname = tablename + '_' + masterapp + table = db.get(tname, None) + if table is None: + table = db.define_table( + tname, + db.Field('locked', 'boolean', default=False), + db.Field('client_ip', length=64), + db.Field('created_datetime', 'datetime', + default=request.now), + db.Field('modified_datetime', 'datetime'), + db.Field('unique_key', length=64), + db.Field('session_data', 'blob'), + migrate=table_migrate, + ) + try: + key = request.cookies[response.session_id_name].value + (record_id, unique_key) = key.split(':') + if record_id == '0': + raise Exception, 'record_id == 0' + rows = db(table.id == record_id).select() + if len(rows) == 0 or rows[0].unique_key != unique_key: + raise Exception, 'No record' + + # rows[0].update_record(locked=True) + + session_data = cPickle.loads(rows[0].session_data) + self.update(session_data) + except Exception: + record_id = None + unique_key = web2py_uuid() + session_data = {} + response._dbtable_and_field = \ + (response.session_id_name, table, record_id, unique_key) + response.session_id = '%s:%s' % (record_id, unique_key) + response.cookies[response.session_id_name] = response.session_id + response.cookies[response.session_id_name]['path'] = '/' + self.__hash = hashlib.md5(str(self)).digest() + if self.flash: + (response.flash, self.flash) = (self.flash, None) + + def is_new(self): + if self._start_timestamp: + return False + else: + self._start_timestamp = datetime.datetime.today() + return True + + def is_expired(self, seconds = 3600): + now = datetime.datetime.today() + if not self._last_timestamp or \ + self._last_timestamp + datetime.timedelta(seconds = seconds) > now: + self._last_timestamp = now + return False + else: + return True + + def secure(self): + self._secure = True + + def forget(self, response=None): + self._close(response) + self._forget = True + + def _try_store_in_db(self, request, response): + + # don't save if file-based sessions, no session id, or session being forgotten + if not response.session_db or not response.session_id or self._forget: + return + + # don't save if no change to session + __hash = self.__hash + if __hash is not None: + del self.__hash + if __hash == hashlib.md5(str(self)).digest(): + return + + (record_id_name, table, record_id, unique_key) = \ + response._dbtable_and_field + dd = dict(locked=False, client_ip=request.env.remote_addr, + modified_datetime=request.now, + session_data=cPickle.dumps(dict(self)), + unique_key=unique_key) + if record_id: + table._db(table.id == record_id).update(**dd) + else: + record_id = table.insert(**dd) + response.cookies[response.session_id_name] = '%s:%s'\ + % (record_id, unique_key) + response.cookies[response.session_id_name]['path'] = '/' + + def _try_store_on_disk(self, request, response): + + # don't save if sessions not not file-based + if response.session_db: + return + + # don't save if no change to session + __hash = self.__hash + if __hash is not None: + del self.__hash + if __hash == hashlib.md5(str(self)).digest(): + self._close(response) + return + + if not response.session_id or self._forget: + self._close(response) + return + + if response.session_new: + # Tests if the session sub-folder exists, if not, create it + session_folder = os.path.dirname(response.session_filename) + if not os.path.exists(session_folder): + os.mkdir(session_folder) + response.session_file = open(response.session_filename, 'wb') + portalocker.lock(response.session_file, portalocker.LOCK_EX) + response.session_locked = True + + if response.session_file: + cPickle.dump(dict(self), response.session_file) + response.session_file.truncate() + self._close(response) + + def _unlock(self, response): + if response and response.session_file and response.session_locked: + try: + portalocker.unlock(response.session_file) + response.session_locked = False + except: ### this should never happen but happens in Windows + pass + + def _close(self, response): + if response and response.session_file: + self._unlock(response) + try: + response.session_file.close() + del response.session_file + except: + pass + + ADDED gluon/highlight.py Index: gluon/highlight.py ================================================================== --- gluon/highlight.py +++ gluon/highlight.py @@ -0,0 +1,334 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +This file is part of the web2py Web Framework +Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> +License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) +""" + +import re +import cgi + +__all__ = ['highlight'] + + +class Highlighter(object): + + """ + Do syntax highlighting. + """ + + def __init__( + self, + mode, + link=None, + styles={}, + ): + """ + Initialise highlighter: + mode = language (PYTHON, WEB2PY,C, CPP, HTML, HTML_PLAIN) + """ + + mode = mode.upper() + if link and link[-1] != '/': + link = link + '/' + self.link = link + self.styles = styles + self.output = [] + self.span_style = None + if mode == 'WEB2PY': + (mode, self.suppress_tokens) = ('PYTHON', []) + elif mode == 'PYTHON': + self.suppress_tokens = ['GOTOHTML'] + elif mode == 'CPP': + (mode, self.suppress_tokens) = ('C', []) + elif mode == 'C': + self.suppress_tokens = ['CPPKEYWORD'] + elif mode == 'HTML_PLAIN': + (mode, self.suppress_tokens) = ('HTML', ['GOTOPYTHON']) + elif mode == 'HTML': + self.suppress_tokens = [] + else: + raise SyntaxError, 'Unknown mode: %s' % mode + self.mode = mode + + def c_tokenizer( + self, + token, + match, + style, + ): + """ + Callback for C specific highlighting. + """ + + value = cgi.escape(match.group()) + self.change_style(token, style) + self.output.append(value) + + def python_tokenizer( + self, + token, + match, + style, + ): + """ + Callback for python specific highlighting. + """ + + value = cgi.escape(match.group()) + if token == 'MULTILINESTRING': + self.change_style(token, style) + self.output.append(value) + self.strMultilineString = match.group(1) + return 'PYTHONMultilineString' + elif token == 'ENDMULTILINESTRING': + if match.group(1) == self.strMultilineString: + self.output.append(value) + self.strMultilineString = '' + return 'PYTHON' + if style and style[:5] == 'link:': + self.change_style(None, None) + (url, style) = style[5:].split(';', 1) + if url == 'None' or url == '': + self.output.append('<span style="%s">%s</span>' + % (style, value)) + else: + self.output.append('<a href="%s%s" style="%s">%s</a>' + % (url, value, style, value)) + else: + self.change_style(token, style) + self.output.append(value) + if token == 'GOTOHTML': + return 'HTML' + return None + + def html_tokenizer( + self, + token, + match, + style, + ): + """ + Callback for HTML specific highlighting. + """ + + value = cgi.escape(match.group()) + self.change_style(token, style) + self.output.append(value) + if token == 'GOTOPYTHON': + return 'PYTHON' + return None + + all_styles = { + 'C': (c_tokenizer, ( + ('COMMENT', re.compile(r'//.*\r?\n'), + 'color: green; font-style: italic'), + ('MULTILINECOMMENT', re.compile(r'/\*.*?\*/', re.DOTALL), + 'color: green; font-style: italic'), + ('PREPROCESSOR', re.compile(r'\s*#.*?[^\\]\s*\n', + re.DOTALL), 'color: magenta; font-style: italic'), + ('PUNC', re.compile(r'[-+*!&|^~/%\=<>\[\]{}(),.:]'), + 'font-weight: bold'), + ('NUMBER', + re.compile(r'0x[0-9a-fA-F]+|[+-]?\d+(\.\d+)?([eE][+-]\d+)?|\d+'), + 'color: red'), + ('KEYWORD', re.compile(r'(sizeof|int|long|short|char|void|' + + r'signed|unsigned|float|double|' + + r'goto|break|return|continue|asm|' + + r'case|default|if|else|switch|while|for|do|' + + r'struct|union|enum|typedef|' + + r'static|register|auto|volatile|extern|const)(?![a-zA-Z0-9_])'), + 'color:#185369; font-weight: bold'), + ('CPPKEYWORD', + re.compile(r'(class|private|protected|public|template|new|delete|' + + r'this|friend|using|inline|export|bool|throw|try|catch|' + + r'operator|typeid|virtual)(?![a-zA-Z0-9_])'), + 'color: blue; font-weight: bold'), + ('STRING', re.compile(r'r?u?\'(.*?)(?<!\\)\'|"(.*?)(?<!\\)"'), + 'color: #FF9966'), + ('IDENTIFIER', re.compile(r'[a-zA-Z_][a-zA-Z0-9_]*'), + None), + ('WHITESPACE', re.compile(r'[ \r\n]+'), 'Keep'), + )), + 'PYTHON': (python_tokenizer, ( + ('GOTOHTML', re.compile(r'\}\}'), 'color: red'), + ('PUNC', re.compile(r'[-+*!|&^~/%\=<>\[\]{}(),.:]'), + 'font-weight: bold'), + ('NUMBER', + re.compile(r'0x[0-9a-fA-F]+|[+-]?\d+(\.\d+)?([eE][+-]\d+)?|\d+' + ), 'color: red'), + ('KEYWORD', + re.compile(r'(def|class|break|continue|del|exec|finally|pass|' + + r'print|raise|return|try|except|global|assert|lambda|' + + r'yield|for|while|if|elif|else|and|in|is|not|or|import|' + + r'from|True|False)(?![a-zA-Z0-9_])'), + 'color:#185369; font-weight: bold'), + ('WEB2PY', + re.compile(r'(request|response|session|cache|redirect|local_import|HTTP|TR|XML|URL|BEAUTIFY|A|BODY|BR|B|CAT|CENTER|CODE|DIV|EM|EMBED|FIELDSET|LEGEND|FORM|H1|H2|H3|H4|H5|H6|IFRAME|HEAD|HR|HTML|I|IMG|INPUT|LABEL|LI|LINK|MARKMIN|MENU|META|OBJECT|OL|ON|OPTION|P|PRE|SCRIPT|SELECT|SPAN|STYLE|TABLE|THEAD|TBODY|TFOOT|TAG|TD|TEXTAREA|TH|TITLE|TT|T|UL|XHTML|IS_SLUG|IS_STRONG|IS_LOWER|IS_UPPER|IS_ALPHANUMERIC|IS_DATETIME|IS_DATETIME_IN_RANGE|IS_DATE|IS_DATE_IN_RANGE|IS_DECIMAL_IN_RANGE|IS_EMAIL|IS_EXPR|IS_FLOAT_IN_RANGE|IS_IMAGE|IS_INT_IN_RANGE|IS_IN_SET|IS_IPV4|IS_LIST_OF|IS_LENGTH|IS_MATCH|IS_EQUAL_TO|IS_EMPTY_OR|IS_NULL_OR|IS_NOT_EMPTY|IS_TIME|IS_UPLOAD_FILENAME|IS_URL|CLEANUP|CRYPT|IS_IN_DB|IS_NOT_IN_DB|DAL|Field|SQLFORM|SQLTABLE|xmlescape|embed64)(?![a-zA-Z0-9_])' + ), 'link:%(link)s;text-decoration:None;color:#FF5C1F;'), + ('MAGIC', re.compile(r'self|None'), + 'color:#185369; font-weight: bold'), + ('MULTILINESTRING', re.compile(r'r?u?(\'\'\'|""")'), + 'color: #FF9966'), + ('STRING', re.compile(r'r?u?\'(.*?)(?<!\\)\'|"(.*?)(?<!\\)"' + ), 'color: #FF9966'), + ('IDENTIFIER', re.compile(r'[a-zA-Z_][a-zA-Z0-9_]*'), + None), + ('COMMENT', re.compile(r'\#.*\r?\n'), + 'color: green; font-style: italic'), + ('WHITESPACE', re.compile(r'[ \r\n]+'), 'Keep'), + )), + 'PYTHONMultilineString': (python_tokenizer, + (('ENDMULTILINESTRING', + re.compile(r'.*?("""|\'\'\')', + re.DOTALL), 'color: darkred'), )), + 'HTML': (html_tokenizer, ( + ('GOTOPYTHON', re.compile(r'\{\{'), 'color: red'), + ('COMMENT', re.compile(r'<!--[^>]*-->|<!>'), + 'color: green; font-style: italic'), + ('XMLCRAP', re.compile(r'<![^>]*>'), + 'color: blue; font-style: italic'), + ('SCRIPT', re.compile(r'<script .*?</script>', re.IGNORECASE + + re.DOTALL), 'color: black'), + ('TAG', re.compile(r'</?\s*[a-zA-Z0-9]+'), + 'color: darkred; font-weight: bold'), + ('ENDTAG', re.compile(r'/?>'), + 'color: darkred; font-weight: bold'), + )), + } + + def highlight(self, data): + """ + Syntax highlight some python code. + Returns html version of code. + """ + + i = 0 + mode = self.mode + while i < len(data): + for (token, o_re, style) in Highlighter.all_styles[mode][1]: + if not token in self.suppress_tokens: + match = o_re.match(data, i) + if match: + if style: + new_mode = \ + Highlighter.all_styles[mode][0](self, + token, match, style + % dict(link=self.link)) + else: + new_mode = \ + Highlighter.all_styles[mode][0](self, + token, match, style) + if new_mode != None: + mode = new_mode + i += max(1, len(match.group())) + break + else: + self.change_style(None, None) + self.output.append(data[i]) + i += 1 + self.change_style(None, None) + return ''.join(self.output).expandtabs(4) + + def change_style(self, token, style): + """ + Generate output to change from existing style to another style only. + """ + + if token in self.styles: + style = self.styles[token] + if self.span_style != style: + if style != 'Keep': + if self.span_style != None: + self.output.append('</span>') + if style != None: + self.output.append('<span style="%s">' % style) + self.span_style = style + + +def highlight( + code, + language, + link='/examples/globals/vars/', + counter=1, + styles={}, + highlight_line=None, + attributes={}, + ): + if not 'CODE' in styles: + code_style = """ + font-size: 11px; + font-family: Bitstream Vera Sans Mono,monospace; + background-color: transparent; + margin: 0; + padding: 5px; + border: none; + overflow: auto; + white-space: pre !important;\n""" + else: + code_style = styles['CODE'] + if not 'LINENUMBERS' in styles: + linenumbers_style = """ + font-size: 11px; + font-family: Bitstream Vera Sans Mono,monospace; + background-color: transparent; + margin: 0; + padding: 5px; + border: none; + color: #A0A0A0;\n""" + else: + linenumbers_style = styles['LINENUMBERS'] + if not 'LINEHIGHLIGHT' in styles: + linehighlight_style = "background-color: #EBDDE2;" + else: + linehighlight_style = styles['LINEHIGHLIGHT'] + + if language and language.upper() in ['PYTHON', 'C', 'CPP', 'HTML', + 'WEB2PY']: + code = Highlighter(language, link, styles).highlight(code) + else: + code = cgi.escape(code) + lines = code.split('\n') + + if counter is None: + linenumbers = [''] * len(lines) + elif isinstance(counter, str): + linenumbers = [cgi.escape(counter)] * len(lines) + else: + linenumbers = [str(i + counter) + '.' for i in + xrange(len(lines))] + + if highlight_line: + if counter and not isinstance(counter, str): + lineno = highlight_line - counter + else: + lineno = highlight_line + if lineno<len(lines): + lines[lineno] = '<div style="%s">%s</div>' % (linehighlight_style, lines[lineno]) + linenumbers[lineno] = '<div style="%s">%s</div>' % (linehighlight_style, linenumbers[lineno]) + + code = '<br/>'.join(lines) + numbers = '<br/>'.join(linenumbers) + + items = attributes.items() + fa = ' '.join([key[1:].lower() for (key, value) in items if key[:1] + == '_' and value == None] + ['%s="%s"' + % (key[1:].lower(), str(value).replace('"', "'")) + for (key, value) in attributes.items() if key[:1] + == '_' and value]) + if fa: + fa = ' ' + fa + return '<table%s><tr valign="top"><td style="width:40px; text-align: right;"><pre style="%s">%s</pre></td><td><pre style="%s">%s</pre></td></tr></table>'\ + % (fa, linenumbers_style, numbers, code_style, code) + + +if __name__ == '__main__': + import sys + argfp = open(sys.argv[1]) + data = argfp.read() + argfp.close() + print '<html><body>' + highlight(data, sys.argv[2])\ + + '</body></html>' + ADDED gluon/html.py Index: gluon/html.py ================================================================== --- gluon/html.py +++ gluon/html.py @@ -0,0 +1,2239 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +This file is part of the web2py Web Framework +Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> +License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) +""" + +import cgi +import os +import re +import copy +import types +import urllib +import base64 +import sanitizer +import rewrite +import itertools +import decoder +import copy_reg +import cPickle +import marshal +from HTMLParser import HTMLParser +from htmlentitydefs import name2codepoint +from contrib.markmin.markmin2html import render + +from storage import Storage +from highlight import highlight +from utils import web2py_uuid, hmac_hash + +import hmac +import hashlib + +regex_crlf = re.compile('\r|\n') + +join = ''.join + +__all__ = [ + 'A', + 'B', + 'BEAUTIFY', + 'BODY', + 'BR', + 'BUTTON', + 'CENTER', + 'CAT', + 'CODE', + 'DIV', + 'EM', + 'EMBED', + 'FIELDSET', + 'FORM', + 'H1', + 'H2', + 'H3', + 'H4', + 'H5', + 'H6', + 'HEAD', + 'HR', + 'HTML', + 'I', + 'IFRAME', + 'IMG', + 'INPUT', + 'LABEL', + 'LEGEND', + 'LI', + 'LINK', + 'OL', + 'UL', + 'MARKMIN', + 'MENU', + 'META', + 'OBJECT', + 'ON', + 'OPTION', + 'P', + 'PRE', + 'SCRIPT', + 'OPTGROUP', + 'SELECT', + 'SPAN', + 'STYLE', + 'TABLE', + 'TAG', + 'TD', + 'TEXTAREA', + 'TH', + 'THEAD', + 'TBODY', + 'TFOOT', + 'TITLE', + 'TR', + 'TT', + 'URL', + 'XHTML', + 'XML', + 'xmlescape', + 'embed64', + ] + + +def xmlescape(data, quote = True): + """ + returns an escaped string of the provided data + + :param data: the data to be escaped + :param quote: optional (default False) + """ + + # first try the xml function + if hasattr(data,'xml') and callable(data.xml): + return data.xml() + + # otherwise, make it a string + if not isinstance(data, (str, unicode)): + data = str(data) + elif isinstance(data, unicode): + data = data.encode('utf8', 'xmlcharrefreplace') + + # ... and do the escaping + data = cgi.escape(data, quote).replace("'","'") + return data + + +def URL( + a=None, + c=None, + f=None, + r=None, + args=[], + vars={}, + anchor='', + extension=None, + env=None, + hmac_key=None, + hash_vars=True, + salt=None, + user_signature=None, + scheme=None, + host=None, + port=None, + ): + """ + generate a URL + + example:: + + >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'], + ... vars={'p':1, 'q':2}, anchor='1')) + '/a/c/f/x/y/z?p=1&q=2#1' + + >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'], + ... vars={'p':(1,3), 'q':2}, anchor='1')) + '/a/c/f/x/y/z?p=1&p=3&q=2#1' + + >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'], + ... vars={'p':(3,1), 'q':2}, anchor='1')) + '/a/c/f/x/y/z?p=3&p=1&q=2#1' + + >>> str(URL(a='a', c='c', f='f', anchor='1+2')) + '/a/c/f#1%2B2' + + >>> str(URL(a='a', c='c', f='f', args=['x', 'y', 'z'], + ... vars={'p':(1,3), 'q':2}, anchor='1', hmac_key='key')) + '/a/c/f/x/y/z?p=1&p=3&q=2&_signature=5d06bb8a4a6093dd325da2ee591c35c61afbd3c6#1' + + generates a url '/a/c/f' corresponding to application a, controller c + and function f. If r=request is passed, a, c, f are set, respectively, + to r.application, r.controller, r.function. + + The more typical usage is: + + URL(r=request, f='index') that generates a url for the index function + within the present application and controller. + + :param a: application (default to current if r is given) + :param c: controller (default to current if r is given) + :param f: function (default to current if r is given) + :param r: request (optional) + :param args: any arguments (optional) + :param vars: any variables (optional) + :param anchor: anchorname, without # (optional) + :param hmac_key: key to use when generating hmac signature (optional) + :param hash_vars: which of the vars to include in our hmac signature + True (default) - hash all vars, False - hash none of the vars, + iterable - hash only the included vars ['key1','key2'] + :param scheme: URI scheme (True, 'http' or 'https', etc); forces absolute URL (optional) + :param host: string to force absolute URL with host (True means http_host) + :param port: optional port number (forces absolute URL) + + :raises SyntaxError: when no application, controller or function is + available + :raises SyntaxError: when a CRLF is found in the generated url + """ + + if args in (None,[]): args = [] + vars = vars or {} + application = None + controller = None + function = None + + if not r: + if a and not c and not f: (f,a,c)=(a,c,f) + elif a and c and not f: (c,f,a)=(a,c,f) + from globals import current + if hasattr(current,'request'): + r = current.request + if r: + application = r.application + controller = r.controller + function = r.function + env = r.env + if extension is None and r.extension != 'html': + extension = r.extension + if a: + application = a + if c: + controller = c + if f: + if not isinstance(f, str): + function = f.__name__ + elif '.' in f: + function, extension = f.split('.', 1) + else: + function = f + + function2 = '%s.%s' % (function,extension or 'html') + + if not (application and controller and function): + raise SyntaxError, 'not enough information to build the url' + + if not isinstance(args, (list, tuple)): + args = [args] + other = args and urllib.quote('/' + '/'.join([str(x) for x in args])) or '' + if other.endswith('/'): + other += '/' # add trailing slash to make last trailing empty arg explicit + + if vars.has_key('_signature'): vars.pop('_signature') + list_vars = [] + for (key, vals) in sorted(vars.items()): + if not isinstance(vals, (list, tuple)): + vals = [vals] + for val in vals: + list_vars.append((key, val)) + + if user_signature: + from globals import current + if current.session.auth: + hmac_key = current.session.auth.hmac_key + + if hmac_key: + # generate an hmac signature of the vars & args so can later + # verify the user hasn't messed with anything + + h_args = '/%s/%s/%s%s' % (application, controller, function2, other) + + # how many of the vars should we include in our hash? + if hash_vars is True: # include them all + h_vars = list_vars + elif hash_vars is False: # include none of them + h_vars = '' + else: # include just those specified + if hash_vars and not isinstance(hash_vars, (list, tuple)): + hash_vars = [hash_vars] + h_vars = [(k, v) for (k, v) in list_vars if k in hash_vars] + + # re-assembling the same way during hash authentication + message = h_args + '?' + urllib.urlencode(sorted(h_vars)) + + sig = hmac_hash(message,hmac_key,salt=salt) + # add the signature into vars + list_vars.append(('_signature', sig)) + + if list_vars: + other += '?%s' % urllib.urlencode(list_vars) + if anchor: + other += '#' + urllib.quote(str(anchor)) + if extension: + function += '.' + extension + + if regex_crlf.search(join([application, controller, function, other])): + raise SyntaxError, 'CRLF Injection Detected' + url = rewrite.url_out(r, env, application, controller, function, + args, other, scheme, host, port) + return url + + +def verifyURL(request, hmac_key=None, hash_vars=True, salt=None, user_signature=None): + """ + Verifies that a request's args & vars have not been tampered with by the user + + :param request: web2py's request object + :param hmac_key: the key to authenticate with, must be the same one previously + used when calling URL() + :param hash_vars: which vars to include in our hashing. (Optional) + Only uses the 1st value currently + True (or undefined) means all, False none, + an iterable just the specified keys + + do not call directly. Use instead: + + URL.verify(hmac_key='...') + + the key has to match the one used to generate the URL. + + >>> r = Storage() + >>> gv = Storage(p=(1,3),q=2,_signature='5d06bb8a4a6093dd325da2ee591c35c61afbd3c6') + >>> r.update(dict(application='a', controller='c', function='f')) + >>> r['args'] = ['x', 'y', 'z'] + >>> r['get_vars'] = gv + >>> verifyURL(r, 'key') + True + >>> verifyURL(r, 'kay') + False + >>> r.get_vars.p = (3, 1) + >>> verifyURL(r, 'key') + True + >>> r.get_vars.p = (3, 2) + >>> verifyURL(r, 'key') + False + + """ + + if not request.get_vars.has_key('_signature'): + return False # no signature in the request URL + + # check if user_signature requires + if user_signature: + from globals import current + if not current.session: + return False + hmac_key = current.session.auth.hmac_key + if not hmac_key: + return False + + # get our sig from request.get_vars for later comparison + original_sig = request.get_vars._signature + + # now generate a new hmac for the remaining args & vars + vars, args = request.get_vars, request.args + + # remove the signature var since it was not part of our signed message + request.get_vars.pop('_signature') + + # join all the args & vars into one long string + + # always include all of the args + other = args and urllib.quote('/' + '/'.join([str(x) for x in args])) or '' + h_args = '/%s/%s/%s.%s%s' % (request.application, + request.controller, + request.function, + request.extension, + other) + + # but only include those vars specified (allows more flexibility for use with + # forms or ajax) + + list_vars = [] + for (key, vals) in sorted(vars.items()): + if not isinstance(vals, (list, tuple)): + vals = [vals] + for val in vals: + list_vars.append((key, val)) + + # which of the vars are to be included? + if hash_vars is True: # include them all + h_vars = list_vars + elif hash_vars is False: # include none of them + h_vars = '' + else: # include just those specified + # wrap in a try - if the desired vars have been removed it'll fail + try: + if hash_vars and not isinstance(hash_vars, (list, tuple)): + hash_vars = [hash_vars] + h_vars = [(k, v) for (k, v) in list_vars if k in hash_vars] + except: + # user has removed one of our vars! Immediate fail + return False + # build the full message string with both args & vars + message = h_args + '?' + urllib.urlencode(sorted(h_vars)) + + # hash with the hmac_key provided + sig = hmac_hash(message,str(hmac_key),salt=salt) + + # put _signature back in get_vars just in case a second call to URL.verify is performed + # (otherwise it'll immediately return false) + request.get_vars['_signature'] = original_sig + + # return whether or not the signature in the request matched the one we just generated + # (I.E. was the message the same as the one we originally signed) + return original_sig == sig + +URL.verify = verifyURL + +ON = True + + +class XmlComponent(object): + """ + Abstract root for all Html components + """ + + # TODO: move some DIV methods to here + + def xml(self): + raise NotImplementedError + + +class XML(XmlComponent): + """ + use it to wrap a string that contains XML/HTML so that it will not be + escaped by the template + + example: + + >>> XML('<h1>Hello</h1>').xml() + '<h1>Hello</h1>' + """ + + def __init__( + self, + text, + sanitize = False, + permitted_tags = [ + 'a', + 'b', + 'blockquote', + 'br/', + 'i', + 'li', + 'ol', + 'ul', + 'p', + 'cite', + 'code', + 'pre', + 'img/', + 'h1','h2','h3','h4','h5','h6', + 'table','tr','td','div', + ], + allowed_attributes = { + 'a': ['href', 'title'], + 'img': ['src', 'alt'], + 'blockquote': ['type'], + 'td': ['colspan'], + }, + ): + """ + :param text: the XML text + :param sanitize: sanitize text using the permitted tags and allowed + attributes (default False) + :param permitted_tags: list of permitted tags (default: simple list of + tags) + :param allowed_attributes: dictionary of allowed attributed (default + for A, IMG and BlockQuote). + The key is the tag; the value is a list of allowed attributes. + """ + + if sanitize: + text = sanitizer.sanitize(text, permitted_tags, + allowed_attributes) + if isinstance(text, unicode): + text = text.encode('utf8', 'xmlcharrefreplace') + elif not isinstance(text, str): + text = str(text) + self.text = text + + def xml(self): + return self.text + + def __str__(self): + return self.xml() + + def __add__(self,other): + return '%s%s' % (self,other) + + def __radd__(self,other): + return '%s%s' % (other,self) + + def __cmp__(self,other): + return cmp(str(self),str(other)) + + def __hash__(self): + return hash(str(self)) + + def __getattr__(self,name): + return getattr(str(self),name) + + def __getitem__(self,i): + return str(self)[i] + + def __getslice__(self,i,j): + return str(self)[i:j] + + def __iter__(self): + for c in str(self): yield c + + def __len__(self): + return len(str(self)) + + def flatten(self,render=None): + """ + return the text stored by the XML object rendered by the render function + """ + if render: + return render(self.text,None,{}) + return self.text + + def elements(self, *args, **kargs): + """ + to be considered experimental since the behavior of this method is questionable + another options could be TAG(self.text).elements(*args,**kargs) + """ + return [] + +### important to allow safe session.flash=T(....) +def XML_unpickle(data): + return marshal.loads(data) +def XML_pickle(data): + return XML_unpickle, (marshal.dumps(str(data)),) +copy_reg.pickle(XML, XML_pickle, XML_unpickle) + + + +class DIV(XmlComponent): + """ + HTML helper, for easy generating and manipulating a DOM structure. + Little or no validation is done. + + Behaves like a dictionary regarding updating of attributes. + Behaves like a list regarding inserting/appending components. + + example:: + + >>> DIV('hello', 'world', _style='color:red;').xml() + '<div style=\"color:red;\">helloworld</div>' + + all other HTML helpers are derived from DIV. + + _something=\"value\" attributes are transparently translated into + something=\"value\" HTML attributes + """ + + # name of the tag, subclasses should update this + # tags ending with a '/' denote classes that cannot + # contain components + tag = 'div' + + def __init__(self, *components, **attributes): + """ + :param *components: any components that should be nested in this element + :param **attributes: any attributes you want to give to this element + + :raises SyntaxError: when a stand alone tag receives components + """ + + if self.tag[-1:] == '/' and components: + raise SyntaxError, '<%s> tags cannot have components'\ + % self.tag + if len(components) == 1 and isinstance(components[0], (list,tuple)): + self.components = list(components[0]) + else: + self.components = list(components) + self.attributes = attributes + self._fixup() + # converts special attributes in components attributes + self._postprocessing() + self.parent = None + for c in self.components: + self._setnode(c) + + def update(self, **kargs): + """ + dictionary like updating of the tag attributes + """ + + for (key, value) in kargs.items(): + self[key] = value + return self + + def append(self, value): + """ + list style appending of components + + >>> a=DIV() + >>> a.append(SPAN('x')) + >>> print a + <div><span>x</span></div> + """ + self._setnode(value) + ret = self.components.append(value) + self._fixup() + return ret + + def insert(self, i, value): + """ + list style inserting of components + + >>> a=DIV() + >>> a.insert(0,SPAN('x')) + >>> print a + <div><span>x</span></div> + """ + self._setnode(value) + ret = self.components.insert(i, value) + self._fixup() + return ret + + def __getitem__(self, i): + """ + gets attribute with name 'i' or component #i. + If attribute 'i' is not found returns None + + :param i: index + if i is a string: the name of the attribute + otherwise references to number of the component + """ + + if isinstance(i, str): + try: + return self.attributes[i] + except KeyError: + return None + else: + return self.components[i] + + def __setitem__(self, i, value): + """ + sets attribute with name 'i' or component #i. + + :param i: index + if i is a string: the name of the attribute + otherwise references to number of the component + :param value: the new value + """ + self._setnode(value) + if isinstance(i, (str, unicode)): + self.attributes[i] = value + else: + self.components[i] = value + + def __delitem__(self, i): + """ + deletes attribute with name 'i' or component #i. + + :param i: index + if i is a string: the name of the attribute + otherwise references to number of the component + """ + + if isinstance(i, str): + del self.attributes[i] + else: + del self.components[i] + + def __len__(self): + """ + returns the number of included components + """ + return len(self.components) + + def __nonzero__(self): + """ + always return True + """ + return True + + def _fixup(self): + """ + Handling of provided components. + + Nothing to fixup yet. May be overridden by subclasses, + eg for wrapping some components in another component or blocking them. + """ + return + + def _wrap_components(self, allowed_parents, + wrap_parent = None, + wrap_lambda = None): + """ + helper for _fixup. Checks if a component is in allowed_parents, + otherwise wraps it in wrap_parent + + :param allowed_parents: (tuple) classes that the component should be an + instance of + :param wrap_parent: the class to wrap the component in, if needed + :param wrap_lambda: lambda to use for wrapping, if needed + + """ + components = [] + for c in self.components: + if isinstance(c, allowed_parents): + pass + elif wrap_lambda: + c = wrap_lambda(c) + else: + c = wrap_parent(c) + if isinstance(c,DIV): + c.parent = self + components.append(c) + self.components = components + + def _postprocessing(self): + """ + Handling of attributes (normally the ones not prefixed with '_'). + + Nothing to postprocess yet. May be overridden by subclasses + """ + return + + def _traverse(self, status, hideerror=False): + # TODO: docstring + newstatus = status + for c in self.components: + if hasattr(c, '_traverse') and callable(c._traverse): + c.vars = self.vars + c.request_vars = self.request_vars + c.errors = self.errors + c.latest = self.latest + c.session = self.session + c.formname = self.formname + c['hideerror']=hideerror + newstatus = c._traverse(status,hideerror) and newstatus + + # for input, textarea, select, option + # deal with 'value' and 'validation' + + name = self['_name'] + if newstatus: + newstatus = self._validate() + self._postprocessing() + elif 'old_value' in self.attributes: + self['value'] = self['old_value'] + self._postprocessing() + elif name and name in self.vars: + self['value'] = self.vars[name] + self._postprocessing() + if name: + self.latest[name] = self['value'] + return newstatus + + def _validate(self): + """ + nothing to validate yet. May be overridden by subclasses + """ + return True + + def _setnode(self,value): + if isinstance(value,DIV): + value.parent = self + + def _xml(self): + """ + helper for xml generation. Returns separately: + - the component attributes + - the generated xml of the inner components + + Component attributes start with an underscore ('_') and + do not have a False or None value. The underscore is removed. + A value of True is replaced with the attribute name. + + :returns: tuple: (attributes, components) + """ + + # get the attributes for this component + # (they start with '_', others may have special meanings) + fa = '' + for key in sorted(self.attributes): + value = self[key] + if key[:1] != '_': + continue + name = key[1:] + if value is True: + value = name + elif value is False or value is None: + continue + fa += ' %s="%s"' % (name, xmlescape(value, True)) + + # get the xml for the inner components + co = join([xmlescape(component) for component in + self.components]) + + return (fa, co) + + def xml(self): + """ + generates the xml for this component. + """ + + (fa, co) = self._xml() + + if not self.tag: + return co + + if self.tag[-1:] == '/': + # <tag [attributes] /> + return '<%s%s />' % (self.tag[:-1], fa) + + # else: <tag [attributes]> inner components xml </tag> + return '<%s%s>%s</%s>' % (self.tag, fa, co, self.tag) + + def __str__(self): + """ + str(COMPONENT) returns equals COMPONENT.xml() + """ + + return self.xml() + + def flatten(self, render=None): + """ + return the text stored by the DIV object rendered by the render function + the render function must take text, tagname, and attributes + render=None is equivalent to render=lambda text, tag, attr: text + + >>> markdown = lambda text,tag=None,attributes={}: \ + {None: re.sub('\s+',' ',text), \ + 'h1':'#'+text+'\\n\\n', \ + 'p':text+'\\n'}.get(tag,text) + >>> a=TAG('<h1>Header</h1><p>this is a test</p>') + >>> a.flatten(markdown) + '#Header\\n\\nthis is a test\\n' + """ + + text = '' + for c in self.components: + if isinstance(c,XmlComponent): + s=c.flatten(render) + elif render: + s=render(str(c)) + else: + s=str(c) + text+=s + if render: + text = render(text,self.tag,self.attributes) + return text + + regex_tag=re.compile('^[\w\-\:]+') + regex_id=re.compile('#([\w\-]+)') + regex_class=re.compile('\.([\w\-]+)') + regex_attr=re.compile('\[([\w\-\:]+)=(.*?)\]') + + + def elements(self, *args, **kargs): + """ + find all component that match the supplied attribute dictionary, + or None if nothing could be found + + All components of the components are searched. + + >>> a = DIV(DIV(SPAN('x'),3,DIV(SPAN('y')))) + >>> for c in a.elements('span',first_only=True): c[0]='z' + >>> print a + <div><div><span>z</span>3<div><span>y</span></div></div></div> + >>> for c in a.elements('span'): c[0]='z' + >>> print a + <div><div><span>z</span>3<div><span>z</span></div></div></div> + + It also supports a syntax compatible with jQuery + + >>> a=TAG('<div><span><a id="1-1" u:v=$>hello</a></span><p class="this is a test">world</p></div>') + >>> for e in a.elements('div a#1-1, p.is'): print e.flatten() + hello + world + >>> for e in a.elements('#1-1'): print e.flatten() + hello + >>> a.elements('a[u:v=$]')[0].xml() + '<a id="1-1" u:v="$">hello</a>' + + >>> a=FORM( INPUT(_type='text'), SELECT(range(1)), TEXTAREA() ) + >>> for c in a.elements('input, select, textarea'): c['_disabled'] = 'disabled' + >>> a.xml() + '<form action="" enctype="multipart/form-data" method="post"><input disabled="disabled" type="text" /><select disabled="disabled"><option value="0">0</option></select><textarea cols="40" disabled="disabled" rows="10"></textarea></form>' + """ + if len(args)==1: + args = [a.strip() for a in args[0].split(',')] + if len(args)>1: + subset = [self.elements(a,**kargs) for a in args] + return reduce(lambda a,b:a+b,subset,[]) + elif len(args)==1: + items = args[0].split() + if len(items)>1: + subset=[a.elements(' '.join(items[1:]),**kargs) for a in self.elements(items[0])] + return reduce(lambda a,b:a+b,subset,[]) + else: + item=items[0] + if '#' in item or '.' in item or '[' in item: + match_tag = self.regex_tag.search(item) + match_id = self.regex_id.search(item) + match_class = self.regex_class.search(item) + match_attr = self.regex_attr.finditer(item) + args = [] + if match_tag: args = [match_tag.group()] + if match_id: kargs['_id'] = match_id.group(1) + if match_class: kargs['_class'] = re.compile('(?<!\w)%s(?!\w)' % \ + match_class.group(1).replace('-','\\-').replace(':','\\:')) + for item in match_attr: + kargs['_'+item.group(1)]=item.group(2) + return self.elements(*args,**kargs) + # make a copy of the components + matches = [] + first_only = False + if kargs.has_key("first_only"): + first_only = kargs["first_only"] + del kargs["first_only"] + # check if the component has an attribute with the same + # value as provided + check = True + tag = getattr(self,'tag').replace("/","") + if args and tag not in args: + check = False + for (key, value) in kargs.items(): + if isinstance(value,(str,int)): + if self[key] != str(value): + check = False + elif key in self.attributes: + if not value.search(str(self[key])): + check = False + else: + check = False + if 'find' in kargs: + find = kargs['find'] + for c in self.components: + if isinstance(find,(str,int)): + if isinstance(c,str) and str(find) in c: + check = True + else: + if isinstance(c,str) and find.search(c): + check = True + # if found, return the component + if check: + matches.append(self) + if first_only: + return matches + # loop the copy + for c in self.components: + if isinstance(c, XmlComponent): + kargs['first_only'] = first_only + child_matches = c.elements( *args, **kargs ) + if first_only and len(child_matches) != 0: + return child_matches + matches.extend( child_matches ) + return matches + + + def element(self, *args, **kargs): + """ + find the first component that matches the supplied attribute dictionary, + or None if nothing could be found + + Also the components of the components are searched. + """ + kargs['first_only'] = True + elements = self.elements(*args, **kargs) + if not elements: + # we found nothing + return None + return elements[0] + + def siblings(self,*args,**kargs): + """ + find all sibling components that match the supplied argument list + and attribute dictionary, or None if nothing could be found + """ + sibs = [s for s in self.parent.components if not s == self] + matches = [] + first_only = False + if kargs.has_key("first_only"): + first_only = kargs["first_only"] + del kargs["first_only"] + for c in sibs: + try: + check = True + tag = getattr(c,'tag').replace("/","") + if args and tag not in args: + check = False + for (key, value) in kargs.items(): + if c[key] != value: + check = False + if check: + matches.append(c) + if first_only: break + except: + pass + return matches + + def sibling(self,*args,**kargs): + """ + find the first sibling component that match the supplied argument list + and attribute dictionary, or None if nothing could be found + """ + kargs['first_only'] = True + sibs = self.siblings(*args, **kargs) + if not sibs: + return None + return sibs[0] + +class CAT(DIV): + + tag = '' + +def TAG_unpickler(data): + return cPickle.loads(data) + +def TAG_pickler(data): + d = DIV() + d.__dict__ = data.__dict__ + marshal_dump = cPickle.dumps(d) + return (TAG_unpickler, (marshal_dump,)) + +class __TAG__(XmlComponent): + + """ + TAG factory example:: + + >>> print TAG.first(TAG.second('test'), _key = 3) + <first key=\"3\"><second>test</second></first> + + """ + + def __getitem__(self, name): + return self.__getattr__(name) + + def __getattr__(self, name): + if name[-1:] == '_': + name = name[:-1] + '/' + if isinstance(name,unicode): + name = name.encode('utf-8') + class __tag__(DIV): + tag = name + copy_reg.pickle(__tag__, TAG_pickler, TAG_unpickler) + return lambda *a, **b: __tag__(*a, **b) + + def __call__(self,html): + return web2pyHTMLParser(decoder.decoder(html)).tree + +TAG = __TAG__() + + +class HTML(DIV): + """ + There are four predefined document type definitions. + They can be specified in the 'doctype' parameter: + + -'strict' enables strict doctype + -'transitional' enables transitional doctype (default) + -'frameset' enables frameset doctype + -'html5' enables HTML 5 doctype + -any other string will be treated as user's own doctype + + 'lang' parameter specifies the language of the document. + Defaults to 'en'. + + See also :class:`DIV` + """ + + tag = 'html' + + strict = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">\n' + transitional = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n' + frameset = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">\n' + html5 = '<!DOCTYPE HTML>\n' + + def xml(self): + lang = self['lang'] + if not lang: + lang = 'en' + self.attributes['_lang'] = lang + doctype = self['doctype'] + if doctype: + if doctype == 'strict': + doctype = self.strict + elif doctype == 'transitional': + doctype = self.transitional + elif doctype == 'frameset': + doctype = self.frameset + elif doctype == 'html5': + doctype = self.html5 + else: + doctype = '%s\n' % doctype + else: + doctype = self.transitional + (fa, co) = self._xml() + return '%s<%s%s>%s</%s>' % (doctype, self.tag, fa, co, self.tag) + +class XHTML(DIV): + """ + This is XHTML version of the HTML helper. + + There are three predefined document type definitions. + They can be specified in the 'doctype' parameter: + + -'strict' enables strict doctype + -'transitional' enables transitional doctype (default) + -'frameset' enables frameset doctype + -any other string will be treated as user's own doctype + + 'lang' parameter specifies the language of the document and the xml document. + Defaults to 'en'. + + 'xmlns' parameter specifies the xml namespace. + Defaults to 'http://www.w3.org/1999/xhtml'. + + See also :class:`DIV` + """ + + tag = 'html' + + strict = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n' + transitional = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n' + frameset = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">\n' + xmlns = 'http://www.w3.org/1999/xhtml' + + def xml(self): + xmlns = self['xmlns'] + if xmlns: + self.attributes['_xmlns'] = xmlns + else: + self.attributes['_xmlns'] = self.xmlns + lang = self['lang'] + if not lang: + lang = 'en' + self.attributes['_lang'] = lang + self.attributes['_xml:lang'] = lang + doctype = self['doctype'] + if doctype: + if doctype == 'strict': + doctype = self.strict + elif doctype == 'transitional': + doctype = self.transitional + elif doctype == 'frameset': + doctype = self.frameset + else: + doctype = '%s\n' % doctype + else: + doctype = self.transitional + (fa, co) = self._xml() + return '%s<%s%s>%s</%s>' % (doctype, self.tag, fa, co, self.tag) + + +class HEAD(DIV): + + tag = 'head' + +class TITLE(DIV): + + tag = 'title' + + +class META(DIV): + + tag = 'meta/' + + +class LINK(DIV): + + tag = 'link/' + + +class SCRIPT(DIV): + + tag = 'script' + + def xml(self): + (fa, co) = self._xml() + # no escaping of subcomponents + co = '\n'.join([str(component) for component in + self.components]) + if co: + # <script [attributes]><!--//--><![CDATA[//><!-- + # script body + # //--><!]]></script> + # return '<%s%s><!--//--><![CDATA[//><!--\n%s\n//--><!]]></%s>' % (self.tag, fa, co, self.tag) + return '<%s%s><!--\n%s\n//--></%s>' % (self.tag, fa, co, self.tag) + else: + return DIV.xml(self) + + +class STYLE(DIV): + + tag = 'style' + + def xml(self): + (fa, co) = self._xml() + # no escaping of subcomponents + co = '\n'.join([str(component) for component in + self.components]) + if co: + # <style [attributes]><!--/*--><![CDATA[/*><!--*/ + # style body + # /*]]>*/--></style> + return '<%s%s><!--/*--><![CDATA[/*><!--*/\n%s\n/*]]>*/--></%s>' % (self.tag, fa, co, self.tag) + else: + return DIV.xml(self) + + +class IMG(DIV): + + tag = 'img/' + + +class SPAN(DIV): + + tag = 'span' + + +class BODY(DIV): + + tag = 'body' + + +class H1(DIV): + + tag = 'h1' + + +class H2(DIV): + + tag = 'h2' + + +class H3(DIV): + + tag = 'h3' + + +class H4(DIV): + + tag = 'h4' + + +class H5(DIV): + + tag = 'h5' + + +class H6(DIV): + + tag = 'h6' + + +class P(DIV): + """ + Will replace ``\\n`` by ``<br />`` if the `cr2br` attribute is provided. + + see also :class:`DIV` + """ + + tag = 'p' + + def xml(self): + text = DIV.xml(self) + if self['cr2br']: + text = text.replace('\n', '<br />') + return text + + +class B(DIV): + + tag = 'b' + + +class BR(DIV): + + tag = 'br/' + + +class HR(DIV): + + tag = 'hr/' + + +class A(DIV): + + tag = 'a' + + def xml(self): + if self['callback']: + self['_onclick']="ajax('%s',[],'%s');return false;" % \ + (self['callback'],self['target'] or '') + self['_href'] = self['_href'] or '#null' + elif self['cid']: + self['_onclick']='web2py_component("%s","%s");return false;' % \ + (self['_href'],self['cid']) + return DIV.xml(self) + + +class BUTTON(DIV): + + tag = 'button' + + +class EM(DIV): + + tag = 'em' + + +class EMBED(DIV): + + tag = 'embed/' + + +class TT(DIV): + + tag = 'tt' + + +class PRE(DIV): + + tag = 'pre' + + +class CENTER(DIV): + + tag = 'center' + + +class CODE(DIV): + + """ + displays code in HTML with syntax highlighting. + + :param attributes: optional attributes: + + - language: indicates the language, otherwise PYTHON is assumed + - link: can provide a link + - styles: for styles + + Example:: + + {{=CODE(\"print 'hello world'\", language='python', link=None, + counter=1, styles={}, highlight_line=None)}} + + + supported languages are \"python\", \"html_plain\", \"c\", \"cpp\", + \"web2py\", \"html\". + The \"html\" language interprets {{ and }} tags as \"web2py\" code, + \"html_plain\" doesn't. + + if a link='/examples/global/vars/' is provided web2py keywords are linked to + the online docs. + + the counter is used for line numbering, counter can be None or a prompt + string. + """ + + def xml(self): + language = self['language'] or 'PYTHON' + link = self['link'] + counter = self.attributes.get('counter', 1) + highlight_line = self.attributes.get('highlight_line', None) + styles = self['styles'] or {} + return highlight( + join(self.components), + language=language, + link=link, + counter=counter, + styles=styles, + attributes=self.attributes, + highlight_line=highlight_line, + ) + + +class LABEL(DIV): + + tag = 'label' + + +class LI(DIV): + + tag = 'li' + + +class UL(DIV): + """ + UL Component. + + If subcomponents are not LI-components they will be wrapped in a LI + + see also :class:`DIV` + """ + + tag = 'ul' + + def _fixup(self): + self._wrap_components(LI, LI) + + +class OL(UL): + + tag = 'ol' + + +class TD(DIV): + + tag = 'td' + + +class TH(DIV): + + tag = 'th' + + +class TR(DIV): + """ + TR Component. + + If subcomponents are not TD/TH-components they will be wrapped in a TD + + see also :class:`DIV` + """ + + tag = 'tr' + + def _fixup(self): + self._wrap_components((TD, TH), TD) + +class THEAD(DIV): + + tag = 'thead' + + def _fixup(self): + self._wrap_components(TR, TR) + + +class TBODY(DIV): + + tag = 'tbody' + + def _fixup(self): + self._wrap_components(TR, TR) + + +class TFOOT(DIV): + + tag = 'tfoot' + + def _fixup(self): + self._wrap_components(TR, TR) + + +class COL(DIV): + + tag = 'col' + + +class COLGROUP(DIV): + + tag = 'colgroup' + + +class TABLE(DIV): + """ + TABLE Component. + + If subcomponents are not TR/TBODY/THEAD/TFOOT-components + they will be wrapped in a TR + + see also :class:`DIV` + """ + + tag = 'table' + + def _fixup(self): + self._wrap_components((TR, TBODY, THEAD, TFOOT, COL, COLGROUP), TR) + +class I(DIV): + + tag = 'i' + +class IFRAME(DIV): + + tag = 'iframe' + + +class INPUT(DIV): + + """ + INPUT Component + + examples:: + + >>> INPUT(_type='text', _name='name', value='Max').xml() + '<input name=\"name\" type=\"text\" value=\"Max\" />' + + >>> INPUT(_type='checkbox', _name='checkbox', value='on').xml() + '<input checked=\"checked\" name=\"checkbox\" type=\"checkbox\" value=\"on\" />' + + >>> INPUT(_type='radio', _name='radio', _value='yes', value='yes').xml() + '<input checked=\"checked\" name=\"radio\" type=\"radio\" value=\"yes\" />' + + >>> INPUT(_type='radio', _name='radio', _value='no', value='yes').xml() + '<input name=\"radio\" type=\"radio\" value=\"no\" />' + + the input helper takes two special attributes value= and requires=. + + :param value: used to pass the initial value for the input field. + value differs from _value because it works for checkboxes, radio, + textarea and select/option too. + + - for a checkbox value should be '' or 'on'. + - for a radio or select/option value should be the _value + of the checked/selected item. + + :param requires: should be None, or a validator or a list of validators + for the value of the field. + """ + + tag = 'input/' + + def _validate(self): + + # # this only changes value, not _value + + name = self['_name'] + if name is None or name == '': + return True + name = str(name) + + if self['_type'] != 'checkbox': + self['old_value'] = self['value'] or self['_value'] or '' + value = self.request_vars.get(name, '') + self['value'] = value + else: + self['old_value'] = self['value'] or False + value = self.request_vars.get(name) + if isinstance(value, (tuple, list)): + self['value'] = self['_value'] in value + else: + self['value'] = self['_value'] == value + requires = self['requires'] + if requires: + if not isinstance(requires, (list, tuple)): + requires = [requires] + for validator in requires: + (value, errors) = validator(value) + if errors != None: + self.vars[name] = value + self.errors[name] = errors + break + if not name in self.errors: + self.vars[name] = value + return True + return False + + def _postprocessing(self): + t = self['_type'] + if not t: + t = self['_type'] = 'text' + t = t.lower() + value = self['value'] + if self['_value'] == None: + _value = None + else: + _value = str(self['_value']) + if t == 'checkbox': + if not _value: + _value = self['_value'] = 'on' + if not value: + value = [] + elif value is True: + value = [_value] + elif not isinstance(value,(list,tuple)): + value = str(value).split('|') + self['_checked'] = _value in value and 'checked' or None + elif t == 'radio': + if str(value) == str(_value): + self['_checked'] = 'checked' + else: + self['_checked'] = None + elif t == 'text' or t == 'hidden': + if value != None: + self['_value'] = value + else: + self['value'] = _value + + def xml(self): + name = self.attributes.get('_name', None) + if name and hasattr(self, 'errors') \ + and self.errors.get(name, None) \ + and self['hideerror'] != True: + return DIV.xml(self) + DIV(self.errors[name], _class='error', + errors=None, _id='%s__error' % name).xml() + else: + return DIV.xml(self) + + +class TEXTAREA(INPUT): + + """ + example:: + + TEXTAREA(_name='sometext', value='blah '*100, requires=IS_NOT_EMPTY()) + + 'blah blah blah ...' will be the content of the textarea field. + """ + + tag = 'textarea' + + def _postprocessing(self): + if not '_rows' in self.attributes: + self['_rows'] = 10 + if not '_cols' in self.attributes: + self['_cols'] = 40 + if self['value'] != None: + self.components = [self['value']] + elif self.components: + self['value'] = self.components[0] + + +class OPTION(DIV): + + tag = 'option' + + def _fixup(self): + if not '_value' in self.attributes: + self.attributes['_value'] = str(self.components[0]) + + +class OBJECT(DIV): + + tag = 'object' + +class OPTGROUP(DIV): + + tag = 'optgroup' + + def _fixup(self): + components = [] + for c in self.components: + if isinstance(c, OPTION): + components.append(c) + else: + components.append(OPTION(c, _value=str(c))) + self.components = components + + +class SELECT(INPUT): + + """ + example:: + + >>> from validators import IS_IN_SET + >>> SELECT('yes', 'no', _name='selector', value='yes', + ... requires=IS_IN_SET(['yes', 'no'])).xml() + '<select name=\"selector\"><option selected=\"selected\" value=\"yes\">yes</option><option value=\"no\">no</option></select>' + + """ + + tag = 'select' + + def _fixup(self): + components = [] + for c in self.components: + if isinstance(c, (OPTION, OPTGROUP)): + components.append(c) + else: + components.append(OPTION(c, _value=str(c))) + self.components = components + + def _postprocessing(self): + component_list = [] + for c in self.components: + if isinstance(c, OPTGROUP): + component_list.append(c.components) + else: + component_list.append([c]) + options = itertools.chain(*component_list) + + value = self['value'] + if value != None: + if not self['_multiple']: + for c in options: # my patch + if value and str(c['_value'])==str(value): + c['_selected'] = 'selected' + else: + c['_selected'] = None + else: + if isinstance(value,(list,tuple)): + values = [str(item) for item in value] + else: + values = [str(value)] + for c in options: # my patch + if value and str(c['_value']) in values: + c['_selected'] = 'selected' + else: + c['_selected'] = None + + +class FIELDSET(DIV): + + tag = 'fieldset' + + +class LEGEND(DIV): + + tag = 'legend' + + +class FORM(DIV): + + """ + example:: + + >>> from validators import IS_NOT_EMPTY + >>> form=FORM(INPUT(_name=\"test\", requires=IS_NOT_EMPTY())) + >>> form.xml() + '<form action=\"\" enctype=\"multipart/form-data\" method=\"post\"><input name=\"test\" type=\"text\" /></form>' + + a FORM is container for INPUT, TEXTAREA, SELECT and other helpers + + form has one important method:: + + form.accepts(request.vars, session) + + if form is accepted (and all validators pass) form.vars contains the + accepted vars, otherwise form.errors contains the errors. + in case of errors the form is modified to present the errors to the user. + """ + + tag = 'form' + + def __init__(self, *components, **attributes): + DIV.__init__(self, *components, **attributes) + self.vars = Storage() + self.errors = Storage() + self.latest = Storage() + + def accepts( + self, + vars, + session=None, + formname='default', + keepvalues=False, + onvalidation=None, + hideerror=False, + ): + if vars.__class__.__name__ == 'Request': + vars=vars.post_vars + self.errors.clear() + self.request_vars = Storage() + self.request_vars.update(vars) + self.session = session + self.formname = formname + self.keepvalues = keepvalues + + # if this tag is a form and we are in accepting mode (status=True) + # check formname and formkey + + status = True + if self.session: + formkey = self.session.get('_formkey[%s]' % self.formname, None) + # check if user tampering with form and void CSRF + if formkey != self.request_vars._formkey: + status = False + if self.formname != self.request_vars._formname: + status = False + if status and self.session: + # check if editing a record that has been modified by the server + if hasattr(self,'record_hash') and self.record_hash != formkey: + status = False + self.record_changed = True + status = self._traverse(status,hideerror) + if onvalidation: + if isinstance(onvalidation, dict): + onsuccess = onvalidation.get('onsuccess', None) + onfailure = onvalidation.get('onfailure', None) + if onsuccess and status: + onsuccess(self) + if onfailure and vars and not status: + onfailure(self) + status = len(self.errors) == 0 + elif status: + if isinstance(onvalidation, (list, tuple)): + [f(self) for f in onvalidation] + else: + onvalidation(self) + if self.errors: + status = False + if session != None: + if hasattr(self,'record_hash'): + formkey = self.record_hash + else: + formkey = web2py_uuid() + self.formkey = session['_formkey[%s]' % formname] = formkey + if status and not keepvalues: + self._traverse(False,hideerror) + return status + + def _postprocessing(self): + if not '_action' in self.attributes: + self['_action'] = '' + if not '_method' in self.attributes: + self['_method'] = 'post' + if not '_enctype' in self.attributes: + self['_enctype'] = 'multipart/form-data' + + def hidden_fields(self): + c = [] + if 'hidden' in self.attributes: + for (key, value) in self.attributes.get('hidden',{}).items(): + c.append(INPUT(_type='hidden', _name=key, _value=value)) + + if hasattr(self, 'formkey') and self.formkey: + c.append(INPUT(_type='hidden', _name='_formkey', + _value=self.formkey)) + if hasattr(self, 'formname') and self.formname: + c.append(INPUT(_type='hidden', _name='_formname', + _value=self.formname)) + return DIV(c, _class="hidden") + + def xml(self): + newform = FORM(*self.components, **self.attributes) + hidden_fields = self.hidden_fields() + if hidden_fields.components: + newform.append(hidden_fields) + return DIV.xml(newform) + + def validate(self, + values=None, + session=None, + formname='default', + keepvalues=False, + onvalidation=None, + hideerror=False, + onsuccess='flash', + onfailure='flash', + message_onsuccess=None, + message_onfailure=None, + ): + """ + This function validates the form, + you can use it instead of directly form.accepts. + + Usage: + In controller + + def action(): + form=FORM(INPUT(_name=\"test\", requires=IS_NOT_EMPTY())) + form.validate() #you can pass some args here - see below + return dict(form=form) + + This can receive a bunch of arguments + + onsuccess = 'flash' - will show message_onsuccess in response.flash + None - will do nothing + can be a function (lambda form: pass) + onfailure = 'flash' - will show message_onfailure in response.flash + None - will do nothing + can be a function (lambda form: pass) + + values = values to test the validation - dictionary, response.vars, session or other - Default to (request.vars, session) + message_onsuccess + message_onfailure + """ + from gluon import current + if not session: session = current.session + if not values: values = current.request.post_vars + + message_onsuccess = message_onsuccess or current.T("Success!") + message_onfailure = message_onfailure or \ + current.T("Errors in form, please check it out.") + + if self.accepts(values, session): + if onsuccess == 'flash': + current.response.flash = message_onsuccess + elif callable(onsuccess): + onsuccess(self) + return True + elif self.errors: + if onfailure == 'flash': + current.response.flash = message_onfailure + elif callable(onfailure): + onfailure(self) + return False + + def process(self, values=None, session=None, **args): + """ + Perform the .validate() method but returns the form + + Usage in controllers: + # directly on return + def action(): + #some code here + return dict(form=FORM(...).process(...)) + + You can use it with FORM, SQLFORM or FORM based plugins + + Examples: + #response.flash messages + def action(): + form = SQLFORM(db.table).process(message_onsuccess='Sucess!') + retutn dict(form=form) + + # callback function + # callback receives True or False as first arg, and a list of args. + def my_callback(status, msg): + response.flash = "Success! "+msg if status else "Errors occured" + + # after argument can be 'flash' to response.flash messages + # or a function name to use as callback or None to do nothing. + def action(): + return dict(form=SQLFORM(db.table).process(onsuccess=my_callback) + """ + self.validate(values=values, session=session, **args) + return self + + +class BEAUTIFY(DIV): + + """ + example:: + + >>> BEAUTIFY(['a', 'b', {'hello': 'world'}]).xml() + '<div><table><tr><td><div>a</div></td></tr><tr><td><div>b</div></td></tr><tr><td><div><table><tr><td style="font-weight:bold;">hello</td><td valign="top">:</td><td><div>world</div></td></tr></table></div></td></tr></table></div>' + + turns any list, dictionary, etc into decent looking html. + Two special attributes are + :sorted: a function that takes the dict and returned sorted keys + :keyfilter: a funciton that takes a key and returns its representation + or None if the key is to be skipped. By default key[:1]=='_' is skipped. + """ + + tag = 'div' + + @staticmethod + def no_underscore(key): + if key[:1]=='_': + return None + return key + + def __init__(self, component, **attributes): + self.components = [component] + self.attributes = attributes + sorter = attributes.get('sorted',sorted) + keyfilter = attributes.get('keyfilter',BEAUTIFY.no_underscore) + components = [] + attributes = copy.copy(self.attributes) + level = attributes['level'] = attributes.get('level',6) - 1 + if '_class' in attributes: + attributes['_class'] += 'i' + if level == 0: + return + for c in self.components: + if hasattr(c,'xml') and callable(c.xml): + components.append(c) + continue + elif hasattr(c,'keys') and callable(c.keys): + rows = [] + try: + keys = (sorter and sorter(c)) or c + for key in keys: + if isinstance(key,(str,unicode)) and keyfilter: + filtered_key = keyfilter(key) + else: + filtered_key = str(key) + if filtered_key is None: + continue + value = c[key] + if type(value) == types.LambdaType: + continue + rows.append(TR(TD(filtered_key, _style='font-weight:bold;'), + TD(':',_valign='top'), + TD(BEAUTIFY(value, **attributes)))) + components.append(TABLE(*rows, **attributes)) + continue + except: + pass + if isinstance(c, str): + components.append(str(c)) + elif isinstance(c, unicode): + components.append(c.encode('utf8')) + elif isinstance(c, (list, tuple)): + items = [TR(TD(BEAUTIFY(item, **attributes))) + for item in c] + components.append(TABLE(*items, **attributes)) + elif isinstance(c, cgi.FieldStorage): + components.append('FieldStorage object') + else: + components.append(repr(c)) + self.components = components + + +class MENU(DIV): + """ + Used to build menus + + Optional arguments + _class: defaults to 'web2py-menu web2py-menu-vertical' + ul_class: defaults to 'web2py-menu-vertical' + li_class: defaults to 'web2py-menu-expand' + + Example: + menu = MENU([['name', False, URL(...), [submenu]], ...]) + {{=menu}} + """ + + tag = 'ul' + + def __init__(self, data, **args): + self.data = data + self.attributes = args + if not '_class' in self.attributes: + self['_class'] = 'web2py-menu web2py-menu-vertical' + if not 'ul_class' in self.attributes: + self['ul_class'] = 'web2py-menu-vertical' + if not 'li_class' in self.attributes: + self['li_class'] = 'web2py-menu-expand' + if not 'li_active' in self.attributes: + self['li_active'] = 'web2py-menu-active' + + def serialize(self, data, level=0): + if level == 0: + ul = UL(**self.attributes) + else: + ul = UL(_class=self['ul_class']) + for item in data: + (name, active, link) = item[:3] + if isinstance(link,DIV): + li = LI(link) + elif 'no_link_url' in self.attributes and self['no_link_url']==link: + li = LI(DIV(name)) + elif link: + li = LI(A(name, _href=link)) + else: + li = LI(A(name, _href='#', + _onclick='javascript:void(0);return false;')) + if len(item) > 3 and item[3]: + li['_class'] = self['li_class'] + li.append(self.serialize(item[3], level+1)) + if active or ('active_url' in self.attributes and self['active_url']==link): + if li['_class']: + li['_class'] = li['_class']+' '+self['li_active'] + else: + li['_class'] = self['li_active'] + ul.append(li) + return ul + + def xml(self): + return self.serialize(self.data, 0).xml() + + +def embed64( + filename = None, + file = None, + data = None, + extension = 'image/gif', + ): + """ + helper to encode the provided (binary) data into base64. + + :param filename: if provided, opens and reads this file in 'rb' mode + :param file: if provided, reads this file + :param data: if provided, uses the provided data + """ + + if filename and os.path.exists(file): + fp = open(filename, 'rb') + data = fp.read() + fp.close() + data = base64.b64encode(data) + return 'data:%s;base64,%s' % (extension, data) + + +def test(): + """ + Example: + + >>> from validators import * + >>> print DIV(A('click me', _href=URL(a='a', c='b', f='c')), BR(), HR(), DIV(SPAN(\"World\"), _class='unknown')).xml() + <div><a href=\"/a/b/c\">click me</a><br /><hr /><div class=\"unknown\"><span>World</span></div></div> + >>> print DIV(UL(\"doc\",\"cat\",\"mouse\")).xml() + <div><ul><li>doc</li><li>cat</li><li>mouse</li></ul></div> + >>> print DIV(UL(\"doc\", LI(\"cat\", _class='feline'), 18)).xml() + <div><ul><li>doc</li><li class=\"feline\">cat</li><li>18</li></ul></div> + >>> print TABLE(['a', 'b', 'c'], TR('d', 'e', 'f'), TR(TD(1), TD(2), TD(3))).xml() + <table><tr><td>a</td><td>b</td><td>c</td></tr><tr><td>d</td><td>e</td><td>f</td></tr><tr><td>1</td><td>2</td><td>3</td></tr></table> + >>> form=FORM(INPUT(_type='text', _name='myvar', requires=IS_EXPR('int(value)<10'))) + >>> print form.xml() + <form action=\"\" enctype=\"multipart/form-data\" method=\"post\"><input name=\"myvar\" type=\"text\" /></form> + >>> print form.accepts({'myvar':'34'}, formname=None) + False + >>> print form.xml() + <form action="" enctype="multipart/form-data" method="post"><input name="myvar" type="text" value="34" /><div class="error" id="myvar__error">invalid expression</div></form> + >>> print form.accepts({'myvar':'4'}, formname=None, keepvalues=True) + True + >>> print form.xml() + <form action=\"\" enctype=\"multipart/form-data\" method=\"post\"><input name=\"myvar\" type=\"text\" value=\"4\" /></form> + >>> form=FORM(SELECT('cat', 'dog', _name='myvar')) + >>> print form.accepts({'myvar':'dog'}, formname=None, keepvalues=True) + True + >>> print form.xml() + <form action=\"\" enctype=\"multipart/form-data\" method=\"post\"><select name=\"myvar\"><option value=\"cat\">cat</option><option selected=\"selected\" value=\"dog\">dog</option></select></form> + >>> form=FORM(INPUT(_type='text', _name='myvar', requires=IS_MATCH('^\w+$', 'only alphanumeric!'))) + >>> print form.accepts({'myvar':'as df'}, formname=None) + False + >>> print form.xml() + <form action=\"\" enctype=\"multipart/form-data\" method=\"post\"><input name=\"myvar\" type=\"text\" value=\"as df\" /><div class=\"error\" id=\"myvar__error\">only alphanumeric!</div></form> + >>> session={} + >>> form=FORM(INPUT(value=\"Hello World\", _name=\"var\", requires=IS_MATCH('^\w+$'))) + >>> if form.accepts({}, session,formname=None): print 'passed' + >>> if form.accepts({'var':'test ', '_formkey': session['_formkey[None]']}, session, formname=None): print 'passed' + """ + pass + + +class web2pyHTMLParser(HTMLParser): + """ + obj = web2pyHTMLParser(text) parses and html/xml text into web2py helpers. + obj.tree contains the root of the tree, and tree can be manipulated + + >>> str(web2pyHTMLParser('hello<div a="b" c=3>wor<ld<span>xxx</span>y<script/>yy</div>zzz').tree) + 'hello<div a="b" c="3">wor<ld<span>xxx</span>y<script></script>yy</div>zzz' + >>> str(web2pyHTMLParser('<div>a<span>b</div>c').tree) + '<div>a<span>b</span></div>c' + >>> tree = web2pyHTMLParser('hello<div a="b">world</div>').tree + >>> tree.element(_a='b')['_c']=5 + >>> str(tree) + 'hello<div a="b" c="5">world</div>' + """ + def __init__(self,text,closed=('input','link')): + HTMLParser.__init__(self) + self.tree = self.parent = TAG['']() + self.closed = closed + self.tags = [x for x in __all__ if isinstance(eval(x),DIV)] + self.last = None + self.feed(text) + def handle_starttag(self, tagname, attrs): + if tagname.upper() in self.tags: + tag=eval(tagname.upper()) + else: + if tagname in self.closed: tagname+='/' + tag = TAG[tagname]() + for key,value in attrs: tag['_'+key]=value + tag.parent = self.parent + self.parent.append(tag) + if not tag.tag.endswith('/'): + self.parent=tag + else: + self.last = tag.tag[:-1] + def handle_data(self,data): + try: + self.parent.append(data.encode('utf8','xmlcharref')) + except: + self.parent.append(data.decode('latin1').encode('utf8','xmlcharref')) + def handle_charref(self,name): + if name[1].lower()=='x': + self.parent.append(unichr(int(name[2:], 16)).encode('utf8')) + else: + self.parent.append(unichr(int(name[1:], 10)).encode('utf8')) + def handle_entityref(self,name): + self.parent.append(unichr(name2codepoint[name]).encode('utf8')) + def handle_endtag(self, tagname): + # this deals with unbalanced tags + if tagname==self.last: + return + while True: + try: + parent_tagname=self.parent.tag + self.parent = self.parent.parent + except: + raise RuntimeError, "unable to balance tag %s" % tagname + if parent_tagname[:len(tagname)]==tagname: break + +def markdown_serializer(text,tag=None,attr={}): + if tag is None: return re.sub('\s+',' ',text) + if tag=='br': return '\n\n' + if tag=='h1': return '#'+text+'\n\n' + if tag=='h2': return '#'*2+text+'\n\n' + if tag=='h3': return '#'*3+text+'\n\n' + if tag=='h4': return '#'*4+text+'\n\n' + if tag=='p': return text+'\n\n' + if tag=='b' or tag=='strong': return '**%s**' % text + if tag=='em' or tag=='i': return '*%s*' % text + if tag=='tt' or tag=='code': return '`%s`' % text + if tag=='a': return '[%s](%s)' % (text,attr.get('_href','')) + if tag=='img': return '![%s](%s)' % (attr.get('_alt',''),attr.get('_src','')) + return text + +def markmin_serializer(text,tag=None,attr={}): + # if tag is None: return re.sub('\s+',' ',text) + if tag=='br': return '\n\n' + if tag=='h1': return '# '+text+'\n\n' + if tag=='h2': return '#'*2+' '+text+'\n\n' + if tag=='h3': return '#'*3+' '+text+'\n\n' + if tag=='h4': return '#'*4+' '+text+'\n\n' + if tag=='p': return text+'\n\n' + if tag=='li': return '\n- '+text.replace('\n',' ') + if tag=='tr': return text[3:].replace('\n',' ')+'\n' + if tag in ['table','blockquote']: return '\n-----\n'+text+'\n------\n' + if tag in ['td','th']: return ' | '+text + if tag in ['b','strong','label']: return '**%s**' % text + if tag in ['em','i']: return "''%s''" % text + if tag in ['tt']: return '``%s``' % text.strip() + if tag in ['code']: return '``\n%s``' % text + if tag=='a': return '[[%s %s]]' % (text,attr.get('_href','')) + if tag=='img': return '[[%s %s left]]' % (attr.get('_alt','no title'),attr.get('_src','')) + return text + + +class MARKMIN(XmlComponent): + """ + For documentation: http://web2py.com/examples/static/markmin.html + """ + def __init__(self, text, extra={}, allowed={}, sep='p'): + self.text = text + self.extra = extra + self.allowed = allowed + self.sep = sep + + def xml(self): + """ + calls the gluon.contrib.markmin render function to convert the wiki syntax + """ + return render(self.text,extra=self.extra,allowed=self.allowed,sep=self.sep) + + def __str__(self): + return self.xml() + + def flatten(self,render=None): + """ + return the text stored by the MARKMIN object rendered by the render function + """ + return self.text + + def elements(self, *args, **kargs): + """ + to be considered experimental since the behavior of this method is questionable + another options could be TAG(self.text).elements(*args,**kargs) + """ + return [self.text] + + +if __name__ == '__main__': + import doctest + doctest.testmod() + ADDED gluon/http.py Index: gluon/http.py ================================================================== --- gluon/http.py +++ gluon/http.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +This file is part of the web2py Web Framework +Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> +License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) +""" + +__all__ = ['HTTP', 'redirect'] + +defined_status = { + 200: 'OK', + 201: 'CREATED', + 202: 'ACCEPTED', + 203: 'NON-AUTHORITATIVE INFORMATION', + 204: 'NO CONTENT', + 205: 'RESET CONTENT', + 206: 'PARTIAL CONTENT', + 301: 'MOVED PERMANENTLY', + 302: 'FOUND', + 303: 'SEE OTHER', + 304: 'NOT MODIFIED', + 305: 'USE PROXY', + 307: 'TEMPORARY REDIRECT', + 400: 'BAD REQUEST', + 401: 'UNAUTHORIZED', + 403: 'FORBIDDEN', + 404: 'NOT FOUND', + 405: 'METHOD NOT ALLOWED', + 406: 'NOT ACCEPTABLE', + 407: 'PROXY AUTHENTICATION REQUIRED', + 408: 'REQUEST TIMEOUT', + 409: 'CONFLICT', + 410: 'GONE', + 411: 'LENGTH REQUIRED', + 412: 'PRECONDITION FAILED', + 413: 'REQUEST ENTITY TOO LARGE', + 414: 'REQUEST-URI TOO LONG', + 415: 'UNSUPPORTED MEDIA TYPE', + 416: 'REQUESTED RANGE NOT SATISFIABLE', + 417: 'EXPECTATION FAILED', + 500: 'INTERNAL SERVER ERROR', + 501: 'NOT IMPLEMENTED', + 502: 'BAD GATEWAY', + 503: 'SERVICE UNAVAILABLE', + 504: 'GATEWAY TIMEOUT', + 505: 'HTTP VERSION NOT SUPPORTED', + } + +# If web2py is executed with python2.4 we need +# to use Exception instead of BaseException + +try: + BaseException +except NameError: + BaseException = Exception + + +class HTTP(BaseException): + + def __init__( + self, + status, + body='', + **headers + ): + self.status = status + self.body = body + self.headers = headers + + def to(self, responder): + if self.status in defined_status: + status = '%d %s' % (self.status, defined_status[self.status]) + else: + status = str(self.status) + ' ' + if not 'Content-Type' in self.headers: + self.headers['Content-Type'] = 'text/html; charset=UTF-8' + body = self.body + if status[:1] == '4': + if not body: + body = status + if isinstance(body, str): + if len(body)<512 and self.headers['Content-Type'].startswith('text/html'): + body += '<!-- %s //-->' % ('x'*512) ### trick IE + self.headers['Content-Length'] = len(body) + headers = [] + for (k, v) in self.headers.items(): + if isinstance(v, list): + for item in v: + headers.append((k, str(item))) + else: + headers.append((k, str(v))) + responder(status, headers) + if hasattr(body, '__iter__') and not isinstance(self.body, str): + return body + return [str(body)] + + @property + def message(self): + ''' + compose a message describing this exception + + "status defined_status [web2py_error]" + + message elements that are not defined are omitted + ''' + msg = '%(status)d' + if self.status in defined_status: + msg = '%(status)d %(defined_status)s' + if 'web2py_error' in self.headers: + msg += ' [%(web2py_error)s]' + return msg % dict(status=self.status, + defined_status=defined_status.get(self.status), + web2py_error=self.headers.get('web2py_error')) + + def __str__(self): + "stringify me" + return self.message + + +def redirect(location, how=303): + location = location.replace('\r', '%0D').replace('\n', '%0A') + raise HTTP(how, + 'You are being redirected <a href="%s">here</a>' % location, + Location=location) + ADDED gluon/import_all.py Index: gluon/import_all.py ================================================================== --- gluon/import_all.py +++ gluon/import_all.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python + +""" +This file is part of the web2py Web Framework +Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> +License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) + +This file is not strictly required by web2py. It is used for three purposes: + +1) check that all required modules are installed properly +2) provide py2exe and py2app a list of modules to be packaged in the binary +3) (optional) preload modules in memory to speed up http responses + +""" + +import os +import sys + +base_modules = ['aifc', 'anydbm', 'array', 'asynchat', 'asyncore', 'atexit', + 'audioop', 'base64', 'BaseHTTPServer', 'Bastion', 'binascii', + 'binhex', 'bisect', 'bz2', 'calendar', 'cgi', 'CGIHTTPServer', + 'cgitb', 'chunk', 'cmath', 'cmd', 'code', 'codecs', 'codeop', + 'collections', 'colorsys', 'compileall', 'compiler', + 'compiler.ast', 'compiler.visitor', 'ConfigParser', + 'contextlib', 'Cookie', 'cookielib', 'copy', 'copy_reg', + 'cPickle', 'cProfile', 'cStringIO', 'csv', 'ctypes', + 'datetime', 'decimal', 'difflib', 'dircache', 'dis', + 'doctest', 'DocXMLRPCServer', 'dumbdbm', 'dummy_thread', + 'dummy_threading', 'email', 'email.charset', 'email.encoders', + 'email.errors', 'email.generator', 'email.header', + 'email.iterators', 'email.message', 'email.mime', + 'email.mime.audio', 'email.mime.base', 'email.mime.image', + 'email.mime.message', 'email.mime.multipart', + 'email.mime.nonmultipart', 'email.mime.text', 'email.parser', + 'email.utils', 'encodings.idna', 'errno', 'exceptions', + 'filecmp', 'fileinput', 'fnmatch', 'formatter', 'fpformat', + 'ftplib', 'functools', 'gc', 'getopt', 'getpass', 'gettext', + 'glob', 'gzip', 'hashlib', 'heapq', 'hmac', 'hotshot', + 'hotshot.stats', 'htmlentitydefs', 'htmllib', 'HTMLParser', + 'httplib', 'imaplib', 'imghdr', 'imp', 'inspect', + 'itertools', 'keyword', 'linecache', 'locale', 'logging', + 'macpath', 'mailbox', 'mailcap', 'marshal', 'math', + 'mimetools', 'mimetypes', 'mmap', 'modulefinder', 'mutex', + 'netrc', 'new', 'nntplib', 'operator', 'optparse', 'os', + 'parser', 'pdb', 'pickle', 'pickletools', 'pkgutil', + 'platform', 'poplib', 'pprint', 'py_compile', 'pyclbr', + 'pydoc', 'Queue', 'quopri', 'random', 're', 'repr', + 'rexec', 'rfc822', 'rlcompleter', 'robotparser', 'runpy', + 'sched', 'select', 'sgmllib', 'shelve', + 'shlex', 'shutil', 'signal', 'SimpleHTTPServer', + 'SimpleXMLRPCServer', 'site', 'smtpd', 'smtplib', + 'sndhdr', 'socket', 'SocketServer', 'sqlite3', + 'stat', 'statvfs', 'string', 'StringIO', + 'stringprep', 'struct', 'subprocess', 'sunau', 'symbol', + 'tabnanny', 'tarfile', 'telnetlib', 'tempfile', 'textwrap', 'thread', 'threading', + 'time', 'timeit', 'Tix', 'Tkinter', 'token', + 'tokenize', 'trace', 'traceback', 'types', + 'unicodedata', 'unittest', 'urllib', 'urllib2', + 'urlparse', 'user', 'UserDict', 'UserList', 'UserString', + 'uu', 'uuid', 'warnings', 'wave', 'weakref', 'webbrowser', + 'whichdb', 'wsgiref', 'wsgiref.handlers', 'wsgiref.headers', + 'wsgiref.simple_server', 'wsgiref.util', 'wsgiref.validate', + 'xdrlib', 'xml.dom', 'xml.dom.minidom', 'xml.dom.pulldom', + 'xml.etree.ElementTree', 'xml.parsers.expat', 'xml.sax', + 'xml.sax.handler', 'xml.sax.saxutils', 'xml.sax.xmlreader', + 'xmlrpclib', 'zipfile', 'zipimport', 'zlib', 'mhlib', + 'MimeWriter', 'mimify', 'multifile', 'sets'] + +contributed_modules = [] +for root, dirs, files in os.walk('gluon'): + for candidate in ['.'.join( + os.path.join(root, os.path.splitext(name)[0]).split(os.sep)) + for name in files if name.endswith('.py') + and root.split(os.sep) != ['gluon', 'tests'] + ]: + contributed_modules.append(candidate) + +# Python base version +python_version = sys.version[:3] + +# Modules which we want to raise an Exception if they are missing +alert_dependency = ['hashlib', 'uuid'] + +# Now we remove the blacklisted modules if we are using the stated +# python version. +# +# List of modules deprecated in Python 2.6 or 2.7 that are in the above set +py26_deprecated = ['mhlib', 'multifile', 'mimify', 'sets', 'MimeWriter'] +py27_deprecated = [] # ['optparse'] but we need it for now + +if python_version >= '2.6': + base_modules += ['json', 'multiprocessing'] + base_modules = list(set(base_modules).difference(set(py26_deprecated))) + +if python_version >= '2.7': + base_modules += ['argparse'] + base_modules = list(set(base_modules).difference(set(py27_deprecated))) + +# Now iterate in the base_modules, trying to do the import +for module in base_modules + contributed_modules: + try: + __import__(module, globals(), locals(), []) + except: + # Raise an exception if the current module is a dependency + if module in alert_dependency: + msg = "Missing dependency: %(module)s\n" % locals() + msg += "Try the following command: " + msg += "easy_install-%(python_version)s -U %(module)s" % locals() + raise ImportError, msg + ADDED gluon/languages.py Index: gluon/languages.py ================================================================== --- gluon/languages.py +++ gluon/languages.py @@ -0,0 +1,348 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +This file is part of the web2py Web Framework +Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> +License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) +""" + +import os +import re +import cgi +import portalocker +import logging +import marshal +import copy_reg +from fileutils import listdir +import settings +from cfs import getcfs + +__all__ = ['translator', 'findT', 'update_all_languages'] + +is_gae = settings.global_settings.web2py_runtime_gae + +# pattern to find T(blah blah blah) expressions + +PY_STRING_LITERAL_RE = r'(?<=[^\w]T\()(?P<name>'\ + + r"[uU]?[rR]?(?:'''(?:[^']|'{1,2}(?!'))*''')|"\ + + r"(?:'(?:[^'\\]|\\.)*')|" + r'(?:"""(?:[^"]|"{1,2}(?!"))*""")|'\ + + r'(?:"(?:[^"\\]|\\.)*"))' + +regex_translate = re.compile(PY_STRING_LITERAL_RE, re.DOTALL) + +# patter for a valid accept_language + +regex_language = \ + re.compile('^[a-zA-Z]{2}(\-[a-zA-Z]{2})?(\-[a-zA-Z]+)?$') + + +def read_dict_aux(filename): + fp = open(filename, 'r') + portalocker.lock(fp, portalocker.LOCK_SH) + lang_text = fp.read().replace('\r\n', '\n') + portalocker.unlock(fp) + fp.close() + if not lang_text.strip(): + return {} + try: + return eval(lang_text) + except: + logging.error('Syntax error in %s' % filename) + return {} + +def read_dict(filename): + return getcfs('language:%s'%filename,filename, + lambda filename=filename:read_dict_aux(filename)) + +def utf8_repr(s): + r''' # note that we use raw strings to avoid having to use double back slashes below + + utf8_repr() works same as repr() when processing ascii string + >>> utf8_repr('abc') == utf8_repr("abc") == repr('abc') == repr("abc") == "'abc'" + True + >>> utf8_repr('a"b"c') == repr('a"b"c') == '\'a"b"c\'' + True + >>> utf8_repr("a'b'c") == repr("a'b'c") == '"a\'b\'c"' + True + >>> utf8_repr('a\'b"c') == repr('a\'b"c') == utf8_repr("a'b\"c") == repr("a'b\"c") == '\'a\\\'b"c\'' + True + >>> utf8_repr('a\r\nb') == repr('a\r\nb') == "'a\\r\\nb'" # Test for \r, \n + True + + Unlike repr(), utf8_repr() remains utf8 content when processing utf8 string + >>> utf8_repr('中文字') == utf8_repr("中文字") == "'中文字'" != repr('中文字') + True + >>> utf8_repr('中"文"字') == "'中\"文\"字'" != repr('中"文"字') + True + >>> utf8_repr("中'文'字") == '"中\'文\'字"' != repr("中'文'字") + True + >>> utf8_repr('中\'文"字') == utf8_repr("中'文\"字") == '\'中\\\'文"字\'' != repr('中\'文"字') == repr("中'文\"字") + True + >>> utf8_repr('中\r\n文') == "'中\\r\\n文'" != repr('中\r\n文') # Test for \r, \n + True + ''' + if (s.find("'") >= 0) and (s.find('"') < 0): # only single quote exists + s = ''.join(['"', s, '"']) # s = ''.join(['"', s.replace('"','\\"'), '"']) + else: + s = ''.join(["'", s.replace("'","\\'"), "'"]) + return s.replace("\n","\\n").replace("\r","\\r") + + +def write_dict(filename, contents): + try: + fp = open(filename, 'w') + except IOError: + logging.error('Unable to write to file %s' % filename) + return + portalocker.lock(fp, portalocker.LOCK_EX) + fp.write('# coding: utf8\n{\n') + for key in sorted(contents): + fp.write('%s: %s,\n' % (utf8_repr(key), utf8_repr(contents[key]))) + fp.write('}\n') + portalocker.unlock(fp) + fp.close() + + +class lazyT(object): + + """ + never to be called explicitly, returned by translator.__call__ + """ + + m = None + s = None + T = None + + def __init__( + self, + message, + symbols = {}, + T = None, + ): + self.m = message + self.s = symbols + self.T = T + + def __repr__(self): + return "<lazyT %s>" % (repr(str(self.m)), ) + + def __str__(self): + return self.T.translate(self.m, self.s) + + def __eq__(self, other): + return self.T.translate(self.m, self.s) == other + + def __ne__(self, other): + return self.T.translate(self.m, self.s) != other + + def __add__(self, other): + return '%s%s' % (self, other) + + def __radd__(self, other): + return '%s%s' % (other, self) + + def __cmp__(self,other): + return cmp(str(self),str(other)) + + def __hash__(self): + return hash(str(self)) + + def __getattr__(self, name): + return getattr(str(self),name) + + def __getitem__(self, i): + return str(self)[i] + + def __getslice__(self, i, j): + return str(self)[i:j] + + def __iter__(self): + for c in str(self): yield c + + def __len__(self): + return len(str(self)) + + def xml(self): + return cgi.escape(str(self)) + + def encode(self, *a, **b): + return str(self).encode(*a, **b) + + def decode(self, *a, **b): + return str(self).decode(*a, **b) + + def read(self): + return str(self) + + def __mod__(self, symbols): + return self.T.translate(self.m, symbols) + + +class translator(object): + + """ + this class is instantiated by gluon.compileapp.build_environment + as the T object + + :: + + T.force(None) # turns off translation + T.force('fr, it') # forces web2py to translate using fr.py or it.py + + T(\"Hello World\") # translates \"Hello World\" using the selected file + + notice 1: there is no need to force since, by default, T uses + accept_language to determine a translation file. + + notice 2: en and en-en are considered different languages! + """ + + def __init__(self, request): + self.request = request + self.folder = request.folder + self.current_languages = ['en'] + self.accepted_language = None + self.language_file = None + self.http_accept_language = request.env.http_accept_language + self.requested_languages = self.force(self.http_accept_language) + self.lazy = True + self.otherTs = {} + + def get_possible_languages(self): + possible_languages = self.current_languages + file_ending = re.compile("\.py$") + for langfile in os.listdir(os.path.join(self.folder,'languages')): + if file_ending.search(langfile): + possible_languages.append(file_ending.sub('',langfile)) + return possible_languages + + def set_current_languages(self, *languages): + if len(languages) == 1 and isinstance(languages[0], (tuple, list)): + languages = languages[0] + self.current_languages = languages + self.force(self.http_accept_language) + + def force(self, *languages): + if not languages or languages[0] == None: + languages = [] + if len(languages) == 1 and isinstance(languages[0], (str, unicode)): + languages = languages[0] + if languages: + if isinstance(languages, (str, unicode)): + accept_languages = languages.split(';') + languages = [] + [languages.extend(al.split(',')) for al in accept_languages] + languages = [item.strip().lower() for item in languages \ + if regex_language.match(item.strip())] + + for language in languages: + if language in self.current_languages: + self.accepted_language = language + break + filename = os.path.join(self.folder, 'languages/', language + '.py') + if os.path.exists(filename): + self.accepted_language = language + self.language_file = filename + self.t = read_dict(filename) + return languages + self.language_file = None + self.t = {} # ## no language by default + return languages + + def __call__(self, message, symbols={},language=None): + if not language: + if self.lazy: + return lazyT(message, symbols, self) + else: + return self.translate(message, symbols) + else: + try: + otherT = self.otherTs[language] + except KeyError: + otherT = self.otherTs[language] = translator(self.request) + otherT.force(language) + return otherT(message,symbols) + + def translate(self, message, symbols): + """ + user ## to add a comment into a translation string + the comment can be useful do discriminate different possible + translations for the same string (for example different locations) + + T(' hello world ') -> ' hello world ' + T(' hello world ## token') -> 'hello world' + T('hello ## world ## token') -> 'hello ## world' + + the ## notation is ignored in multiline strings and strings that + start with ##. this is to allow markmin syntax to be translated + """ + if not message.startswith('#') and not '\n' in message: + tokens = message.rsplit('##', 1) + else: + # this allows markmin syntax in translations + tokens = [message] + if len(tokens) == 2: + tokens[0] = tokens[0].strip() + message = tokens[0] + '##' + tokens[1].strip() + mt = self.t.get(message, None) + if mt == None: + self.t[message] = mt = tokens[0] + if self.language_file and not is_gae: + write_dict(self.language_file, self.t) + if symbols or symbols == 0: + return mt % symbols + return mt + + +def findT(path, language='en-us'): + """ + must be run by the admin app + """ + filename = os.path.join(path, 'languages', '%s.py' % language) + sentences = read_dict(filename) + mp = os.path.join(path, 'models') + cp = os.path.join(path, 'controllers') + vp = os.path.join(path, 'views') + for file in listdir(mp, '.+\.py', 0) + listdir(cp, '.+\.py', 0)\ + + listdir(vp, '.+\.html', 0): + fp = open(file, 'r') + portalocker.lock(fp, portalocker.LOCK_SH) + data = fp.read() + portalocker.unlock(fp) + fp.close() + items = regex_translate.findall(data) + for item in items: + try: + message = eval(item) + if not message.startswith('#') and not '\n' in message: + tokens = message.rsplit('##', 1) + else: + # this allows markmin syntax in translations + tokens = [message] + if len(tokens) == 2: + message = tokens[0].strip() + '##' + tokens[1].strip() + if message and not message in sentences: + sentences[message] = message + except: + pass + write_dict(filename, sentences) + +### important to allow safe session.flash=T(....) +def lazyT_unpickle(data): + return marshal.loads(data) +def lazyT_pickle(data): + return lazyT_unpickle, (marshal.dumps(str(data)),) +copy_reg.pickle(lazyT, lazyT_pickle, lazyT_unpickle) + +def update_all_languages(application_path): + path = os.path.join(application_path, 'languages/') + for language in listdir(path, '^\w+(\-\w+)?\.py$'): + findT(application_path, language[:-3]) + + +if __name__ == '__main__': + import doctest + doctest.testmod() + + ADDED gluon/main.py Index: gluon/main.py ================================================================== --- gluon/main.py +++ gluon/main.py @@ -0,0 +1,810 @@ +#!/bin/env python +# -*- coding: utf-8 -*- + +""" +This file is part of the web2py Web Framework +Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> +License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) + +Contains: + +- wsgibase: the gluon wsgi application + +""" + +import gc +import cgi +import cStringIO +import Cookie +import os +import re +import copy +import sys +import time +import thread +import datetime +import signal +import socket +import tempfile +import random +import string +import platform +from fileutils import abspath, write_file +from settings import global_settings +from admin import add_path_first, create_missing_folders, create_missing_app_folders +from globals import current + +from custom_import import custom_import_install +from contrib.simplejson import dumps + +# Remarks: +# calling script has inserted path to script directory into sys.path +# applications_parent (path to applications/, site-packages/ etc) +# defaults to that directory set sys.path to +# ("", gluon_parent/site-packages, gluon_parent, ...) +# +# this is wrong: +# web2py_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +# because we do not want the path to this file which may be Library.zip +# gluon_parent is the directory containing gluon, web2py.py, logging.conf +# and the handlers. +# applications_parent (web2py_path) is the directory containing applications/ +# and routes.py +# The two are identical unless web2py_path is changed via the web2py.py -f folder option +# main.web2py_path is the same as applications_parent (for backward compatibility) + +if not hasattr(os, 'mkdir'): + global_settings.db_sessions = True +if global_settings.db_sessions is not True: + global_settings.db_sessions = set() +global_settings.gluon_parent = os.environ.get('web2py_path', os.getcwd()) +global_settings.applications_parent = global_settings.gluon_parent +web2py_path = global_settings.applications_parent # backward compatibility +global_settings.app_folders = set() +global_settings.debugging = False + +custom_import_install(web2py_path) + +create_missing_folders() + +# set up logging for subsequent imports +import logging +import logging.config +logpath = abspath("logging.conf") +if os.path.exists(logpath): + logging.config.fileConfig(abspath("logging.conf")) +else: + logging.basicConfig() +logger = logging.getLogger("web2py") + +from restricted import RestrictedError +from http import HTTP, redirect +from globals import Request, Response, Session +from compileapp import build_environment, run_models_in, \ + run_controller_in, run_view_in +from fileutils import copystream +from contenttype import contenttype +from dal import BaseAdapter +from settings import global_settings +from validators import CRYPT +from cache import Cache +from html import URL as Url +import newcron +import rewrite + +__all__ = ['wsgibase', 'save_password', 'appfactory', 'HttpServer'] + +requests = 0 # gc timer + +# Security Checks: validate URL and session_id here, +# accept_language is validated in languages + +# pattern used to validate client address +regex_client = re.compile('[\w\-:]+(\.[\w\-]+)*\.?') # ## to account for IPV6 + +version_info = open(abspath('VERSION', gluon=True), 'r') +web2py_version = version_info.read() +version_info.close() + +try: + import rocket +except: + if not global_settings.web2py_runtime_gae: + logger.warn('unable to import Rocket') + +rewrite.load() + +def get_client(env): + """ + guess the client address from the environment variables + + first tries 'http_x_forwarded_for', secondly 'remote_addr' + if all fails assume '127.0.0.1' (running locally) + """ + g = regex_client.search(env.get('http_x_forwarded_for', '')) + if g: + return g.group() + g = regex_client.search(env.get('remote_addr', '')) + if g: + return g.group() + return '127.0.0.1' + +def copystream_progress(request, chunk_size= 10**5): + """ + copies request.env.wsgi_input into request.body + and stores progress upload status in cache.ram + X-Progress-ID:length and X-Progress-ID:uploaded + """ + if not request.env.content_length: + return cStringIO.StringIO() + source = request.env.wsgi_input + size = int(request.env.content_length) + dest = tempfile.TemporaryFile() + if not 'X-Progress-ID' in request.vars: + copystream(source, dest, size, chunk_size) + return dest + cache_key = 'X-Progress-ID:'+request.vars['X-Progress-ID'] + cache = Cache(request) + cache.ram(cache_key+':length', lambda: size, 0) + cache.ram(cache_key+':uploaded', lambda: 0, 0) + while size > 0: + if size < chunk_size: + data = source.read(size) + cache.ram.increment(cache_key+':uploaded', size) + else: + data = source.read(chunk_size) + cache.ram.increment(cache_key+':uploaded', chunk_size) + length = len(data) + if length > size: + (data, length) = (data[:size], size) + size -= length + if length == 0: + break + dest.write(data) + if length < chunk_size: + break + dest.seek(0) + cache.ram(cache_key+':length', None) + cache.ram(cache_key+':uploaded', None) + return dest + + +def serve_controller(request, response, session): + """ + this function is used to generate a dynamic page. + It first runs all models, then runs the function in the controller, + and then tries to render the output using a view/template. + this function must run from the [application] folder. + A typical example would be the call to the url + /[application]/[controller]/[function] that would result in a call + to [function]() in applications/[application]/[controller].py + rendered by applications/[application]/views/[controller]/[function].html + """ + + # ################################################## + # build environment for controller and view + # ################################################## + + environment = build_environment(request, response, session) + + # set default view, controller can override it + + response.view = '%s/%s.%s' % (request.controller, + request.function, + request.extension) + + # also, make sure the flash is passed through + # ################################################## + # process models, controller and view (if required) + # ################################################## + + run_models_in(environment) + response._view_environment = copy.copy(environment) + page = run_controller_in(request.controller, request.function, environment) + if isinstance(page, dict): + response._vars = page + for key in page: + response._view_environment[key] = page[key] + run_view_in(response._view_environment) + page = response.body.getvalue() + # logic to garbage collect after exec, not always, once every 100 requests + global requests + requests = ('requests' in globals()) and (requests+1) % 100 or 0 + if not requests: gc.collect() + # end garbage collection logic + raise HTTP(response.status, page, **response.headers) + + +def start_response_aux(status, headers, exc_info, response=None): + """ + in controller you can use:: + + - request.wsgi.environ + - request.wsgi.start_response + + to call third party WSGI applications + """ + response.status = str(status).split(' ',1)[0] + response.headers = dict(headers) + return lambda *args, **kargs: response.write(escape=False,*args,**kargs) + + +def middleware_aux(request, response, *middleware_apps): + """ + In you controller use:: + + @request.wsgi.middleware(middleware1, middleware2, ...) + + to decorate actions with WSGI middleware. actions must return strings. + uses a simulated environment so it may have weird behavior in some cases + """ + def middleware(f): + def app(environ, start_response): + data = f() + start_response(response.status,response.headers.items()) + if isinstance(data,list): + return data + return [data] + for item in middleware_apps: + app=item(app) + def caller(app): + return app(request.wsgi.environ,request.wsgi.start_response) + return lambda caller=caller, app=app: caller(app) + return middleware + +def environ_aux(environ,request): + new_environ = copy.copy(environ) + new_environ['wsgi.input'] = request.body + new_environ['wsgi.version'] = 1 + return new_environ + +def parse_get_post_vars(request, environ): + + # always parse variables in URL for GET, POST, PUT, DELETE, etc. in get_vars + dget = cgi.parse_qsl(request.env.query_string or '', keep_blank_values=1) + for (key, value) in dget: + if key in request.get_vars: + if isinstance(request.get_vars[key], list): + request.get_vars[key] += [value] + else: + request.get_vars[key] = [request.get_vars[key]] + [value] + else: + request.get_vars[key] = value + request.vars[key] = request.get_vars[key] + + # parse POST variables on POST, PUT, BOTH only in post_vars + request.body = copystream_progress(request) ### stores request body + if (request.body and request.env.request_method in ('POST', 'PUT', 'BOTH')): + dpost = cgi.FieldStorage(fp=request.body,environ=environ,keep_blank_values=1) + # The same detection used by FieldStorage to detect multipart POSTs + is_multipart = dpost.type[:10] == 'multipart/' + request.body.seek(0) + isle25 = sys.version_info[1] <= 5 + + def listify(a): + return (not isinstance(a,list) and [a]) or a + try: + keys = sorted(dpost) + except TypeError: + keys = [] + for key in keys: + dpk = dpost[key] + # if en element is not a file replace it with its value else leave it alone + if isinstance(dpk, list): + if not dpk[0].filename: + value = [x.value for x in dpk] + else: + value = [x for x in dpk] + elif not dpk.filename: + value = dpk.value + else: + value = dpk + pvalue = listify(value) + if key in request.vars: + gvalue = listify(request.vars[key]) + if isle25: + value = pvalue + gvalue + elif is_multipart: + pvalue = pvalue[len(gvalue):] + else: + pvalue = pvalue[:-len(gvalue)] + request.vars[key] = value + if len(pvalue): + request.post_vars[key] = (len(pvalue)>1 and pvalue) or pvalue[0] + + +def wsgibase(environ, responder): + """ + this is the gluon wsgi application. the first function called when a page + is requested (static or dynamic). it can be called by paste.httpserver + or by apache mod_wsgi. + + - fills request with info + - the environment variables, replacing '.' with '_' + - adds web2py path and version info + - compensates for fcgi missing path_info and query_string + - validates the path in url + + The url path must be either: + + 1. for static pages: + + - /<application>/static/<file> + + 2. for dynamic pages: + + - /<application>[/<controller>[/<function>[/<sub>]]][.<extension>] + - (sub may go several levels deep, currently 3 levels are supported: + sub1/sub2/sub3) + + The naming conventions are: + + - application, controller, function and extension may only contain + [a-zA-Z0-9_] + - file and sub may also contain '-', '=', '.' and '/' + """ + + current.__dict__.clear() + request = Request() + response = Response() + session = Session() + request.env.web2py_path = global_settings.applications_parent + request.env.web2py_version = web2py_version + request.env.update(global_settings) + static_file = False + try: + try: + try: + # ################################################## + # handle fcgi missing path_info and query_string + # select rewrite parameters + # rewrite incoming URL + # parse rewritten header variables + # parse rewritten URL + # serve file if static + # ################################################## + + if not environ.get('PATH_INFO',None) and \ + environ.get('REQUEST_URI',None): + # for fcgi, get path_info and query_string from request_uri + items = environ['REQUEST_URI'].split('?') + environ['PATH_INFO'] = items[0] + if len(items) > 1: + environ['QUERY_STRING'] = items[1] + else: + environ['QUERY_STRING'] = '' + if not environ.get('HTTP_HOST',None): + environ['HTTP_HOST'] = '%s:%s' % (environ.get('SERVER_NAME'), + environ.get('SERVER_PORT')) + + (static_file, environ) = rewrite.url_in(request, environ) + if static_file: + if request.env.get('query_string', '')[:10] == 'attachment': + response.headers['Content-Disposition'] = 'attachment' + response.stream(static_file, request=request) + + # ################################################## + # fill in request items + # ################################################## + + http_host = request.env.http_host.split(':',1)[0] + + local_hosts = [http_host,'::1','127.0.0.1','::ffff:127.0.0.1'] + if not global_settings.web2py_runtime_gae: + local_hosts += [socket.gethostname(), + socket.gethostbyname(http_host)] + request.client = get_client(request.env) + request.folder = abspath('applications', + request.application) + os.sep + x_req_with = str(request.env.http_x_requested_with).lower() + request.ajax = x_req_with == 'xmlhttprequest' + request.cid = request.env.http_web2py_component_element + request.is_local = request.env.remote_addr in local_hosts + request.is_https = request.env.wsgi_url_scheme \ + in ['https', 'HTTPS'] or request.env.https == 'on' + + # ################################################## + # compute a request.uuid to be used for tickets and toolbar + # ################################################## + + response.uuid = request.compute_uuid() + + # ################################################## + # access the requested application + # ################################################## + + if not os.path.exists(request.folder): + if request.application == rewrite.thread.routes.default_application and request.application != 'welcome': + request.application = 'welcome' + redirect(Url(r=request)) + elif rewrite.thread.routes.error_handler: + redirect(Url(rewrite.thread.routes.error_handler['application'], + rewrite.thread.routes.error_handler['controller'], + rewrite.thread.routes.error_handler['function'], + args=request.application)) + else: + raise HTTP(404, + rewrite.thread.routes.error_message % 'invalid request', + web2py_error='invalid application') + request.url = Url(r=request, args=request.args, + extension=request.raw_extension) + + # ################################################## + # build missing folders + # ################################################## + + create_missing_app_folders(request) + + # ################################################## + # get the GET and POST data + # ################################################## + + parse_get_post_vars(request, environ) + + # ################################################## + # expose wsgi hooks for convenience + # ################################################## + + request.wsgi.environ = environ_aux(environ,request) + request.wsgi.start_response = lambda status='200', headers=[], \ + exec_info=None, response=response: \ + start_response_aux(status, headers, exec_info, response) + request.wsgi.middleware = lambda *a: middleware_aux(request,response,*a) + + # ################################################## + # load cookies + # ################################################## + + if request.env.http_cookie: + try: + request.cookies.load(request.env.http_cookie) + except Cookie.CookieError, e: + pass # invalid cookies + + # ################################################## + # try load session or create new session file + # ################################################## + + session.connect(request, response) + + # ################################################## + # set no-cache headers + # ################################################## + + response.headers['Content-Type'] = contenttype('.'+request.extension) + response.headers['Cache-Control'] = \ + 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0' + response.headers['Expires'] = \ + time.strftime('%a, %d %b %Y %H:%M:%S GMT', time.gmtime()) + response.headers['Pragma'] = 'no-cache' + + # ################################################## + # run controller + # ################################################## + + serve_controller(request, response, session) + + except HTTP, http_response: + if static_file: + return http_response.to(responder) + + if request.body: + request.body.close() + + # ################################################## + # on success, try store session in database + # ################################################## + session._try_store_in_db(request, response) + + # ################################################## + # on success, commit database + # ################################################## + + if response._custom_commit: + response._custom_commit() + else: + BaseAdapter.close_all_instances('commit') + + # ################################################## + # if session not in db try store session on filesystem + # this must be done after trying to commit database! + # ################################################## + + session._try_store_on_disk(request, response) + + # ################################################## + # store cookies in headers + # ################################################## + + if request.cid: + + if response.flash and not 'web2py-component-flash' in http_response.headers: + http_response.headers['web2py-component-flash'] = \ + str(response.flash).replace('\n','') + if response.js and not 'web2py-component-command' in http_response.headers: + http_response.headers['web2py-component-command'] = \ + response.js.replace('\n','') + if session._forget and \ + response.session_id_name in response.cookies: + del response.cookies[response.session_id_name] + elif session._secure: + response.cookies[response.session_id_name]['secure'] = True + if len(response.cookies)>0: + http_response.headers['Set-Cookie'] = \ + [str(cookie)[11:] for cookie in response.cookies.values()] + ticket=None + + except RestrictedError, e: + + if request.body: + request.body.close() + + # ################################################## + # on application error, rollback database + # ################################################## + + ticket = e.log(request) or 'unknown' + if response._custom_rollback: + response._custom_rollback() + else: + BaseAdapter.close_all_instances('rollback') + + http_response = \ + HTTP(500, + rewrite.thread.routes.error_message_ticket % dict(ticket=ticket), + web2py_error='ticket %s' % ticket) + + except: + + if request.body: + request.body.close() + + # ################################################## + # on application error, rollback database + # ################################################## + + try: + if response._custom_rollback: + response._custom_rollback() + else: + BaseAdapter.close_all_instances('rollback') + except: + pass + e = RestrictedError('Framework', '', '', locals()) + ticket = e.log(request) or 'unrecoverable' + http_response = \ + HTTP(500, + rewrite.thread.routes.error_message_ticket % dict(ticket=ticket), + web2py_error='ticket %s' % ticket) + + finally: + if response and hasattr(response, 'session_file') and response.session_file: + response.session_file.close() +# if global_settings.debugging: +# import gluon.debug +# gluon.debug.stop_trace() + + session._unlock(response) + http_response, new_environ = rewrite.try_rewrite_on_error( + http_response, request, environ, ticket) + if not http_response: + return wsgibase(new_environ,responder) + if global_settings.web2py_crontype == 'soft': + newcron.softcron(global_settings.applications_parent).start() + return http_response.to(responder) + + +def save_password(password, port): + """ + used by main() to save the password in the parameters_port.py file. + """ + + password_file = abspath('parameters_%i.py' % port) + if password == '<random>': + # make up a new password + chars = string.letters + string.digits + password = ''.join([random.choice(chars) for i in range(8)]) + cpassword = CRYPT()(password)[0] + print '******************* IMPORTANT!!! ************************' + print 'your admin password is "%s"' % password + print '*********************************************************' + elif password == '<recycle>': + # reuse the current password if any + if os.path.exists(password_file): + return + else: + password = '' + elif password.startswith('<pam_user:'): + # use the pam password for specified user + cpassword = password[1:-1] + else: + # use provided password + cpassword = CRYPT()(password)[0] + fp = open(password_file, 'w') + if password: + fp.write('password="%s"\n' % cpassword) + else: + fp.write('password=None\n') + fp.close() + + +def appfactory(wsgiapp=wsgibase, + logfilename='httpserver.log', + profilerfilename='profiler.log'): + """ + generates a wsgi application that does logging and profiling and calls + wsgibase + + .. function:: gluon.main.appfactory( + [wsgiapp=wsgibase + [, logfilename='httpserver.log' + [, profilerfilename='profiler.log']]]) + + """ + if profilerfilename and os.path.exists(profilerfilename): + os.unlink(profilerfilename) + locker = thread.allocate_lock() + + def app_with_logging(environ, responder): + """ + a wsgi app that does logging and profiling and calls wsgibase + """ + status_headers = [] + + def responder2(s, h): + """ + wsgi responder app + """ + status_headers.append(s) + status_headers.append(h) + return responder(s, h) + + time_in = time.time() + ret = [0] + if not profilerfilename: + ret[0] = wsgiapp(environ, responder2) + else: + import cProfile + import pstats + logger.warn('profiler is on. this makes web2py slower and serial') + + locker.acquire() + cProfile.runctx('ret[0] = wsgiapp(environ, responder2)', + globals(), locals(), profilerfilename+'.tmp') + stat = pstats.Stats(profilerfilename+'.tmp') + stat.stream = cStringIO.StringIO() + stat.strip_dirs().sort_stats("time").print_stats(80) + profile_out = stat.stream.getvalue() + profile_file = open(profilerfilename, 'a') + profile_file.write('%s\n%s\n%s\n%s\n\n' % \ + ('='*60, environ['PATH_INFO'], '='*60, profile_out)) + profile_file.close() + locker.release() + try: + line = '%s, %s, %s, %s, %s, %s, %f\n' % ( + environ['REMOTE_ADDR'], + datetime.datetime.today().strftime('%Y-%m-%d %H:%M:%S'), + environ['REQUEST_METHOD'], + environ['PATH_INFO'].replace(',', '%2C'), + environ['SERVER_PROTOCOL'], + (status_headers[0])[:3], + time.time() - time_in, + ) + if not logfilename: + sys.stdout.write(line) + elif isinstance(logfilename, str): + write_file(logfilename, line, 'a') + else: + logfilename.write(line) + except: + pass + return ret[0] + + return app_with_logging + + +class HttpServer(object): + """ + the web2py web server (Rocket) + """ + + def __init__( + self, + ip='127.0.0.1', + port=8000, + password='', + pid_filename='httpserver.pid', + log_filename='httpserver.log', + profiler_filename=None, + ssl_certificate=None, + ssl_private_key=None, + min_threads=None, + max_threads=None, + server_name=None, + request_queue_size=5, + timeout=10, + shutdown_timeout=None, # Rocket does not use a shutdown timeout + path=None, + interfaces=None # Rocket is able to use several interfaces - must be list of socket-tuples as string + ): + """ + starts the web server. + """ + + if interfaces: + # if interfaces is specified, it must be tested for rocket parameter correctness + # not necessarily completely tested (e.g. content of tuples or ip-format) + import types + if isinstance(interfaces,types.ListType): + for i in interfaces: + if not isinstance(i,types.TupleType): + raise "Wrong format for rocket interfaces parameter - see http://packages.python.org/rocket/" + else: + raise "Wrong format for rocket interfaces parameter - see http://packages.python.org/rocket/" + + if path: + # if a path is specified change the global variables so that web2py + # runs from there instead of cwd or os.environ['web2py_path'] + global web2py_path + path = os.path.normpath(path) + web2py_path = path + global_settings.applications_parent = path + os.chdir(path) + [add_path_first(p) for p in (path, abspath('site-packages'), "")] + + save_password(password, port) + self.pid_filename = pid_filename + if not server_name: + server_name = socket.gethostname() + logger.info('starting web server...') + rocket.SERVER_NAME = server_name + sock_list = [ip, port] + if not ssl_certificate or not ssl_private_key: + logger.info('SSL is off') + elif not rocket.ssl: + logger.warning('Python "ssl" module unavailable. SSL is OFF') + elif not os.path.exists(ssl_certificate): + logger.warning('unable to open SSL certificate. SSL is OFF') + elif not os.path.exists(ssl_private_key): + logger.warning('unable to open SSL private key. SSL is OFF') + else: + sock_list.extend([ssl_private_key, ssl_certificate]) + logger.info('SSL is ON') + app_info = {'wsgi_app': appfactory(wsgibase, + log_filename, + profiler_filename) } + + self.server = rocket.Rocket(interfaces or tuple(sock_list), + method='wsgi', + app_info=app_info, + min_threads=min_threads, + max_threads=max_threads, + queue_size=int(request_queue_size), + timeout=int(timeout), + handle_signals=False, + ) + + + def start(self): + """ + start the web server + """ + try: + signal.signal(signal.SIGTERM, lambda a, b, s=self: s.stop()) + signal.signal(signal.SIGINT, lambda a, b, s=self: s.stop()) + except: + pass + write_file(self.pid_filename, str(os.getpid())) + self.server.start() + + def stop(self, stoplogging=False): + """ + stop cron and the web server + """ + newcron.stopcron() + self.server.stop(stoplogging) + try: + os.unlink(self.pid_filename) + except: + pass + ADDED gluon/myregex.py Index: gluon/myregex.py ================================================================== --- gluon/myregex.py +++ gluon/myregex.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +This file is part of the web2py Web Framework +Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> +License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) +""" + +import re + +# pattern to find defined tables + +regex_tables = re.compile(\ + """^[\w]+\.define_table\(\s*[\'\"](?P<name>[\w_]+)[\'\"]""", + flags=re.M) + +# pattern to find exposed functions in controller + +regex_expose = re.compile(\ + '^def\s+(?P<name>(?:[a-zA-Z0-9]\w*)|(?:_[a-zA-Z0-9]\w*))\(\)\s*:', + flags=re.M) + +regex_include = re.compile(\ + '(?P<all>\{\{\s*include\s+[\'"](?P<name>[^\'"]*)[\'"]\s*\}\})') + +regex_extend = re.compile(\ + '^\s*(?P<all>\{\{\s*extend\s+[\'"](?P<name>[^\'"]+)[\'"]\s*\}\})',re.MULTILINE) + ADDED gluon/newcron.py Index: gluon/newcron.py ================================================================== --- gluon/newcron.py +++ gluon/newcron.py @@ -0,0 +1,313 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Created by Attila Csipa <web2py@csipa.in.rs> +Modified by Massimo Di Pierro <mdipierro@cs.depaul.edu> +""" + +import sys +import os +import threading +import logging +import time +import sched +import re +import datetime +import platform +import portalocker +import fileutils +import cPickle +from settings import global_settings + +logger = logging.getLogger("web2py.cron") +_cron_stopping = False + +def stopcron(): + "graceful shutdown of cron" + global _cron_stopping + _cron_stopping = True + +class extcron(threading.Thread): + + def __init__(self, applications_parent): + threading.Thread.__init__(self) + self.setDaemon(False) + self.path = applications_parent + crondance(self.path, 'external', startup=True) + + def run(self): + if not _cron_stopping: + logger.debug('external cron invocation') + crondance(self.path, 'external', startup=False) + +class hardcron(threading.Thread): + + def __init__(self, applications_parent): + threading.Thread.__init__(self) + self.setDaemon(True) + self.path = applications_parent + crondance(self.path, 'hard', startup=True) + + def launch(self): + if not _cron_stopping: + logger.debug('hard cron invocation') + crondance(self.path, 'hard', startup = False) + + def run(self): + s = sched.scheduler(time.time, time.sleep) + logger.info('Hard cron daemon started') + while not _cron_stopping: + now = time.time() + s.enter(60 - now % 60, 1, self.launch, ()) + s.run() + +class softcron(threading.Thread): + + def __init__(self, applications_parent): + threading.Thread.__init__(self) + self.path = applications_parent + crondance(self.path, 'soft', startup=True) + + def run(self): + if not _cron_stopping: + logger.debug('soft cron invocation') + crondance(self.path, 'soft', startup=False) + +class Token(object): + + def __init__(self,path): + self.path = os.path.join(path, 'cron.master') + if not os.path.exists(self.path): + fileutils.write_file(self.path, '', 'wb') + self.master = None + self.now = time.time() + + def acquire(self,startup=False): + """ + returns the time when the lock is acquired or + None if cron already running + + lock is implemented by writing a pickle (start, stop) in cron.master + start is time when cron job starts and stop is time when cron completed + stop == 0 if job started but did not yet complete + if a cron job started within less than 60 seconds, acquire returns None + if a cron job started before 60 seconds and did not stop, + a warning is issue "Stale cron.master detected" + """ + if portalocker.LOCK_EX == None: + logger.warning('WEB2PY CRON: Disabled because no file locking') + return None + self.master = open(self.path,'rb+') + try: + ret = None + portalocker.lock(self.master,portalocker.LOCK_EX) + try: + (start, stop) = cPickle.load(self.master) + except: + (start, stop) = (0, 1) + if startup or self.now - start > 59.99: + ret = self.now + if not stop: + # this happens if previous cron job longer than 1 minute + logger.warning('WEB2PY CRON: Stale cron.master detected') + logger.debug('WEB2PY CRON: Acquiring lock') + self.master.seek(0) + cPickle.dump((self.now,0),self.master) + finally: + portalocker.unlock(self.master) + if not ret: + # do this so no need to release + self.master.close() + return ret + + def release(self): + """ + this function writes into cron.master the time when cron job + was completed + """ + if not self.master.closed: + portalocker.lock(self.master,portalocker.LOCK_EX) + logger.debug('WEB2PY CRON: Releasing cron lock') + self.master.seek(0) + (start, stop) = cPickle.load(self.master) + if start == self.now: # if this is my lock + self.master.seek(0) + cPickle.dump((self.now,time.time()),self.master) + portalocker.unlock(self.master) + self.master.close() + + +def rangetolist(s, period='min'): + retval = [] + if s.startswith('*'): + if period == 'min': + s = s.replace('*', '0-59', 1) + elif period == 'hr': + s = s.replace('*', '0-23', 1) + elif period == 'dom': + s = s.replace('*', '1-31', 1) + elif period == 'mon': + s = s.replace('*', '1-12', 1) + elif period == 'dow': + s = s.replace('*', '0-6', 1) + m = re.compile(r'(\d+)-(\d+)/(\d+)') + match = m.match(s) + if match: + for i in range(int(match.group(1)), int(match.group(2)) + 1): + if i % int(match.group(3)) == 0: + retval.append(i) + return retval + + +def parsecronline(line): + task = {} + if line.startswith('@reboot'): + line=line.replace('@reboot', '-1 * * * *') + elif line.startswith('@yearly'): + line=line.replace('@yearly', '0 0 1 1 *') + elif line.startswith('@annually'): + line=line.replace('@annually', '0 0 1 1 *') + elif line.startswith('@monthly'): + line=line.replace('@monthly', '0 0 1 * *') + elif line.startswith('@weekly'): + line=line.replace('@weekly', '0 0 * * 0') + elif line.startswith('@daily'): + line=line.replace('@daily', '0 0 * * *') + elif line.startswith('@midnight'): + line=line.replace('@midnight', '0 0 * * *') + elif line.startswith('@hourly'): + line=line.replace('@hourly', '0 * * * *') + params = line.strip().split(None, 6) + if len(params) < 7: + return None + daysofweek={'sun':0,'mon':1,'tue':2,'wed':3,'thu':4,'fri':5,'sat':6} + for (s, id) in zip(params[:5], ['min', 'hr', 'dom', 'mon', 'dow']): + if not s in [None, '*']: + task[id] = [] + vals = s.split(',') + for val in vals: + if val != '-1' and '-' in val and '/' not in val: + val = '%s/1' % val + if '/' in val: + task[id] += rangetolist(val, id) + elif val.isdigit() or val=='-1': + task[id].append(int(val)) + elif id=='dow' and val[:3].lower() in daysofweek: + task[id].append(daysofweek(val[:3].lower())) + task['user'] = params[5] + task['cmd'] = params[6] + return task + + +class cronlauncher(threading.Thread): + + def __init__(self, cmd, shell=True): + threading.Thread.__init__(self) + if platform.system() == 'Windows': + shell = False + elif isinstance(cmd,list): + cmd = ' '.join(cmd) + self.cmd = cmd + self.shell = shell + + def run(self): + import subprocess + proc = subprocess.Popen(self.cmd, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + shell=self.shell) + (stdoutdata,stderrdata) = proc.communicate() + if proc.returncode != 0: + logger.warning( + 'WEB2PY CRON Call returned code %s:\n%s' % \ + (proc.returncode, stdoutdata+stderrdata)) + else: + logger.debug('WEB2PY CRON Call returned success:\n%s' \ + % stdoutdata) + +def crondance(applications_parent, ctype='soft', startup=False): + apppath = os.path.join(applications_parent,'applications') + cron_path = os.path.join(apppath,'admin','cron') + token = Token(cron_path) + cronmaster = token.acquire(startup=startup) + if not cronmaster: + return + now_s = time.localtime() + checks=(('min',now_s.tm_min), + ('hr',now_s.tm_hour), + ('mon',now_s.tm_mon), + ('dom',now_s.tm_mday), + ('dow',(now_s.tm_wday+1)%7)) + + apps = [x for x in os.listdir(apppath) + if os.path.isdir(os.path.join(apppath, x))] + + for app in apps: + if _cron_stopping: + break; + apath = os.path.join(apppath,app) + cronpath = os.path.join(apath, 'cron') + crontab = os.path.join(cronpath, 'crontab') + if not os.path.exists(crontab): + continue + try: + cronlines = fileutils.readlines_file(crontab, 'rt') + lines = [x.strip() for x in cronlines if x.strip() and not x.strip().startswith('#')] + tasks = [parsecronline(cline) for cline in lines] + except Exception, e: + logger.error('WEB2PY CRON: crontab read error %s' % e) + continue + + for task in tasks: + if _cron_stopping: + break; + commands = [sys.executable] + w2p_path = fileutils.abspath('web2py.py', gluon=True) + if os.path.exists(w2p_path): + commands.append(w2p_path) + if global_settings.applications_parent != global_settings.gluon_parent: + commands.extend(('-f', global_settings.applications_parent)) + citems = [(k in task and not v in task[k]) for k,v in checks] + task_min= task.get('min',[]) + if not task: + continue + elif not startup and task_min == [-1]: + continue + elif task_min != [-1] and reduce(lambda a,b: a or b, citems): + continue + logger.info('WEB2PY CRON (%s): %s executing %s in %s at %s' \ + % (ctype, app, task.get('cmd'), + os.getcwd(), datetime.datetime.now())) + action, command, models = False, task['cmd'], '' + if command.startswith('**'): + (action,models,command) = (True,'',command[2:]) + elif command.startswith('*'): + (action,models,command) = (True,'-M',command[1:]) + else: + action=False + if action and command.endswith('.py'): + commands.extend(('-J', # cron job + models, # import models? + '-S', app, # app name + '-a', '"<recycle>"', # password + '-R', command)) # command + shell = True + elif action: + commands.extend(('-J', # cron job + models, # import models? + '-S', app+'/'+command, # app name + '-a', '"<recycle>"')) # password + shell = True + else: + commands = command + shell = False + try: + cronlauncher(commands, shell=shell).start() + except Exception, e: + logger.warning( + 'WEB2PY CRON: Execution error for %s: %s' \ + % (task.get('cmd'), e)) + token.release() + ADDED gluon/portalocker.py Index: gluon/portalocker.py ================================================================== --- gluon/portalocker.py +++ gluon/portalocker.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# portalocker.py - Cross-platform (posix/nt) API for flock-style file locking. +# Requires python 1.5.2 or better. + +""" +Cross-platform (posix/nt) API for flock-style file locking. + +Synopsis: + + import portalocker + file = open(\"somefile\", \"r+\") + portalocker.lock(file, portalocker.LOCK_EX) + file.seek(12) + file.write(\"foo\") + file.close() + +If you know what you're doing, you may choose to + + portalocker.unlock(file) + +before closing the file, but why? + +Methods: + + lock( file, flags ) + unlock( file ) + +Constants: + + LOCK_EX + LOCK_SH + LOCK_NB + +I learned the win32 technique for locking files from sample code +provided by John Nielsen <nielsenjf@my-deja.com> in the documentation +that accompanies the win32 modules. + +Author: Jonathan Feinberg <jdf@pobox.com> +Version: $Id: portalocker.py,v 1.3 2001/05/29 18:47:55 Administrator Exp $ +""" + +import os +import logging +import platform +logger = logging.getLogger("web2py") + +os_locking = None +try: + import fcntl + os_locking = 'posix' +except: + pass +try: + import win32con + import win32file + import pywintypes + os_locking = 'windows' +except: + pass + +if os_locking == 'windows': + LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK + LOCK_SH = 0 # the default + LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY + + # is there any reason not to reuse the following structure? + + __overlapped = pywintypes.OVERLAPPED() + + def lock(file, flags): + hfile = win32file._get_osfhandle(file.fileno()) + win32file.LockFileEx(hfile, flags, 0, 0x7fff0000, __overlapped) + + def unlock(file): + hfile = win32file._get_osfhandle(file.fileno()) + win32file.UnlockFileEx(hfile, 0, 0x7fff0000, __overlapped) + + +elif os_locking == 'posix': + LOCK_EX = fcntl.LOCK_EX + LOCK_SH = fcntl.LOCK_SH + LOCK_NB = fcntl.LOCK_NB + + def lock(file, flags): + fcntl.flock(file.fileno(), flags) + + def unlock(file): + fcntl.flock(file.fileno(), fcntl.LOCK_UN) + + +else: + if platform.system() == 'Windows': + logger.error('no file locking, you must install the win32 extensions from: http://sourceforge.net/projects/pywin32/files/') + else: + logger.debug('no file locking, this will cause problems') + + LOCK_EX = None + LOCK_SH = None + LOCK_NB = None + + def lock(file, flags): + pass + + def unlock(file): + pass + + +if __name__ == '__main__': + from time import time, strftime, localtime + import sys + + log = open('log.txt', 'a+') + lock(log, LOCK_EX) + + timestamp = strftime('%m/%d/%Y %H:%M:%S\n', localtime(time())) + log.write(timestamp) + + print 'Wrote lines. Hit enter to release lock.' + dummy = sys.stdin.readline() + + log.close() + ADDED gluon/reserved_sql_keywords.py Index: gluon/reserved_sql_keywords.py ================================================================== --- gluon/reserved_sql_keywords.py +++ gluon/reserved_sql_keywords.py @@ -0,0 +1,1714 @@ +# encoding utf-8 + +__author__ = "Thadeus Burgess <thadeusb@thadeusb.com>" + +# we classify as "non-reserved" those key words that are explicitly known +# to the parser but are allowed as column or table names. Some key words +# that are otherwise non-reserved cannot be used as function or data type n +# ames and are in the nonreserved list. (Most of these words represent +# built-in functions or data types with special syntax. The function +# or type is still available but it cannot be redefined by the user.) +# Labeled "reserved" are those tokens that are not allowed as column or +# table names. Some reserved key words are allowable as names for +# functions or data typesself. + +# Note at the bottom of the list is a dict containing references to the +# tuples, and also if you add a list don't forget to remove its default +# set of COMMON. + +# Keywords that are adapter specific. Such as a list of "postgresql" +# or "mysql" keywords + +# These are keywords that are common to all SQL dialects, and should +# never be used as a table or column. Even if you use one of these +# the cursor will throw an OperationalError for the SQL syntax. +COMMON = set(( + 'SELECT', + 'INSERT', + 'DELETE', + 'UPDATE', + 'DROP', + 'CREATE', + 'ALTER', + + 'WHERE', + 'FROM', + 'INNER', + 'JOIN', + 'AND', + 'OR', + 'LIKE', + 'ON', + 'IN', + 'SET', + + 'BY', + 'GROUP', + 'ORDER', + 'LEFT', + 'OUTER', + + 'IF', + 'END', + 'THEN', + 'LOOP', + 'AS', + 'ELSE', + 'FOR', + + 'CASE', + 'WHEN', + 'MIN', + 'MAX', + 'DISTINCT', +)) + + +POSTGRESQL = set(( + 'FALSE', + 'TRUE', + 'ALL', + 'ANALYSE', + 'ANALYZE', + 'AND', + 'ANY', + 'ARRAY', + 'AS', + 'ASC', + 'ASYMMETRIC', + 'AUTHORIZATION', + 'BETWEEN', + 'BIGINT', + 'BINARY', + 'BIT', + 'BOOLEAN', + 'BOTH', + 'CASE', + 'CAST', + 'CHAR', + 'CHARACTER', + 'CHECK', + 'COALESCE', + 'COLLATE', + 'COLUMN', + 'CONSTRAINT', + 'CREATE', + 'CROSS', + 'CURRENT_CATALOG', + 'CURRENT_DATE', + 'CURRENT_ROLE', + 'CURRENT_SCHEMA', + 'CURRENT_TIME', + 'CURRENT_TIMESTAMP', + 'CURRENT_USER', + 'DEC', + 'DECIMAL', + 'DEFAULT', + 'DEFERRABLE', + 'DESC', + 'DISTINCT', + 'DO', + 'ELSE', + 'END', + 'EXCEPT', + 'EXISTS', + 'EXTRACT', + 'FETCH', + 'FLOAT', + 'FOR', + 'FOREIGN', + 'FREEZE', + 'FROM', + 'FULL', + 'GRANT', + 'GREATEST', + 'GROUP', + 'HAVING', + 'ILIKE', + 'IN', + 'INITIALLY', + 'INNER', + 'INOUT', + 'INT', + 'INTEGER', + 'INTERSECT', + 'INTERVAL', + 'INTO', + 'IS', + 'ISNULL', + 'JOIN', + 'LEADING', + 'LEAST', + 'LEFT', + 'LIKE', + 'LIMIT', + 'LOCALTIME', + 'LOCALTIMESTAMP', + 'NATIONAL', + 'NATURAL', + 'NCHAR', + 'NEW', + 'NONE', + 'NOT', + 'NOTNULL', + 'NULL', + 'NULLIF', + 'NUMERIC', + 'OFF', + 'OFFSET', + 'OLD', + 'ON', + 'ONLY', + 'OR', + 'ORDER', + 'OUT', + 'OUTER', + 'OVERLAPS', + 'OVERLAY', + 'PLACING', + 'POSITION', + 'PRECISION', + 'PRIMARY', + 'REAL', + 'REFERENCES', + 'RETURNING', + 'RIGHT', + 'ROW', + 'SELECT', + 'SESSION_USER', + 'SETOF', + 'SIMILAR', + 'SMALLINT', + 'SOME', + 'SUBSTRING', + 'SYMMETRIC', + 'TABLE', + 'THEN', + 'TIME', + 'TIMESTAMP', + 'TO', + 'TRAILING', + 'TREAT', + 'TRIM', + 'UNION', + 'UNIQUE', + 'USER', + 'USING', + 'VALUES', + 'VARCHAR', + 'VARIADIC', + 'VERBOSE', + 'WHEN', + 'WHERE', + 'WITH', + 'XMLATTRIBUTES', + 'XMLCONCAT', + 'XMLELEMENT', + 'XMLFOREST', + 'XMLPARSE', + 'XMLPI', + 'XMLROOT', + 'XMLSERIALIZE', +)) + + +POSTGRESQL_NONRESERVED = set(( + 'A', + 'ABORT', + 'ABS', + 'ABSENT', + 'ABSOLUTE', + 'ACCESS', + 'ACCORDING', + 'ACTION', + 'ADA', + 'ADD', + 'ADMIN', + 'AFTER', + 'AGGREGATE', + 'ALIAS', + 'ALLOCATE', + 'ALSO', + 'ALTER', + 'ALWAYS', + 'ARE', + 'ARRAY_AGG', + 'ASENSITIVE', + 'ASSERTION', + 'ASSIGNMENT', + 'AT', + 'ATOMIC', + 'ATTRIBUTE', + 'ATTRIBUTES', + 'AVG', + 'BACKWARD', + 'BASE64', + 'BEFORE', + 'BEGIN', + 'BERNOULLI', + 'BIT_LENGTH', + 'BITVAR', + 'BLOB', + 'BOM', + 'BREADTH', + 'BY', + 'C', + 'CACHE', + 'CALL', + 'CALLED', + 'CARDINALITY', + 'CASCADE', + 'CASCADED', + 'CATALOG', + 'CATALOG_NAME', + 'CEIL', + 'CEILING', + 'CHAIN', + 'CHAR_LENGTH', + 'CHARACTER_LENGTH', + 'CHARACTER_SET_CATALOG', + 'CHARACTER_SET_NAME', + 'CHARACTER_SET_SCHEMA', + 'CHARACTERISTICS', + 'CHARACTERS', + 'CHECKED', + 'CHECKPOINT', + 'CLASS', + 'CLASS_ORIGIN', + 'CLOB', + 'CLOSE', + 'CLUSTER', + 'COBOL', + 'COLLATION', + 'COLLATION_CATALOG', + 'COLLATION_NAME', + 'COLLATION_SCHEMA', + 'COLLECT', + 'COLUMN_NAME', + 'COLUMNS', + 'COMMAND_FUNCTION', + 'COMMAND_FUNCTION_CODE', + 'COMMENT', + 'COMMIT', + 'COMMITTED', + 'COMPLETION', + 'CONCURRENTLY', + 'CONDITION', + 'CONDITION_NUMBER', + 'CONFIGURATION', + 'CONNECT', + 'CONNECTION', + 'CONNECTION_NAME', + 'CONSTRAINT_CATALOG', + 'CONSTRAINT_NAME', + 'CONSTRAINT_SCHEMA', + 'CONSTRAINTS', + 'CONSTRUCTOR', + 'CONTAINS', + 'CONTENT', + 'CONTINUE', + 'CONVERSION', + 'CONVERT', + 'COPY', + 'CORR', + 'CORRESPONDING', + 'COST', + 'COUNT', + 'COVAR_POP', + 'COVAR_SAMP', + 'CREATEDB', + 'CREATEROLE', + 'CREATEUSER', + 'CSV', + 'CUBE', + 'CUME_DIST', + 'CURRENT', + 'CURRENT_DEFAULT_TRANSFORM_GROUP', + 'CURRENT_PATH', + 'CURRENT_TRANSFORM_GROUP_FOR_TYPE', + 'CURSOR', + 'CURSOR_NAME', + 'CYCLE', + 'DATA', + 'DATABASE', + 'DATE', + 'DATETIME_INTERVAL_CODE', + 'DATETIME_INTERVAL_PRECISION', + 'DAY', + 'DEALLOCATE', + 'DECLARE', + 'DEFAULTS', + 'DEFERRED', + 'DEFINED', + 'DEFINER', + 'DEGREE', + 'DELETE', + 'DELIMITER', + 'DELIMITERS', + 'DENSE_RANK', + 'DEPTH', + 'DEREF', + 'DERIVED', + 'DESCRIBE', + 'DESCRIPTOR', + 'DESTROY', + 'DESTRUCTOR', + 'DETERMINISTIC', + 'DIAGNOSTICS', + 'DICTIONARY', + 'DISABLE', + 'DISCARD', + 'DISCONNECT', + 'DISPATCH', + 'DOCUMENT', + 'DOMAIN', + 'DOUBLE', + 'DROP', + 'DYNAMIC', + 'DYNAMIC_FUNCTION', + 'DYNAMIC_FUNCTION_CODE', + 'EACH', + 'ELEMENT', + 'EMPTY', + 'ENABLE', + 'ENCODING', + 'ENCRYPTED', + 'END-EXEC', + 'ENUM', + 'EQUALS', + 'ESCAPE', + 'EVERY', + 'EXCEPTION', + 'EXCLUDE', + 'EXCLUDING', + 'EXCLUSIVE', + 'EXEC', + 'EXECUTE', + 'EXISTING', + 'EXP', + 'EXPLAIN', + 'EXTERNAL', + 'FAMILY', + 'FILTER', + 'FINAL', + 'FIRST', + 'FIRST_VALUE', + 'FLAG', + 'FLOOR', + 'FOLLOWING', + 'FORCE', + 'FORTRAN', + 'FORWARD', + 'FOUND', + 'FREE', + 'FUNCTION', + 'FUSION', + 'G', + 'GENERAL', + 'GENERATED', + 'GET', + 'GLOBAL', + 'GO', + 'GOTO', + 'GRANTED', + 'GROUPING', + 'HANDLER', + 'HEADER', + 'HEX', + 'HIERARCHY', + 'HOLD', + 'HOST', + 'HOUR', +# 'ID', + 'IDENTITY', + 'IF', + 'IGNORE', + 'IMMEDIATE', + 'IMMUTABLE', + 'IMPLEMENTATION', + 'IMPLICIT', + 'INCLUDING', + 'INCREMENT', + 'INDENT', + 'INDEX', + 'INDEXES', + 'INDICATOR', + 'INFIX', + 'INHERIT', + 'INHERITS', + 'INITIALIZE', + 'INPUT', + 'INSENSITIVE', + 'INSERT', + 'INSTANCE', + 'INSTANTIABLE', + 'INSTEAD', + 'INTERSECTION', + 'INVOKER', + 'ISOLATION', + 'ITERATE', + 'K', + 'KEY', + 'KEY_MEMBER', + 'KEY_TYPE', + 'LAG', + 'LANCOMPILER', + 'LANGUAGE', + 'LARGE', + 'LAST', + 'LAST_VALUE', + 'LATERAL', + 'LC_COLLATE', + 'LC_CTYPE', + 'LEAD', + 'LENGTH', + 'LESS', + 'LEVEL', + 'LIKE_REGEX', + 'LISTEN', + 'LN', + 'LOAD', + 'LOCAL', + 'LOCATION', + 'LOCATOR', + 'LOCK', + 'LOGIN', + 'LOWER', + 'M', + 'MAP', + 'MAPPING', + 'MATCH', + 'MATCHED', + 'MAX', + 'MAX_CARDINALITY', + 'MAXVALUE', + 'MEMBER', + 'MERGE', + 'MESSAGE_LENGTH', + 'MESSAGE_OCTET_LENGTH', + 'MESSAGE_TEXT', + 'METHOD', + 'MIN', + 'MINUTE', + 'MINVALUE', + 'MOD', + 'MODE', + 'MODIFIES', + 'MODIFY', + 'MODULE', + 'MONTH', + 'MORE', + 'MOVE', + 'MULTISET', + 'MUMPS', +# 'NAME', + 'NAMES', + 'NAMESPACE', + 'NCLOB', + 'NESTING', + 'NEXT', + 'NFC', + 'NFD', + 'NFKC', + 'NFKD', + 'NIL', + 'NO', + 'NOCREATEDB', + 'NOCREATEROLE', + 'NOCREATEUSER', + 'NOINHERIT', + 'NOLOGIN', + 'NORMALIZE', + 'NORMALIZED', + 'NOSUPERUSER', + 'NOTHING', + 'NOTIFY', + 'NOWAIT', + 'NTH_VALUE', + 'NTILE', + 'NULLABLE', + 'NULLS', + 'NUMBER', + 'OBJECT', + 'OCCURRENCES_REGEX', + 'OCTET_LENGTH', + 'OCTETS', + 'OF', + 'OIDS', + 'OPEN', + 'OPERATION', + 'OPERATOR', + 'OPTION', + 'OPTIONS', + 'ORDERING', + 'ORDINALITY', + 'OTHERS', + 'OUTPUT', + 'OVER', + 'OVERRIDING', + 'OWNED', + 'OWNER', + 'P', + 'PAD', + 'PARAMETER', + 'PARAMETER_MODE', + 'PARAMETER_NAME', + 'PARAMETER_ORDINAL_POSITION', + 'PARAMETER_SPECIFIC_CATALOG', + 'PARAMETER_SPECIFIC_NAME', + 'PARAMETER_SPECIFIC_SCHEMA', + 'PARAMETERS', + 'PARSER', + 'PARTIAL', + 'PARTITION', + 'PASCAL', + 'PASSING', +# 'PASSWORD', + 'PATH', + 'PERCENT_RANK', + 'PERCENTILE_CONT', + 'PERCENTILE_DISC', + 'PLANS', + 'PLI', + 'POSITION_REGEX', + 'POSTFIX', + 'POWER', + 'PRECEDING', + 'PREFIX', + 'PREORDER', + 'PREPARE', + 'PREPARED', + 'PRESERVE', + 'PRIOR', + 'PRIVILEGES', + 'PROCEDURAL', + 'PROCEDURE', + 'PUBLIC', + 'QUOTE', + 'RANGE', + 'RANK', + 'READ', + 'READS', + 'REASSIGN', + 'RECHECK', + 'RECURSIVE', + 'REF', + 'REFERENCING', + 'REGR_AVGX', + 'REGR_AVGY', + 'REGR_COUNT', + 'REGR_INTERCEPT', + 'REGR_R2', + 'REGR_SLOPE', + 'REGR_SXX', + 'REGR_SXY', + 'REGR_SYY', + 'REINDEX', + 'RELATIVE', + 'RELEASE', + 'RENAME', + 'REPEATABLE', + 'REPLACE', + 'REPLICA', + 'RESET', + 'RESPECT', + 'RESTART', + 'RESTRICT', + 'RESULT', + 'RETURN', + 'RETURNED_CARDINALITY', + 'RETURNED_LENGTH', + 'RETURNED_OCTET_LENGTH', + 'RETURNED_SQLSTATE', + 'RETURNS', + 'REVOKE', +# 'ROLE', + 'ROLLBACK', + 'ROLLUP', + 'ROUTINE', + 'ROUTINE_CATALOG', + 'ROUTINE_NAME', + 'ROUTINE_SCHEMA', + 'ROW_COUNT', + 'ROW_NUMBER', + 'ROWS', + 'RULE', + 'SAVEPOINT', + 'SCALE', + 'SCHEMA', + 'SCHEMA_NAME', + 'SCOPE', + 'SCOPE_CATALOG', + 'SCOPE_NAME', + 'SCOPE_SCHEMA', + 'SCROLL', + 'SEARCH', + 'SECOND', + 'SECTION', + 'SECURITY', + 'SELF', + 'SENSITIVE', + 'SEQUENCE', + 'SERIALIZABLE', + 'SERVER', + 'SERVER_NAME', + 'SESSION', + 'SET', + 'SETS', + 'SHARE', + 'SHOW', + 'SIMPLE', + 'SIZE', + 'SOURCE', + 'SPACE', + 'SPECIFIC', + 'SPECIFIC_NAME', + 'SPECIFICTYPE', + 'SQL', + 'SQLCODE', + 'SQLERROR', + 'SQLEXCEPTION', + 'SQLSTATE', + 'SQLWARNING', + 'SQRT', + 'STABLE', + 'STANDALONE', + 'START', + 'STATE', + 'STATEMENT', + 'STATIC', + 'STATISTICS', + 'STDDEV_POP', + 'STDDEV_SAMP', + 'STDIN', + 'STDOUT', + 'STORAGE', + 'STRICT', + 'STRIP', + 'STRUCTURE', + 'STYLE', + 'SUBCLASS_ORIGIN', + 'SUBLIST', + 'SUBMULTISET', + 'SUBSTRING_REGEX', + 'SUM', + 'SUPERUSER', + 'SYSID', + 'SYSTEM', + 'SYSTEM_USER', + 'T', +# 'TABLE_NAME', + 'TABLESAMPLE', + 'TABLESPACE', + 'TEMP', + 'TEMPLATE', + 'TEMPORARY', + 'TERMINATE', + 'TEXT', + 'THAN', + 'TIES', + 'TIMEZONE_HOUR', + 'TIMEZONE_MINUTE', + 'TOP_LEVEL_COUNT', + 'TRANSACTION', + 'TRANSACTION_ACTIVE', + 'TRANSACTIONS_COMMITTED', + 'TRANSACTIONS_ROLLED_BACK', + 'TRANSFORM', + 'TRANSFORMS', + 'TRANSLATE', + 'TRANSLATE_REGEX', + 'TRANSLATION', + 'TRIGGER', + 'TRIGGER_CATALOG', + 'TRIGGER_NAME', + 'TRIGGER_SCHEMA', + 'TRIM_ARRAY', + 'TRUNCATE', + 'TRUSTED', + 'TYPE', + 'UESCAPE', + 'UNBOUNDED', + 'UNCOMMITTED', + 'UNDER', + 'UNENCRYPTED', + 'UNKNOWN', + 'UNLISTEN', + 'UNNAMED', + 'UNNEST', + 'UNTIL', + 'UNTYPED', + 'UPDATE', + 'UPPER', + 'URI', + 'USAGE', + 'USER_DEFINED_TYPE_CATALOG', + 'USER_DEFINED_TYPE_CODE', + 'USER_DEFINED_TYPE_NAME', + 'USER_DEFINED_TYPE_SCHEMA', + 'VACUUM', + 'VALID', + 'VALIDATOR', + 'VALUE', + 'VAR_POP', + 'VAR_SAMP', + 'VARBINARY', + 'VARIABLE', + 'VARYING', + 'VERSION', + 'VIEW', + 'VOLATILE', + 'WHENEVER', + 'WHITESPACE', + 'WIDTH_BUCKET', + 'WINDOW', + 'WITHIN', + 'WITHOUT', + 'WORK', + 'WRAPPER', + 'WRITE', + 'XML', + 'XMLAGG', + 'XMLBINARY', + 'XMLCAST', + 'XMLCOMMENT', + 'XMLDECLARATION', + 'XMLDOCUMENT', + 'XMLEXISTS', + 'XMLITERATE', + 'XMLNAMESPACES', + 'XMLQUERY', + 'XMLSCHEMA', + 'XMLTABLE', + 'XMLTEXT', + 'XMLVALIDATE', + 'YEAR', + 'YES', + 'ZONE', +)) + +#Thanks villas +FIREBIRD = set(( + 'ABS', + 'ACTIVE', + 'ADMIN', + 'AFTER', + 'ASCENDING', + 'AUTO', + 'AUTODDL', + 'BASED', + 'BASENAME', + 'BASE_NAME', + 'BEFORE', + 'BIT_LENGTH', + 'BLOB', + 'BLOBEDIT', + 'BOOLEAN', + 'BOTH', + 'BUFFER', + 'CACHE', + 'CHAR_LENGTH', + 'CHARACTER_LENGTH', + 'CHECK_POINT_LEN', + 'CHECK_POINT_LENGTH', + 'CLOSE', + 'COMMITTED', + 'COMPILETIME', + 'COMPUTED', + 'CONDITIONAL', + 'CONNECT', + 'CONTAINING', + 'CROSS', + 'CSTRING', + 'CURRENT_CONNECTION', + 'CURRENT_ROLE', + 'CURRENT_TRANSACTION', + 'CURRENT_USER', + 'DATABASE', + 'DB_KEY', + 'DEBUG', + 'DESCENDING', + 'DISCONNECT', + 'DISPLAY', + 'DO', + 'ECHO', + 'EDIT', + 'ENTRY_POINT', + 'EVENT', + 'EXIT', + 'EXTERN', + 'FALSE', + 'FETCH', + 'FILE', + 'FILTER', + 'FREE_IT', + 'FUNCTION', + 'GDSCODE', + 'GENERATOR', + 'GEN_ID', + 'GLOBAL', + 'GROUP_COMMIT_WAIT', + 'GROUP_COMMIT_WAIT_TIME', + 'HELP', + 'IF', + 'INACTIVE', + 'INDEX', + 'INIT', + 'INPUT_TYPE', + 'INSENSITIVE', + 'ISQL', + 'LC_MESSAGES', + 'LC_TYPE', + 'LEADING', + 'LENGTH', + 'LEV', + 'LOGFILE', + 'LOG_BUFFER_SIZE', + 'LOG_BUF_SIZE', + 'LONG', + 'LOWER', + 'MANUAL', + 'MAXIMUM', + 'MAXIMUM_SEGMENT', + 'MAX_SEGMENT', + 'MERGE', + 'MESSAGE', + 'MINIMUM', + 'MODULE_NAME', + 'NOAUTO', + 'NUM_LOG_BUFS', + 'NUM_LOG_BUFFERS', + 'OCTET_LENGTH', + 'OPEN', + 'OUTPUT_TYPE', + 'OVERFLOW', + 'PAGE', + 'PAGELENGTH', + 'PAGES', + 'PAGE_SIZE', + 'PARAMETER', +# 'PASSWORD', + 'PLAN', + 'POST_EVENT', + 'QUIT', + 'RAW_PARTITIONS', + 'RDB$DB_KEY', + 'RECORD_VERSION', + 'RECREATE', + 'RECURSIVE', + 'RELEASE', + 'RESERV', + 'RESERVING', + 'RETAIN', + 'RETURN', + 'RETURNING_VALUES', + 'RETURNS', +# 'ROLE', + 'ROW_COUNT', + 'ROWS', + 'RUNTIME', + 'SAVEPOINT', + 'SECOND', + 'SENSITIVE', + 'SHADOW', + 'SHARED', + 'SHELL', + 'SHOW', + 'SINGULAR', + 'SNAPSHOT', + 'SORT', + 'STABILITY', + 'START', + 'STARTING', + 'STARTS', + 'STATEMENT', + 'STATIC', + 'STATISTICS', + 'SUB_TYPE', + 'SUSPEND', + 'TERMINATOR', + 'TRAILING', + 'TRIGGER', + 'TRIM', + 'TRUE', + 'TYPE', + 'UNCOMMITTED', + 'UNKNOWN', + 'USING', + 'VARIABLE', + 'VERSION', + 'WAIT', + 'WEEKDAY', + 'WHILE', + 'YEARDAY', +)) +FIREBIRD_NONRESERVED = set(( + 'BACKUP', + 'BLOCK', + 'COALESCE', + 'COLLATION', + 'COMMENT', + 'DELETING', + 'DIFFERENCE', + 'IIF', + 'INSERTING', + 'LAST', + 'LEAVE', + 'LOCK', + 'NEXT', + 'NULLIF', + 'NULLS', + 'RESTART', + 'RETURNING', + 'SCALAR_ARRAY', + 'SEQUENCE', + 'STATEMENT', + 'UPDATING', + 'ABS', + 'ACCENT', + 'ACOS', + 'ALWAYS', + 'ASCII_CHAR', + 'ASCII_VAL', + 'ASIN', + 'ATAN', + 'ATAN2', + 'BACKUP', + 'BIN_AND', + 'BIN_OR', + 'BIN_SHL', + 'BIN_SHR', + 'BIN_XOR', + 'BLOCK', + 'CEIL', + 'CEILING', + 'COLLATION', + 'COMMENT', + 'COS', + 'COSH', + 'COT', + 'DATEADD', + 'DATEDIFF', + 'DECODE', + 'DIFFERENCE', + 'EXP', + 'FLOOR', + 'GEN_UUID', + 'GENERATED', + 'HASH', + 'IIF', + 'LIST', + 'LN', + 'LOG', + 'LOG10', + 'LPAD', + 'MATCHED', + 'MATCHING', + 'MAXVALUE', + 'MILLISECOND', + 'MINVALUE', + 'MOD', + 'NEXT', + 'OVERLAY', + 'PAD', + 'PI', + 'PLACING', + 'POWER', + 'PRESERVE', + 'RAND', + 'REPLACE', + 'RESTART', + 'RETURNING', + 'REVERSE', + 'ROUND', + 'RPAD', + 'SCALAR_ARRAY', + 'SEQUENCE', + 'SIGN', + 'SIN', + 'SINH', + 'SPACE', + 'SQRT', + 'TAN', + 'TANH', + 'TEMPORARY', + 'TRUNC', + 'WEEK', +)) + +# Thanks Jonathan Lundell +MYSQL = set(( + 'ACCESSIBLE', + 'ADD', + 'ALL', + 'ALTER', + 'ANALYZE', + 'AND', + 'AS', + 'ASC', + 'ASENSITIVE', + 'BEFORE', + 'BETWEEN', + 'BIGINT', + 'BINARY', + 'BLOB', + 'BOTH', + 'BY', + 'CALL', + 'CASCADE', + 'CASE', + 'CHANGE', + 'CHAR', + 'CHARACTER', + 'CHECK', + 'COLLATE', + 'COLUMN', + 'CONDITION', + 'CONSTRAINT', + 'CONTINUE', + 'CONVERT', + 'CREATE', + 'CROSS', + 'CURRENT_DATE', + 'CURRENT_TIME', + 'CURRENT_TIMESTAMP', + 'CURRENT_USER', + 'CURSOR', + 'DATABASE', + 'DATABASES', + 'DAY_HOUR', + 'DAY_MICROSECOND', + 'DAY_MINUTE', + 'DAY_SECOND', + 'DEC', + 'DECIMAL', + 'DECLARE', + 'DEFAULT', + 'DELAYED', + 'DELETE', + 'DESC', + 'DESCRIBE', + 'DETERMINISTIC', + 'DISTINCT', + 'DISTINCTROW', + 'DIV', + 'DOUBLE', + 'DROP', + 'DUAL', + 'EACH', + 'ELSE', + 'ELSEIF', + 'ENCLOSED', + 'ESCAPED', + 'EXISTS', + 'EXIT', + 'EXPLAIN', + 'FALSE', + 'FETCH', + 'FLOAT', + 'FLOAT4', + 'FLOAT8', + 'FOR', + 'FORCE', + 'FOREIGN', + 'FROM', + 'FULLTEXT', + 'GRANT', + 'GROUP', + 'HAVING', + 'HIGH_PRIORITY', + 'HOUR_MICROSECOND', + 'HOUR_MINUTE', + 'HOUR_SECOND', + 'IF', + 'IGNORE', + 'IGNORE_SERVER_IDS', + 'IGNORE_SERVER_IDS', + 'IN', + 'INDEX', + 'INFILE', + 'INNER', + 'INOUT', + 'INSENSITIVE', + 'INSERT', + 'INT', + 'INT1', + 'INT2', + 'INT3', + 'INT4', + 'INT8', + 'INTEGER', + 'INTERVAL', + 'INTO', + 'IS', + 'ITERATE', + 'JOIN', + 'KEY', + 'KEYS', + 'KILL', + 'LEADING', + 'LEAVE', + 'LEFT', + 'LIKE', + 'LIMIT', + 'LINEAR', + 'LINES', + 'LOAD', + 'LOCALTIME', + 'LOCALTIMESTAMP', + 'LOCK', + 'LONG', + 'LONGBLOB', + 'LONGTEXT', + 'LOOP', + 'LOW_PRIORITY', + 'MASTER_HEARTBEAT_PERIOD', + 'MASTER_HEARTBEAT_PERIOD', + 'MASTER_SSL_VERIFY_SERVER_CERT', + 'MATCH', + 'MAXVALUE', + 'MAXVALUE', + 'MEDIUMBLOB', + 'MEDIUMINT', + 'MEDIUMTEXT', + 'MIDDLEINT', + 'MINUTE_MICROSECOND', + 'MINUTE_SECOND', + 'MOD', + 'MODIFIES', + 'NATURAL', + 'NO_WRITE_TO_BINLOG', + 'NOT', + 'NULL', + 'NUMERIC', + 'ON', + 'OPTIMIZE', + 'OPTION', + 'OPTIONALLY', + 'OR', + 'ORDER', + 'OUT', + 'OUTER', + 'OUTFILE', + 'PRECISION', + 'PRIMARY', + 'PROCEDURE', + 'PURGE', + 'RANGE', + 'READ', + 'READ_WRITE', + 'READS', + 'REAL', + 'REFERENCES', + 'REGEXP', + 'RELEASE', + 'RENAME', + 'REPEAT', + 'REPLACE', + 'REQUIRE', + 'RESIGNAL', + 'RESIGNAL', + 'RESTRICT', + 'RETURN', + 'REVOKE', + 'RIGHT', + 'RLIKE', + 'SCHEMA', + 'SCHEMAS', + 'SECOND_MICROSECOND', + 'SELECT', + 'SENSITIVE', + 'SEPARATOR', + 'SET', + 'SHOW', + 'SIGNAL', + 'SIGNAL', + 'SMALLINT', + 'SPATIAL', + 'SPECIFIC', + 'SQL', + 'SQL_BIG_RESULT', + 'SQL_CALC_FOUND_ROWS', + 'SQL_SMALL_RESULT', + 'SQLEXCEPTION', + 'SQLSTATE', + 'SQLWARNING', + 'SSL', + 'STARTING', + 'STRAIGHT_JOIN', + 'TABLE', + 'TERMINATED', + 'THEN', + 'TINYBLOB', + 'TINYINT', + 'TINYTEXT', + 'TO', + 'TRAILING', + 'TRIGGER', + 'TRUE', + 'UNDO', + 'UNION', + 'UNIQUE', + 'UNLOCK', + 'UNSIGNED', + 'UPDATE', + 'USAGE', + 'USE', + 'USING', + 'UTC_DATE', + 'UTC_TIME', + 'UTC_TIMESTAMP', + 'VALUES', + 'VARBINARY', + 'VARCHAR', + 'VARCHARACTER', + 'VARYING', + 'WHEN', + 'WHERE', + 'WHILE', + 'WITH', + 'WRITE', + 'XOR', + 'YEAR_MONTH', + 'ZEROFILL', +)) + +MSSQL = set(( + 'ADD', + 'ALL', + 'ALTER', + 'AND', + 'ANY', + 'AS', + 'ASC', + 'AUTHORIZATION', + 'BACKUP', + 'BEGIN', + 'BETWEEN', + 'BREAK', + 'BROWSE', + 'BULK', + 'BY', + 'CASCADE', + 'CASE', + 'CHECK', + 'CHECKPOINT', + 'CLOSE', + 'CLUSTERED', + 'COALESCE', + 'COLLATE', + 'COLUMN', + 'COMMIT', + 'COMPUTE', + 'CONSTRAINT', + 'CONTAINS', + 'CONTAINSTABLE', + 'CONTINUE', + 'CONVERT', + 'CREATE', + 'CROSS', + 'CURRENT', + 'CURRENT_DATE', + 'CURRENT_TIME', + 'CURRENT_TIMESTAMP', + 'CURRENT_USER', + 'CURSOR', + 'DATABASE', + 'DBCC', + 'DEALLOCATE', + 'DECLARE', + 'DEFAULT', + 'DELETE', + 'DENY', + 'DESC', + 'DISK', + 'DISTINCT', + 'DISTRIBUTED', + 'DOUBLE', + 'DROP', + 'DUMMY', + 'DUMP', + 'ELSE', + 'END', + 'ERRLVL', + 'ESCAPE', + 'EXCEPT', + 'EXEC', + 'EXECUTE', + 'EXISTS', + 'EXIT', + 'FETCH', + 'FILE', + 'FILLFACTOR', + 'FOR', + 'FOREIGN', + 'FREETEXT', + 'FREETEXTTABLE', + 'FROM', + 'FULL', + 'FUNCTION', + 'GOTO', + 'GRANT', + 'GROUP', + 'HAVING', + 'HOLDLOCK', + 'IDENTITY', + 'IDENTITY_INSERT', + 'IDENTITYCOL', + 'IF', + 'IN', + 'INDEX', + 'INNER', + 'INSERT', + 'INTERSECT', + 'INTO', + 'IS', + 'JOIN', + 'KEY', + 'KILL', + 'LEFT', + 'LIKE', + 'LINENO', + 'LOAD', + 'NATIONAL ', + 'NOCHECK', + 'NONCLUSTERED', + 'NOT', + 'NULL', + 'NULLIF', + 'OF', + 'OFF', + 'OFFSETS', + 'ON', + 'OPEN', + 'OPENDATASOURCE', + 'OPENQUERY', + 'OPENROWSET', + 'OPENXML', + 'OPTION', + 'OR', + 'ORDER', + 'OUTER', + 'OVER', + 'PERCENT', + 'PLAN', + 'PRECISION', + 'PRIMARY', + 'PRINT', + 'PROC', + 'PROCEDURE', + 'PUBLIC', + 'RAISERROR', + 'READ', + 'READTEXT', + 'RECONFIGURE', + 'REFERENCES', + 'REPLICATION', + 'RESTORE', + 'RESTRICT', + 'RETURN', + 'REVOKE', + 'RIGHT', + 'ROLLBACK', + 'ROWCOUNT', + 'ROWGUIDCOL', + 'RULE', + 'SAVE', + 'SCHEMA', + 'SELECT', + 'SESSION_USER', + 'SET', + 'SETUSER', + 'SHUTDOWN', + 'SOME', + 'STATISTICS', + 'SYSTEM_USER', + 'TABLE', + 'TEXTSIZE', + 'THEN', + 'TO', + 'TOP', + 'TRAN', + 'TRANSACTION', + 'TRIGGER', + 'TRUNCATE', + 'TSEQUAL', + 'UNION', + 'UNIQUE', + 'UPDATE', + 'UPDATETEXT', + 'USE', + 'USER', + 'VALUES', + 'VARYING', + 'VIEW', + 'WAITFOR', + 'WHEN', + 'WHERE', + 'WHILE', + 'WITH', + 'WRITETEXT', +)) + +ORACLE = set(( + 'ACCESS', + 'ADD', + 'ALL', + 'ALTER', + 'AND', + 'ANY', + 'AS', + 'ASC', + 'AUDIT', + 'BETWEEN', + 'BY', + 'CHAR', + 'CHECK', + 'CLUSTER', + 'COLUMN', + 'COMMENT', + 'COMPRESS', + 'CONNECT', + 'CREATE', + 'CURRENT', + 'DATE', + 'DECIMAL', + 'DEFAULT', + 'DELETE', + 'DESC', + 'DISTINCT', + 'DROP', + 'ELSE', + 'EXCLUSIVE', + 'EXISTS', + 'FILE', + 'FLOAT', + 'FOR', + 'FROM', + 'GRANT', + 'GROUP', + 'HAVING', + 'IDENTIFIED', + 'IMMEDIATE', + 'IN', + 'INCREMENT', + 'INDEX', + 'INITIAL', + 'INSERT', + 'INTEGER', + 'INTERSECT', + 'INTO', + 'IS', + 'LEVEL', + 'LIKE', + 'LOCK', + 'LONG', + 'MAXEXTENTS', + 'MINUS', + 'MLSLABEL', + 'MODE', + 'MODIFY', + 'NOAUDIT', + 'NOCOMPRESS', + 'NOT', + 'NOWAIT', + 'NULL', + 'NUMBER', + 'OF', + 'OFFLINE', + 'ON', + 'ONLINE', + 'OPTION', + 'OR', + 'ORDER', + 'PCTFREE', + 'PRIOR', + 'PRIVILEGES', + 'PUBLIC', + 'RAW', + 'RENAME', + 'RESOURCE', + 'REVOKE', + 'ROW', + 'ROWID', + 'ROWNUM', + 'ROWS', + 'SELECT', + 'SESSION', + 'SET', + 'SHARE', + 'SIZE', + 'SMALLINT', + 'START', + 'SUCCESSFUL', + 'SYNONYM', + 'SYSDATE', + 'TABLE', + 'THEN', + 'TO', + 'TRIGGER', + 'UID', + 'UNION', + 'UNIQUE', + 'UPDATE', + 'USER', + 'VALIDATE', + 'VALUES', + 'VARCHAR', + 'VARCHAR2', + 'VIEW', + 'WHENEVER', + 'WHERE', + 'WITH', +)) + +SQLITE = set(( + 'ABORT', + 'ACTION', + 'ADD', + 'AFTER', + 'ALL', + 'ALTER', + 'ANALYZE', + 'AND', + 'AS', + 'ASC', + 'ATTACH', + 'AUTOINCREMENT', + 'BEFORE', + 'BEGIN', + 'BETWEEN', + 'BY', + 'CASCADE', + 'CASE', + 'CAST', + 'CHECK', + 'COLLATE', + 'COLUMN', + 'COMMIT', + 'CONFLICT', + 'CONSTRAINT', + 'CREATE', + 'CROSS', + 'CURRENT_DATE', + 'CURRENT_TIME', + 'CURRENT_TIMESTAMP', + 'DATABASE', + 'DEFAULT', + 'DEFERRABLE', + 'DEFERRED', + 'DELETE', + 'DESC', + 'DETACH', + 'DISTINCT', + 'DROP', + 'EACH', + 'ELSE', + 'END', + 'ESCAPE', + 'EXCEPT', + 'EXCLUSIVE', + 'EXISTS', + 'EXPLAIN', + 'FAIL', + 'FOR', + 'FOREIGN', + 'FROM', + 'FULL', + 'GLOB', + 'GROUP', + 'HAVING', + 'IF', + 'IGNORE', + 'IMMEDIATE', + 'IN', + 'INDEX', + 'INDEXED', + 'INITIALLY', + 'INNER', + 'INSERT', + 'INSTEAD', + 'INTERSECT', + 'INTO', + 'IS', + 'ISNULL', + 'JOIN', + 'KEY', + 'LEFT', + 'LIKE', + 'LIMIT', + 'MATCH', + 'NATURAL', + 'NO', + 'NOT', + 'NOTNULL', + 'NULL', + 'OF', + 'OFFSET', + 'ON', + 'OR', + 'ORDER', + 'OUTER', + 'PLAN', + 'PRAGMA', + 'PRIMARY', + 'QUERY', + 'RAISE', + 'REFERENCES', + 'REGEXP', + 'REINDEX', + 'RELEASE', + 'RENAME', + 'REPLACE', + 'RESTRICT', + 'RIGHT', + 'ROLLBACK', + 'ROW', + 'SAVEPOINT', + 'SELECT', + 'SET', + 'TABLE', + 'TEMP', + 'TEMPORARY', + 'THEN', + 'TO', + 'TRANSACTION', + 'TRIGGER', + 'UNION', + 'UNIQUE', + 'UPDATE', + 'USING', + 'VACUUM', + 'VALUES', + 'VIEW', + 'VIRTUAL', + 'WHEN', + 'WHERE', +)) + +# remove from here when you add a list. +JDBCSQLITE = SQLITE +DB2 = INFORMIX = INGRES = JDBCPOSTGRESQL = COMMON + +ADAPTERS = { + 'sqlite': SQLITE, + 'mysql': MYSQL, + 'postgres': POSTGRESQL, + 'postgres_nonreserved': POSTGRESQL_NONRESERVED, + 'oracle': ORACLE, + 'mssql': MSSQL, + 'mssql2': MSSQL, + 'db2': DB2, + 'informix': INFORMIX, + 'firebird': FIREBIRD, + 'firebird_embedded': FIREBIRD, + 'firebird_nonreserved': FIREBIRD_NONRESERVED, + 'ingres': INGRES, + 'ingresu': INGRES, + 'jdbc:sqlite': JDBCSQLITE, + 'jdbc:postgres': JDBCPOSTGRESQL, + 'common': COMMON, +} + +ADAPTERS['all'] = reduce(lambda a,b:a.union(b),(x for x in ADAPTERS.values())) + ADDED gluon/restricted.py Index: gluon/restricted.py ================================================================== --- gluon/restricted.py +++ gluon/restricted.py @@ -0,0 +1,286 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +This file is part of the web2py Web Framework +Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> +License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) +""" + +import sys +import cPickle +import traceback +import types +import os +import datetime +import logging + +from utils import web2py_uuid +from storage import Storage +from http import HTTP +from html import BEAUTIFY + +logger = logging.getLogger("web2py") + +__all__ = ['RestrictedError', 'restricted', 'TicketStorage', 'compile2'] + +class TicketStorage(Storage): + + """ + defines the ticket object and the default values of its members (None) + """ + + def __init__( + self, + db=None, + tablename='web2py_ticket' + ): + self.db = db + self.tablename = tablename + + def store(self, request, ticket_id, ticket_data): + """ + stores the ticket. It will figure out if this must be on disk or in db + """ + if self.db: + self._store_in_db(request, ticket_id, ticket_data) + else: + self._store_on_disk(request, ticket_id, ticket_data) + + def _store_in_db(self, request, ticket_id, ticket_data): + table = self._get_table(self.db, self.tablename, request.application) + table.insert(ticket_id=ticket_id, + ticket_data=cPickle.dumps(ticket_data), + created_datetime=request.now) + logger.error('In FILE: %(layer)s\n\n%(traceback)s\n' % ticket_data) + + def _store_on_disk(self, request, ticket_id, ticket_data): + ef = self._error_file(request, ticket_id, 'wb') + try: + cPickle.dump(ticket_data, ef) + finally: + ef.close() + + def _error_file(self, request, ticket_id, mode, app=None): + root = request.folder + if app: + root = os.path.join(os.path.join(root, '..'), app) + errors_folder = os.path.abspath(os.path.join(root, 'errors'))#.replace('\\', '/') + return open(os.path.join(errors_folder, ticket_id), mode) + + def _get_table(self, db, tablename, app): + tablename = tablename + '_' + app + table = db.get(tablename, None) + if table is None: + db.rollback() # not necessary but one day + # any app may store tickets on DB + table = db.define_table( + tablename, + db.Field('ticket_id', length=100), + db.Field('ticket_data', 'text'), + db.Field('created_datetime', 'datetime'), + ) + return table + + def load( + self, + request, + app, + ticket_id, + ): + if not self.db: + ef = self._error_file(request, ticket_id, 'rb', app) + try: + return cPickle.load(ef) + finally: + ef.close() + table = self._get_table(self.db, self.tablename, app) + rows = self.db(table.ticket_id == ticket_id).select() + if rows: + return cPickle.loads(rows[0].ticket_data) + return None + + +class RestrictedError(Exception): + """ + class used to wrap an exception that occurs in the restricted environment + below. the traceback is used to log the exception and generate a ticket. + """ + + def __init__( + self, + layer='', + code='', + output='', + environment={}, + ): + """ + layer here is some description of where in the system the exception + occurred. + """ + + self.layer = layer + self.code = code + self.output = output + if layer: + try: + self.traceback = traceback.format_exc() + except: + self.traceback = 'no traceback because template parting error' + try: + self.snapshot = snapshot(context=10,code=code,environment=environment) + except: + self.snapshot = {} + else: + self.traceback = '(no error)' + self.snapshot = {} + self.environment = environment + + def log(self, request): + """ + logs the exception. + """ + + try: + d = { + 'layer': str(self.layer), + 'code': str(self.code), + 'output': str(self.output), + 'traceback': str(self.traceback), + 'snapshot': self.snapshot, + } + ticket_storage = TicketStorage(db=request.tickets_db) + ticket_storage.store(request, request.uuid.split('/',1)[1], d) + return request.uuid + except: + logger.error(self.traceback) + return None + + + def load(self, request, app, ticket_id): + """ + loads a logged exception. + """ + ticket_storage = TicketStorage(db=request.tickets_db) + d = ticket_storage.load(request, app, ticket_id) + + self.layer = d['layer'] + self.code = d['code'] + self.output = d['output'] + self.traceback = d['traceback'] + self.snapshot = d.get('snapshot') + + +def compile2(code,layer): + """ + The +'\n' is necessary else compile fails when code ends in a comment. + """ + return compile(code.rstrip().replace('\r\n','\n')+'\n', layer, 'exec') + +def restricted(code, environment={}, layer='Unknown'): + """ + runs code in environment and returns the output. if an exception occurs + in code it raises a RestrictedError containing the traceback. layer is + passed to RestrictedError to identify where the error occurred. + """ + environment['__file__'] = layer + try: + if type(code) == types.CodeType: + ccode = code + else: + ccode = compile2(code,layer) + exec ccode in environment + except HTTP: + raise + except Exception, error: + # XXX Show exception in Wing IDE if running in debugger + if __debug__ and 'WINGDB_ACTIVE' in os.environ: + etype, evalue, tb = sys.exc_info() + sys.excepthook(etype, evalue, tb) + raise RestrictedError(layer, code, '', environment) + +def snapshot(info=None, context=5, code=None, environment=None): + """Return a dict describing a given traceback (based on cgitb.text).""" + import os, types, time, traceback, linecache, inspect, pydoc, cgitb + + # if no exception info given, get current: + etype, evalue, etb = info or sys.exc_info() + + if type(etype) is types.ClassType: + etype = etype.__name__ + + # create a snapshot dict with some basic information + s = {} + s['pyver'] = 'Python ' + sys.version.split()[0] + ': ' + sys.executable + s['date'] = time.ctime(time.time()) + + # start to process frames + records = inspect.getinnerframes(etb, context) + s['frames'] = [] + for frame, file, lnum, func, lines, index in records: + file = file and os.path.abspath(file) or '?' + args, varargs, varkw, locals = inspect.getargvalues(frame) + call = '' + if func != '?': + call = inspect.formatargvalues(args, varargs, varkw, locals, + formatvalue=lambda value: '=' + pydoc.text.repr(value)) + + # basic frame information + f = {'file': file, 'func': func, 'call': call, 'lines': {}, 'lnum': lnum} + + highlight = {} + def reader(lnum=[lnum]): + highlight[lnum[0]] = 1 + try: return linecache.getline(file, lnum[0]) + finally: lnum[0] += 1 + vars = cgitb.scanvars(reader, frame, locals) + + # if it is a view, replace with generated code + if file.endswith('html'): + lmin = lnum>context and (lnum-context) or 0 + lmax = lnum+context + lines = code.split("\n")[lmin:lmax] + index = min(context, lnum) - 1 + + if index is not None: + i = lnum - index + for line in lines: + f['lines'][i] = line.rstrip() + i += 1 + + # dump local variables (referenced in current line only) + f['dump'] = {} + for name, where, value in vars: + if name in f['dump']: continue + if value is not cgitb.__UNDEF__: + if where == 'global': name = 'global ' + name + elif where != 'local': name = where + name.split('.')[-1] + f['dump'][name] = pydoc.text.repr(value) + else: + f['dump'][name] = 'undefined' + + s['frames'].append(f) + + # add exception type, value and attributes + s['etype'] = str(etype) + s['evalue'] = str(evalue) + s['exception'] = {} + if isinstance(evalue, BaseException): + for name in dir(evalue): + # prevent py26 DeprecatedWarning: + if name!='message' or sys.version_info<(2.6): + value = pydoc.text.repr(getattr(evalue, name)) + s['exception'][name] = value + + # add all local values (of last frame) to the snapshot + s['locals'] = {} + for name, value in locals.items(): + s['locals'][name] = pydoc.text.repr(value) + + # add web2py environment variables + for k,v in environment.items(): + if k in ('request', 'response', 'session'): + s[k] = BEAUTIFY(v) + + return s + ADDED gluon/rewrite.py Index: gluon/rewrite.py ================================================================== --- gluon/rewrite.py +++ gluon/rewrite.py @@ -0,0 +1,1220 @@ +#!/bin/env python +# -*- coding: utf-8 -*- + +""" +This file is part of the web2py Web Framework +Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> +License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) + +gluon.rewrite parses incoming URLs and formats outgoing URLs for gluon.html.URL. + +In addition, it rewrites both incoming and outgoing URLs based on the (optional) user-supplied routes.py, +which also allows for rewriting of certain error messages. + +routes.py supports two styles of URL rewriting, depending on whether 'routers' is defined. +Refer to router.example.py and routes.example.py for additional documentation. + +""" + +import os +import re +import logging +import traceback +import threading +import urllib +from storage import Storage, List +from http import HTTP +from fileutils import abspath, read_file +from settings import global_settings + +logger = logging.getLogger('web2py.rewrite') + +thread = threading.local() # thread-local storage for routing parameters + +def _router_default(): + "return new copy of default base router" + router = Storage( + default_application = 'init', + applications = 'ALL', + default_controller = 'default', + controllers = 'DEFAULT', + default_function = 'index', + functions = None, + default_language = None, + languages = None, + root_static = ['favicon.ico', 'robots.txt'], + domains = None, + exclusive_domain = False, + map_hyphen = False, + acfe_match = r'\w+$', # legal app/ctlr/fcn/ext + file_match = r'(\w+[-=./]?)+$', # legal file (path) name + args_match = r'([\w@ -]+[=.]?)*$', # legal arg in args + ) + return router + +def _params_default(app=None): + "return new copy of default parameters" + p = Storage() + p.name = app or "BASE" + p.default_application = app or "init" + p.default_controller = "default" + p.default_function = "index" + p.routes_app = [] + p.routes_in = [] + p.routes_out = [] + p.routes_onerror = [] + p.routes_apps_raw = [] + p.error_handler = None + p.error_message = '<html><body><h1>%s</h1></body></html>' + p.error_message_ticket = \ + '<html><body><h1>Internal error</h1>Ticket issued: <a href="/admin/default/ticket/%(ticket)s" target="_blank">%(ticket)s</a></body><!-- this is junk text else IE does not display the page: '+('x'*512)+' //--></html>' + p.routers = None + return p + +params_apps = dict() +params = _params_default(app=None) # regex rewrite parameters +thread.routes = params # default to base regex rewrite parameters +routers = None + +ROUTER_KEYS = set(('default_application', 'applications', 'default_controller', 'controllers', + 'default_function', 'functions', 'default_language', 'languages', + 'domain', 'domains', 'root_static', 'path_prefix', + 'exclusive_domain', 'map_hyphen', 'map_static', + 'acfe_match', 'file_match', 'args_match')) + +ROUTER_BASE_KEYS = set(('applications', 'default_application', 'domains', 'path_prefix')) + +# The external interface to rewrite consists of: +# +# load: load routing configuration file(s) +# url_in: parse and rewrite incoming URL +# url_out: assemble and rewrite outgoing URL +# +# thread.routes.default_application +# thread.routes.error_message +# thread.routes.error_message_ticket +# thread.routes.try_redirect_on_error +# thread.routes.error_handler +# +# filter_url: helper for doctest & unittest +# filter_err: helper for doctest & unittest +# regex_filter_out: doctest + +def url_in(request, environ): + "parse and rewrite incoming URL" + if routers: + return map_url_in(request, environ) + return regex_url_in(request, environ) + +def url_out(request, env, application, controller, function, args, other, scheme, host, port): + "assemble and rewrite outgoing URL" + if routers: + acf = map_url_out(request, env, application, controller, function, args, other, scheme, host, port) + url = '%s%s' % (acf, other) + else: + url = '/%s/%s/%s%s' % (application, controller, function, other) + url = regex_filter_out(url, env) + # + # fill in scheme and host if absolute URL is requested + # scheme can be a string, eg 'http', 'https', 'ws', 'wss' + # + if scheme or port is not None: + if host is None: # scheme or port implies host + host = True + if not scheme or scheme is True: + if request and request.env: + scheme = request.env.get('WSGI_URL_SCHEME', 'http').lower() + else: + scheme = 'http' # some reasonable default in case we need it + if host is not None: + if host is True: + host = request.env.http_host + if host: + if port is None: + port = '' + else: + port = ':%s' % port + url = '%s://%s%s%s' % (scheme, host, port, url) + return url + +def try_rewrite_on_error(http_response, request, environ, ticket=None): + """ + called from main.wsgibase to rewrite the http response. + """ + status = int(str(http_response.status).split()[0]) + if status>=399 and thread.routes.routes_onerror: + keys=set(('%s/%s' % (request.application, status), + '%s/*' % (request.application), + '*/%s' % (status), + '*/*')) + for (key,uri) in thread.routes.routes_onerror: + if key in keys: + if uri == '!': + # do nothing! + return http_response, environ + elif '?' in uri: + path_info, query_string = uri.split('?',1) + query_string += '&' + else: + path_info, query_string = uri, '' + query_string += \ + 'code=%s&ticket=%s&requested_uri=%s&request_url=%s' % \ + (status,ticket,request.env.request_uri,request.url) + if uri.startswith('http://') or uri.startswith('https://'): + # make up a response + url = path_info+'?'+query_string + message = 'You are being redirected <a href="%s">here</a>' + return HTTP(303, message % url, Location=url), environ + elif path_info!=environ['PATH_INFO']: + # rewrite request, call wsgibase recursively, avoid loop + environ['PATH_INFO'] = path_info + environ['QUERY_STRING'] = query_string + return None, environ + # do nothing! + return http_response, environ + +def try_redirect_on_error(http_object, request, ticket=None): + "called from main.wsgibase to rewrite the http response" + status = int(str(http_object.status).split()[0]) + if status>399 and thread.routes.routes_onerror: + keys=set(('%s/%s' % (request.application, status), + '%s/*' % (request.application), + '*/%s' % (status), + '*/*')) + for (key,redir) in thread.routes.routes_onerror: + if key in keys: + if redir == '!': + break + elif '?' in redir: + url = '%s&code=%s&ticket=%s&requested_uri=%s&request_url=%s' % \ + (redir,status,ticket,request.env.request_uri,request.url) + else: + url = '%s?code=%s&ticket=%s&requested_uri=%s&request_url=%s' % \ + (redir,status,ticket,request.env.request_uri,request.url) + return HTTP(303, + 'You are being redirected <a href="%s">here</a>' % url, + Location=url) + return http_object + + +def load(routes='routes.py', app=None, data=None, rdict=None): + """ + load: read (if file) and parse routes + store results in params + (called from main.py at web2py initialization time) + If data is present, it's used instead of the routes.py contents. + If rdict is present, it must be a dict to be used for routers (unit test) + """ + global params + global routers + if app is None: + # reinitialize + global params_apps + params_apps = dict() + params = _params_default(app=None) # regex rewrite parameters + thread.routes = params # default to base regex rewrite parameters + routers = None + + if isinstance(rdict, dict): + symbols = dict(routers=rdict) + path = 'rdict' + else: + if data is not None: + path = 'routes' + else: + if app is None: + path = abspath(routes) + else: + path = abspath('applications', app, routes) + if not os.path.exists(path): + return + data = read_file(path).replace('\r\n','\n') + + symbols = {} + try: + exec (data + '\n') in symbols + except SyntaxError, e: + logger.error( + '%s has a syntax error and will not be loaded\n' % path + + traceback.format_exc()) + raise e + + p = _params_default(app) + + for sym in ('routes_app', 'routes_in', 'routes_out'): + if sym in symbols: + for (k, v) in symbols[sym]: + p[sym].append(compile_regex(k, v)) + for sym in ('routes_onerror', 'routes_apps_raw', + 'error_handler','error_message', 'error_message_ticket', + 'default_application','default_controller', 'default_function'): + if sym in symbols: + p[sym] = symbols[sym] + if 'routers' in symbols: + p.routers = Storage(symbols['routers']) + for key in p.routers: + if isinstance(p.routers[key], dict): + p.routers[key] = Storage(p.routers[key]) + + if app is None: + params = p # install base rewrite parameters + thread.routes = params # install default as current routes + # + # create the BASE router if routers in use + # + routers = params.routers # establish routers if present + if isinstance(routers, dict): + routers = Storage(routers) + if routers is not None: + router = _router_default() + if routers.BASE: + router.update(routers.BASE) + routers.BASE = router + + # scan each app in applications/ + # create a router, if routers are in use + # parse the app-specific routes.py if present + # + all_apps = [] + for appname in [app for app in os.listdir(abspath('applications')) if not app.startswith('.')]: + if os.path.isdir(abspath('applications', appname)) and \ + os.path.isdir(abspath('applications', appname, 'controllers')): + all_apps.append(appname) + if routers: + router = Storage(routers.BASE) # new copy + if appname in routers: + for key in routers[appname].keys(): + if key in ROUTER_BASE_KEYS: + raise SyntaxError, "BASE-only key '%s' in router '%s'" % (key, appname) + router.update(routers[appname]) + routers[appname] = router + if os.path.exists(abspath('applications', appname, routes)): + load(routes, appname) + + if routers: + load_routers(all_apps) + + else: # app + params_apps[app] = p + if routers and p.routers: + if app in p.routers: + routers[app].update(p.routers[app]) + + logger.debug('URL rewrite is on. configuration in %s' % path) + + +regex_at = re.compile(r'(?<!\\)\$[a-zA-Z]\w*') +regex_anything = re.compile(r'(?<!\\)\$anything') + +def compile_regex(k, v): + """ + Preprocess and compile the regular expressions in routes_app/in/out + + The resulting regex will match a pattern of the form: + + [remote address]:[protocol]://[host]:[method] [path] + + We allow abbreviated regexes on input; here we try to complete them. + """ + k0 = k # original k for error reporting + # bracket regex in ^...$ if not already done + if not k[0] == '^': + k = '^%s' % k + if not k[-1] == '$': + k = '%s$' % k + # if there are no :-separated parts, prepend a catch-all for the IP address + if k.find(':') < 0: + # k = '^.*?:%s' % k[1:] + k = '^.*?:https?://[^:/]+:[a-z]+ %s' % k[1:] + # if there's no ://, provide a catch-all for the protocol, host & method + if k.find('://') < 0: + i = k.find(':/') + if i < 0: + raise SyntaxError, "routes pattern syntax error: path needs leading '/' [%s]" % k0 + k = r'%s:https?://[^:/]+:[a-z]+ %s' % (k[:i], k[i+1:]) + # $anything -> ?P<anything>.* + for item in regex_anything.findall(k): + k = k.replace(item, '(?P<anything>.*)') + # $a (etc) -> ?P<a>\w+ + for item in regex_at.findall(k): + k = k.replace(item, r'(?P<%s>\w+)' % item[1:]) + # same for replacement pattern, but with \g + for item in regex_at.findall(v): + v = v.replace(item, r'\g<%s>' % item[1:]) + return (re.compile(k, re.DOTALL), v) + +def load_routers(all_apps): + "load-time post-processing of routers" + + for app in routers.keys(): + # initialize apps with routers that aren't present, on behalf of unit tests + if app not in all_apps: + all_apps.append(app) + router = Storage(routers.BASE) # new copy + if app != 'BASE': + for key in routers[app].keys(): + if key in ROUTER_BASE_KEYS: + raise SyntaxError, "BASE-only key '%s' in router '%s'" % (key, app) + router.update(routers[app]) + routers[app] = router + router = routers[app] + for key in router.keys(): + if key not in ROUTER_KEYS: + raise SyntaxError, "unknown key '%s' in router '%s'" % (key, app) + if not router.controllers: + router.controllers = set() + elif not isinstance(router.controllers, str): + router.controllers = set(router.controllers) + if router.functions: + router.functions = set(router.functions) + else: + router.functions = set() + if router.languages: + router.languages = set(router.languages) + else: + router.languages = set() + if app != 'BASE': + for base_only in ROUTER_BASE_KEYS: + router.pop(base_only, None) + if 'domain' in router: + routers.BASE.domains[router.domain] = app + if isinstance(router.controllers, str) and router.controllers == 'DEFAULT': + router.controllers = set() + if os.path.isdir(abspath('applications', app)): + cpath = abspath('applications', app, 'controllers') + for cname in os.listdir(cpath): + if os.path.isfile(abspath(cpath, cname)) and cname.endswith('.py'): + router.controllers.add(cname[:-3]) + if router.controllers: + router.controllers.add('static') + router.controllers.add(router.default_controller) + if router.functions: + router.functions.add(router.default_function) + + if isinstance(routers.BASE.applications, str) and routers.BASE.applications == 'ALL': + routers.BASE.applications = list(all_apps) + if routers.BASE.applications: + routers.BASE.applications = set(routers.BASE.applications) + else: + routers.BASE.applications = set() + + for app in routers.keys(): + # set router name + router = routers[app] + router.name = app + # compile URL validation patterns + router._acfe_match = re.compile(router.acfe_match) + router._file_match = re.compile(router.file_match) + if router.args_match: + router._args_match = re.compile(router.args_match) + # convert path_prefix to a list of path elements + if router.path_prefix: + if isinstance(router.path_prefix, str): + router.path_prefix = router.path_prefix.strip('/').split('/') + + # rewrite BASE.domains as tuples + # + # key: 'domain[:port]' -> (domain, port) + # value: 'application[/controller] -> (application, controller) + # (port and controller may be None) + # + domains = dict() + if routers.BASE.domains: + for (domain, app) in [(d.strip(':'), a.strip('/')) for (d, a) in routers.BASE.domains.items()]: + port = None + if ':' in domain: + (domain, port) = domain.split(':') + ctlr = None + if '/' in app: + (app, ctlr) = app.split('/') + if app not in all_apps and app not in routers: + raise SyntaxError, "unknown app '%s' in domains" % app + domains[(domain, port)] = (app, ctlr) + routers.BASE.domains = domains + +def regex_uri(e, regexes, tag, default=None): + "filter incoming URI against a list of regexes" + path = e['PATH_INFO'] + host = e.get('HTTP_HOST', 'localhost').lower() + i = host.find(':') + if i > 0: + host = host[:i] + key = '%s:%s://%s:%s %s' % \ + (e.get('REMOTE_ADDR','localhost'), + e.get('WSGI_URL_SCHEME', 'http').lower(), host, + e.get('REQUEST_METHOD', 'get').lower(), path) + for (regex, value) in regexes: + if regex.match(key): + rewritten = regex.sub(value, key) + logger.debug('%s: [%s] [%s] -> %s' % (tag, key, value, rewritten)) + return rewritten + logger.debug('%s: [%s] -> %s (not rewritten)' % (tag, key, default)) + return default + +def regex_select(env=None, app=None, request=None): + """ + select a set of regex rewrite params for the current request + """ + if app: + thread.routes = params_apps.get(app, params) + elif env and params.routes_app: + if routers: + map_url_in(request, env, app=True) + else: + app = regex_uri(env, params.routes_app, "routes_app") + thread.routes = params_apps.get(app, params) + else: + thread.routes = params # default to base rewrite parameters + logger.debug("select routing parameters: %s" % thread.routes.name) + return app # for doctest + +def regex_filter_in(e): + "regex rewrite incoming URL" + query = e.get('QUERY_STRING', None) + e['WEB2PY_ORIGINAL_URI'] = e['PATH_INFO'] + (query and ('?' + query) or '') + if thread.routes.routes_in: + path = regex_uri(e, thread.routes.routes_in, "routes_in", e['PATH_INFO']) + items = path.split('?', 1) + e['PATH_INFO'] = items[0] + if len(items) > 1: + if query: + query = items[1] + '&' + query + else: + query = items[1] + e['QUERY_STRING'] = query + e['REQUEST_URI'] = e['PATH_INFO'] + (query and ('?' + query) or '') + return e + + +# pattern to replace spaces with underscore in URL +# also the html escaped variants '+' and '%20' are covered +regex_space = re.compile('(\+|\s|%20)+') + +# pattern to find valid paths in url /application/controller/... +# this could be: +# for static pages: +# /<b:application>/static/<x:file> +# for dynamic pages: +# /<a:application>[/<c:controller>[/<f:function>[.<e:ext>][/<s:args>]]] +# application, controller, function and ext may only contain [a-zA-Z0-9_] +# file and args may also contain '-', '=', '.' and '/' +# apps in routes_apps_raw must parse raw_args into args + +regex_static = re.compile(r''' + (^ # static pages + /(?P<b> \w+) # b=app + /static # /b/static + /(?P<x> (\w[\-\=\./]?)* ) # x=file + $) + ''', re.X) + +regex_url = re.compile(r''' + (^( # (/a/c/f.e/s) + /(?P<a> [\w\s+]+ ) # /a=app + ( # (/c.f.e/s) + /(?P<c> [\w\s+]+ ) # /a/c=controller + ( # (/f.e/s) + /(?P<f> [\w\s+]+ ) # /a/c/f=function + ( # (.e) + \.(?P<e> [\w\s+]+ ) # /a/c/f.e=extension + )? + ( # (/s) + /(?P<r> # /a/c/f.e/r=raw_args + .* + ) + )? + )? + )? + )? + /?$) + ''', re.X) + +regex_args = re.compile(r''' + (^ + (?P<s> + ( [\w@/-][=.]? )* # s=args + )? + /?$) # trailing slash + ''', re.X) + +def regex_url_in(request, environ): + "rewrite and parse incoming URL" + + # ################################################## + # select application + # rewrite URL if routes_in is defined + # update request.env + # ################################################## + + regex_select(env=environ, request=request) + + if thread.routes.routes_in: + environ = regex_filter_in(environ) + + for (key, value) in environ.items(): + request.env[key.lower().replace('.', '_')] = value + + path = request.env.path_info.replace('\\', '/') + + # ################################################## + # serve if a static file + # ################################################## + + match = regex_static.match(regex_space.sub('_', path)) + if match and match.group('x'): + static_file = os.path.join(request.env.applications_parent, + 'applications', match.group('b'), + 'static', match.group('x')) + return (static_file, environ) + + # ################################################## + # parse application, controller and function + # ################################################## + + path = re.sub('%20', ' ', path) + match = regex_url.match(path) + if not match or match.group('c') == 'static': + raise HTTP(400, + thread.routes.error_message % 'invalid request', + web2py_error='invalid path') + + request.application = \ + regex_space.sub('_', match.group('a') or thread.routes.default_application) + request.controller = \ + regex_space.sub('_', match.group('c') or thread.routes.default_controller) + request.function = \ + regex_space.sub('_', match.group('f') or thread.routes.default_function) + group_e = match.group('e') + request.raw_extension = group_e and regex_space.sub('_', group_e) or None + request.extension = request.raw_extension or 'html' + request.raw_args = match.group('r') + request.args = List([]) + if request.application in thread.routes.routes_apps_raw: + # application is responsible for parsing args + request.args = None + elif request.raw_args: + match = regex_args.match(request.raw_args.replace(' ', '_')) + if match: + group_s = match.group('s') + request.args = \ + List((group_s and group_s.split('/')) or []) + if request.args and request.args[-1] == '': + request.args.pop() # adjust for trailing empty arg + else: + raise HTTP(400, + thread.routes.error_message % 'invalid request', + web2py_error='invalid path (args)') + return (None, environ) + + +def regex_filter_out(url, e=None): + "regex rewrite outgoing URL" + if not hasattr(thread, 'routes'): + regex_select() # ensure thread.routes is set (for application threads) + if routers: + return url # already filtered + if thread.routes.routes_out: + items = url.split('?', 1) + if e: + host = e.get('http_host', 'localhost').lower() + i = host.find(':') + if i > 0: + host = host[:i] + items[0] = '%s:%s://%s:%s %s' % \ + (e.get('remote_addr', ''), + e.get('wsgi_url_scheme', 'http').lower(), host, + e.get('request_method', 'get').lower(), items[0]) + else: + items[0] = ':http://localhost:get %s' % items[0] + for (regex, value) in thread.routes.routes_out: + if regex.match(items[0]): + rewritten = '?'.join([regex.sub(value, items[0])] + items[1:]) + logger.debug('routes_out: [%s] -> %s' % (url, rewritten)) + return rewritten + logger.debug('routes_out: [%s] not rewritten' % url) + return url + + +def filter_url(url, method='get', remote='0.0.0.0', out=False, app=False, lang=None, + domain=(None,None), env=False, scheme=None, host=None, port=None): + "doctest/unittest interface to regex_filter_in() and regex_filter_out()" + regex_url = re.compile(r'^(?P<scheme>http|https|HTTP|HTTPS)\://(?P<host>[^/]*)(?P<uri>.*)') + match = regex_url.match(url) + urlscheme = match.group('scheme').lower() + urlhost = match.group('host').lower() + uri = match.group('uri') + k = uri.find('?') + if k < 0: + k = len(uri) + (path_info, query_string) = (uri[:k], uri[k+1:]) + path_info = urllib.unquote(path_info) # simulate server + e = { + 'REMOTE_ADDR': remote, + 'REQUEST_METHOD': method, + 'WSGI_URL_SCHEME': urlscheme, + 'HTTP_HOST': urlhost, + 'REQUEST_URI': uri, + 'PATH_INFO': path_info, + 'QUERY_STRING': query_string, + #for filter_out request.env use lowercase + 'remote_addr': remote, + 'request_method': method, + 'wsgi_url_scheme': urlscheme, + 'http_host': urlhost + } + + request = Storage() + e["applications_parent"] = global_settings.applications_parent + request.env = Storage(e) + request.uri_language = lang + + # determine application only + # + if app: + if routers: + return map_url_in(request, e, app=True) + return regex_select(e) + + # rewrite outbound URL + # + if out: + (request.env.domain_application, request.env.domain_controller) = domain + items = path_info.lstrip('/').split('/') + if items[-1] == '': + items.pop() # adjust trailing empty args + assert len(items) >= 3, "at least /a/c/f is required" + a = items.pop(0) + c = items.pop(0) + f = items.pop(0) + if not routers: + return regex_filter_out(uri, e) + acf = map_url_out(request, None, a, c, f, items, None, scheme, host, port) + if items: + url = '%s/%s' % (acf, '/'.join(items)) + if items[-1] == '': + url += '/' + else: + url = acf + if query_string: + url += '?' + query_string + return url + + # rewrite inbound URL + # + (static, e) = url_in(request, e) + if static: + return static + result = "/%s/%s/%s" % (request.application, request.controller, request.function) + if request.extension and request.extension != 'html': + result += ".%s" % request.extension + if request.args: + result += " %s" % request.args + if e['QUERY_STRING']: + result += " ?%s" % e['QUERY_STRING'] + if request.uri_language: + result += " (%s)" % request.uri_language + if env: + return request.env + return result + + +def filter_err(status, application='app', ticket='tkt'): + "doctest/unittest interface to routes_onerror" + if status > 399 and thread.routes.routes_onerror: + keys = set(('%s/%s' % (application, status), + '%s/*' % (application), + '*/%s' % (status), + '*/*')) + for (key,redir) in thread.routes.routes_onerror: + if key in keys: + if redir == '!': + break + elif '?' in redir: + url = redir + '&' + 'code=%s&ticket=%s' % (status,ticket) + else: + url = redir + '?' + 'code=%s&ticket=%s' % (status,ticket) + return url # redirection + return status # no action + +# router support +# +class MapUrlIn(object): + "logic for mapping incoming URLs" + + def __init__(self, request=None, env=None): + "initialize a map-in object" + self.request = request + self.env = env + + self.router = None + self.application = None + self.language = None + self.controller = None + self.function = None + self.extension = 'html' + + self.controllers = set() + self.functions = set() + self.languages = set() + self.default_language = None + self.map_hyphen = False + self.exclusive_domain = False + + path = self.env['PATH_INFO'] + self.query = self.env.get('QUERY_STRING', None) + path = path.lstrip('/') + self.env['PATH_INFO'] = '/' + path + self.env['WEB2PY_ORIGINAL_URI'] = self.env['PATH_INFO'] + (self.query and ('?' + self.query) or '') + + # to handle empty args, strip exactly one trailing slash, if present + # .../arg1// represents one trailing empty arg + # + if path.endswith('/'): + path = path[:-1] + self.args = List(path and path.split('/') or []) + + # see http://www.python.org/dev/peps/pep-3333/#url-reconstruction for URL composition + self.remote_addr = self.env.get('REMOTE_ADDR','localhost') + self.scheme = self.env.get('WSGI_URL_SCHEME', 'http').lower() + self.method = self.env.get('REQUEST_METHOD', 'get').lower() + self.host = self.env.get('HTTP_HOST') + self.port = None + if not self.host: + self.host = self.env.get('SERVER_NAME') + self.port = self.env.get('SERVER_PORT') + if not self.host: + self.host = 'localhost' + self.port = '80' + if ':' in self.host: + (self.host, self.port) = self.host.split(':') + if not self.port: + if self.scheme == 'https': + self.port = '443' + else: + self.port = '80' + + def map_prefix(self): + "strip path prefix, if present in its entirety" + prefix = routers.BASE.path_prefix + if prefix: + prefixlen = len(prefix) + if prefixlen > len(self.args): + return + for i in xrange(prefixlen): + if prefix[i] != self.args[i]: + return # prefix didn't match + self.args = List(self.args[prefixlen:]) # strip the prefix + + def map_app(self): + "determine application name" + base = routers.BASE # base router + self.domain_application = None + self.domain_controller = None + arg0 = self.harg0 + if base.applications and arg0 in base.applications: + self.application = arg0 + elif (self.host, self.port) in base.domains: + (self.application, self.domain_controller) = base.domains[(self.host, self.port)] + self.env['domain_application'] = self.application + self.env['domain_controller'] = self.domain_controller + elif (self.host, None) in base.domains: + (self.application, self.domain_controller) = base.domains[(self.host, None)] + self.env['domain_application'] = self.application + self.env['domain_controller'] = self.domain_controller + elif arg0 and not base.applications: + self.application = arg0 + else: + self.application = base.default_application or '' + self.pop_arg_if(self.application == arg0) + + if not base._acfe_match.match(self.application): + raise HTTP(400, thread.routes.error_message % 'invalid request', + web2py_error="invalid application: '%s'" % self.application) + + if self.application not in routers and \ + (self.application != thread.routes.default_application or self.application == 'welcome'): + raise HTTP(400, thread.routes.error_message % 'invalid request', + web2py_error="unknown application: '%s'" % self.application) + + # set the application router + # + logger.debug("select application=%s" % self.application) + self.request.application = self.application + if self.application not in routers: + self.router = routers.BASE # support gluon.main.wsgibase init->welcome + else: + self.router = routers[self.application] # application router + self.controllers = self.router.controllers + self.default_controller = self.domain_controller or self.router.default_controller + self.functions = self.router.functions + self.languages = self.router.languages + self.default_language = self.router.default_language + self.map_hyphen = self.router.map_hyphen + self.exclusive_domain = self.router.exclusive_domain + self._acfe_match = self.router._acfe_match + self._file_match = self.router._file_match + self._args_match = self.router._args_match + + def map_root_static(self): + ''' + handle root-static files (no hyphen mapping) + + a root-static file is one whose incoming URL expects it to be at the root, + typically robots.txt & favicon.ico + ''' + if len(self.args) == 1 and self.arg0 in self.router.root_static: + self.controller = self.request.controller = 'static' + root_static_file = os.path.join(self.request.env.applications_parent, + 'applications', self.application, + self.controller, self.arg0) + logger.debug("route: root static=%s" % root_static_file) + return root_static_file + return None + + def map_language(self): + "handle language (no hyphen mapping)" + arg0 = self.arg0 # no hyphen mapping + if arg0 and self.languages and arg0 in self.languages: + self.language = arg0 + else: + self.language = self.default_language + if self.language: + logger.debug("route: language=%s" % self.language) + self.pop_arg_if(self.language == arg0) + arg0 = self.arg0 + + def map_controller(self): + "identify controller" + # handle controller + # + arg0 = self.harg0 # map hyphens + if not arg0 or (self.controllers and arg0 not in self.controllers): + self.controller = self.default_controller or '' + else: + self.controller = arg0 + self.pop_arg_if(arg0 == self.controller) + logger.debug("route: controller=%s" % self.controller) + if not self.router._acfe_match.match(self.controller): + raise HTTP(400, thread.routes.error_message % 'invalid request', + web2py_error='invalid controller') + + def map_static(self): + ''' + handle static files + file_match but no hyphen mapping + ''' + if self.controller != 'static': + return None + file = '/'.join(self.args) + if not self.router._file_match.match(file): + raise HTTP(400, thread.routes.error_message % 'invalid request', + web2py_error='invalid static file') + # + # support language-specific static subdirectories, + # eg /appname/en/static/filename => applications/appname/static/en/filename + # if language-specific file doesn't exist, try same file in static + # + if self.language: + static_file = os.path.join(self.request.env.applications_parent, + 'applications', self.application, + 'static', self.language, file) + if not self.language or not os.path.isfile(static_file): + static_file = os.path.join(self.request.env.applications_parent, + 'applications', self.application, + 'static', file) + logger.debug("route: static=%s" % static_file) + return static_file + + def map_function(self): + "handle function.extension" + arg0 = self.harg0 # map hyphens + if not arg0 or self.functions and arg0 not in self.functions and self.controller == self.default_controller: + self.function = self.router.default_function or "" + self.pop_arg_if(arg0 and self.function == arg0) + else: + func_ext = arg0.split('.') + if len(func_ext) > 1: + self.function = func_ext[0] + self.extension = func_ext[-1] + else: + self.function = arg0 + self.pop_arg_if(True) + logger.debug("route: function.ext=%s.%s" % (self.function, self.extension)) + + if not self.router._acfe_match.match(self.function): + raise HTTP(400, thread.routes.error_message % 'invalid request', + web2py_error='invalid function') + if self.extension and not self.router._acfe_match.match(self.extension): + raise HTTP(400, thread.routes.error_message % 'invalid request', + web2py_error='invalid extension') + + def validate_args(self): + ''' + check args against validation pattern + ''' + for arg in self.args: + if not self.router._args_match.match(arg): + raise HTTP(400, thread.routes.error_message % 'invalid request', + web2py_error='invalid arg <%s>' % arg) + + def update_request(self): + ''' + update request from self + build env.request_uri + make lower-case versions of http headers in env + ''' + self.request.application = self.application + self.request.controller = self.controller + self.request.function = self.function + self.request.extension = self.extension + self.request.args = self.args + if self.language: + self.request.uri_language = self.language + uri = '/%s/%s/%s' % (self.application, self.controller, self.function) + if self.map_hyphen: + uri = uri.replace('_', '-') + if self.extension != 'html': + uri += '.' + self.extension + if self.language: + uri = '/%s%s' % (self.language, uri) + uri += self.args and urllib.quote('/' + '/'.join([str(x) for x in self.args])) or '' + uri += (self.query and ('?' + self.query) or '') + self.env['REQUEST_URI'] = uri + for (key, value) in self.env.items(): + self.request.env[key.lower().replace('.', '_')] = value + + @property + def arg0(self): + "return first arg" + return self.args(0) + + @property + def harg0(self): + "return first arg with optional hyphen mapping" + if self.map_hyphen and self.args(0): + return self.args(0).replace('-', '_') + return self.args(0) + + def pop_arg_if(self, dopop): + "conditionally remove first arg and return new first arg" + if dopop: + self.args.pop(0) + +class MapUrlOut(object): + "logic for mapping outgoing URLs" + + def __init__(self, request, env, application, controller, function, args, other, scheme, host, port): + "initialize a map-out object" + self.default_application = routers.BASE.default_application + if application in routers: + self.router = routers[application] + else: + self.router = routers.BASE + self.request = request + self.env = env + self.application = application + self.controller = controller + self.function = function + self.args = args + self.other = other + self.scheme = scheme + self.host = host + self.port = port + + self.applications = routers.BASE.applications + self.controllers = self.router.controllers + self.functions = self.router.functions + self.languages = self.router.languages + self.default_language = self.router.default_language + self.exclusive_domain = self.router.exclusive_domain + self.map_hyphen = self.router.map_hyphen + self.map_static = self.router.map_static + self.path_prefix = routers.BASE.path_prefix + + self.domain_application = request and self.request.env.domain_application + self.domain_controller = request and self.request.env.domain_controller + self.default_function = self.router.default_function + + if (self.router.exclusive_domain and self.domain_application and self.domain_application != self.application and not self.host): + raise SyntaxError, 'cross-domain conflict: must specify host' + + lang = request and request.uri_language + if lang and self.languages and lang in self.languages: + self.language = lang + else: + self.language = None + + self.omit_application = False + self.omit_language = False + self.omit_controller = False + self.omit_function = False + + def omit_lang(self): + "omit language if possible" + + if not self.language or self.language == self.default_language: + self.omit_language = True + + def omit_acf(self): + "omit what we can of a/c/f" + + router = self.router + + # Handle the easy no-args case of tail-defaults: /a/c /a / + # + if not self.args and self.function == router.default_function: + self.omit_function = True + if self.controller == router.default_controller: + self.omit_controller = True + if self.application == self.default_application: + self.omit_application = True + + # omit default application + # (which might be the domain default application) + # + default_application = self.domain_application or self.default_application + if self.application == default_application: + self.omit_application = True + + # omit controller if default controller + # + default_controller = ((self.application == self.domain_application) and self.domain_controller) or router.default_controller or '' + if self.controller == default_controller: + self.omit_controller = True + + # omit function if default controller/function + # + if self.functions and self.function == self.default_function and self.omit_controller: + self.omit_function = True + + # prohibit ambiguous cases + # + # because we presume the lang string to be unambiguous, its presence protects application omission + # + if self.omit_language: + if not self.applications or self.controller in self.applications: + self.omit_application = False + if self.omit_application: + if not self.applications or self.function in self.applications: + self.omit_controller = False + if not self.controllers or self.function in self.controllers: + self.omit_controller = False + if self.args: + if self.args[0] in self.functions or self.args[0] in self.controllers or self.args[0] in self.applications: + self.omit_function = False + if self.omit_controller: + if self.function in self.controllers or self.function in self.applications: + self.omit_controller = False + if self.omit_application: + if self.controller in self.applications: + self.omit_application = False + + # handle static as a special case + # (easier for external static handling) + # + if self.controller == 'static' or self.controller.startswith('static/'): + if not self.map_static: + self.omit_application = False + if self.language: + self.omit_language = False + self.omit_controller = False + self.omit_function = False + + def build_acf(self): + "build acf from components" + acf = '' + if self.map_hyphen: + self.application = self.application.replace('_', '-') + self.controller = self.controller.replace('_', '-') + if self.controller != 'static' and not self.controller.startswith('static/'): + self.function = self.function.replace('_', '-') + if not self.omit_application: + acf += '/' + self.application + if not self.omit_language: + acf += '/' + self.language + if not self.omit_controller: + acf += '/' + self.controller + if not self.omit_function: + acf += '/' + self.function + if self.path_prefix: + acf = '/' + '/'.join(self.path_prefix) + acf + if self.args: + return acf + return acf or '/' + + def acf(self): + "convert components to /app/lang/controller/function" + + if not routers: + return None # use regex filter + self.omit_lang() # try to omit language + self.omit_acf() # try to omit a/c/f + return self.build_acf() # build and return the /a/lang/c/f string + + +def map_url_in(request, env, app=False): + "route incoming URL" + + # initialize router-url object + # + thread.routes = params # default to base routes + map = MapUrlIn(request=request, env=env) + map.map_prefix() # strip prefix if present + map.map_app() # determine application + + # configure thread.routes for error rewrite + # + if params.routes_app: + thread.routes = params_apps.get(app, params) + + if app: + return map.application + + root_static_file = map.map_root_static() # handle root-static files + if root_static_file: + return (root_static_file, map.env) + map.map_language() + map.map_controller() + static_file = map.map_static() + if static_file: + return (static_file, map.env) + map.map_function() + map.validate_args() + map.update_request() + return (None, map.env) + +def map_url_out(request, env, application, controller, function, args, other, scheme, host, port): + ''' + supply /a/c/f (or /a/lang/c/f) portion of outgoing url + + The basic rule is that we can only make transformations + that map_url_in can reverse. + + Suppose that the incoming arguments are a,c,f,args,lang + and that the router defaults are da, dc, df, dl. + + We can perform these transformations trivially if args=[] and lang=None or dl: + + /da/dc/df => / + /a/dc/df => /a + /a/c/df => /a/c + + We would also like to be able to strip the default application or application/controller + from URLs with function/args present, thus: + + /da/c/f/args => /c/f/args + /da/dc/f/args => /f/args + + We use [applications] and [controllers] and [functions] to suppress ambiguous omissions. + + We assume that language names do not collide with a/c/f names. + ''' + map = MapUrlOut(request, env, application, controller, function, args, other, scheme, host, port) + return map.acf() + +def get_effective_router(appname): + "return a private copy of the effective router for the specified application" + if not routers or appname not in routers: + return None + return Storage(routers[appname]) # return a copy + ADDED gluon/rocket.py Index: gluon/rocket.py ================================================================== --- gluon/rocket.py +++ gluon/rocket.py @@ -0,0 +1,1625 @@ +# -*- coding: utf-8 -*- + +# This file is part of the Rocket Web Server +# Copyright (c) 2010 Timothy Farrell + +# Import System Modules +import sys +import errno +import socket +import logging +import platform + +# Define Constants +VERSION = '1.2.2' +SERVER_NAME = socket.gethostname() +SERVER_SOFTWARE = 'Rocket %s' % VERSION +HTTP_SERVER_SOFTWARE = '%s Python/%s' % (SERVER_SOFTWARE, sys.version.split(' ')[0]) +BUF_SIZE = 16384 +SOCKET_TIMEOUT = 1 # in secs +THREAD_STOP_CHECK_INTERVAL = 1 # in secs, How often should threads check for a server stop message? +IS_JYTHON = platform.system() == 'Java' # Handle special cases for Jython +IGNORE_ERRORS_ON_CLOSE = set([errno.ECONNABORTED, errno.ECONNRESET]) +DEFAULT_LISTEN_QUEUE_SIZE = 5 +DEFAULT_MIN_THREADS = 10 +DEFAULT_MAX_THREADS = 0 +DEFAULTS = dict(LISTEN_QUEUE_SIZE = DEFAULT_LISTEN_QUEUE_SIZE, + MIN_THREADS = DEFAULT_MIN_THREADS, + MAX_THREADS = DEFAULT_MAX_THREADS) + +PY3K = sys.version_info[0] > 2 + +class NullHandler(logging.Handler): + "A Logging handler to prevent library errors." + def emit(self, record): + pass + +if PY3K: + def b(val): + """ Convert string/unicode/bytes literals into bytes. This allows for + the same code to run on Python 2.x and 3.x. """ + if isinstance(val, str): + return val.encode() + else: + return val + + def u(val, encoding="us-ascii"): + """ Convert bytes into string/unicode. This allows for the + same code to run on Python 2.x and 3.x. """ + if isinstance(val, bytes): + return val.decode(encoding) + else: + return val + +else: + def b(val): + """ Convert string/unicode/bytes literals into bytes. This allows for + the same code to run on Python 2.x and 3.x. """ + if isinstance(val, unicode): + return val.encode() + else: + return val + + def u(val, encoding="us-ascii"): + """ Convert bytes into string/unicode. This allows for the + same code to run on Python 2.x and 3.x. """ + if isinstance(val, str): + return val.decode(encoding) + else: + return val + +# Import Package Modules +# package imports removed in monolithic build + +__all__ = ['VERSION', 'SERVER_SOFTWARE', 'HTTP_SERVER_SOFTWARE', 'BUF_SIZE', + 'IS_JYTHON', 'IGNORE_ERRORS_ON_CLOSE', 'DEFAULTS', 'PY3K', 'b', 'u', + 'Rocket', 'CherryPyWSGIServer', 'SERVER_NAME', 'NullHandler'] + +# Monolithic build...end of module: rocket\__init__.py +# Monolithic build...start of module: rocket\connection.py + +# Import System Modules +import sys +import time +import socket +try: + import ssl + has_ssl = True +except ImportError: + has_ssl = False +# package imports removed in monolithic build + +class Connection(object): + __slots__ = [ + 'setblocking', + 'sendall', + 'shutdown', + 'makefile', + 'fileno', + 'client_addr', + 'client_port', + 'server_port', + 'socket', + 'start_time', + 'ssl', + 'secure' + ] + + def __init__(self, sock_tuple, port, secure=False): + self.client_addr, self.client_port = sock_tuple[1] + self.server_port = port + self.socket = sock_tuple[0] + self.start_time = time.time() + self.ssl = has_ssl and isinstance(self.socket, ssl.SSLSocket) + self.secure = secure + + if IS_JYTHON: + # In Jython we must set TCP_NODELAY here since it does not + # inherit from the listening socket. + # See: http://bugs.jython.org/issue1309 + self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + + self.socket.settimeout(SOCKET_TIMEOUT) + + self.sendall = self.socket.sendall + self.shutdown = self.socket.shutdown + self.fileno = self.socket.fileno + self.makefile = self.socket.makefile + self.setblocking = self.socket.setblocking + + def close(self): + if hasattr(self.socket, '_sock'): + try: + self.socket._sock.close() + except socket.error: + info = sys.exc_info() + if info[1].errno != socket.EBADF: + raise info[1] + else: + pass + self.socket.close() + +# Monolithic build...end of module: rocket\connection.py +# Monolithic build...start of module: rocket\listener.py + +# Import System Modules +import os +import socket +import logging +import traceback +from threading import Thread + +try: + import ssl + from ssl import SSLError + has_ssl = True +except ImportError: + has_ssl = False + class SSLError(socket.error): + pass +# Import Package Modules +# package imports removed in monolithic build + +class Listener(Thread): + """The Listener class is a class responsible for accepting connections + and queuing them to be processed by a worker thread.""" + + def __init__(self, interface, queue_size, active_queue, *args, **kwargs): + Thread.__init__(self, *args, **kwargs) + + # Instance variables + self.active_queue = active_queue + self.interface = interface + self.addr = interface[0] + self.port = interface[1] + self.secure = len(interface) == 4 and \ + os.path.exists(interface[2]) and \ + os.path.exists(interface[3]) + self.ready = False + + # Error Log + self.err_log = logging.getLogger('Rocket.Errors.Port%i' % self.port) + self.err_log.addHandler(NullHandler()) + + # Build the socket + listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + if not listener: + self.err_log.error("Failed to get socket.") + return + + if self.secure: + if not has_ssl: + self.err_log.error("ssl module required to serve HTTPS.") + return + elif not os.path.exists(interface[2]): + data = (interface[2], interface[0], interface[1]) + self.err_log.error("Cannot find key file " + "'%s'. Cannot bind to %s:%s" % data) + return + elif not os.path.exists(interface[3]): + data = (interface[3], interface[0], interface[1]) + self.err_log.error("Cannot find certificate file " + "'%s'. Cannot bind to %s:%s" % data) + return + + # Set socket options + try: + listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + except: + msg = "Cannot share socket. Using %s:%i exclusively." + self.err_log.warning(msg % (self.addr, self.port)) + + try: + if not IS_JYTHON: + listener.setsockopt(socket.IPPROTO_TCP, + socket.TCP_NODELAY, + 1) + except: + msg = "Cannot set TCP_NODELAY, things might run a little slower" + self.err_log.warning(msg) + + try: + listener.bind((self.addr, self.port)) + except: + msg = "Socket %s:%i in use by other process and it won't share." + self.err_log.error(msg % (self.addr, self.port)) + else: + # We want socket operations to timeout periodically so we can + # check if the server is shutting down + listener.settimeout(THREAD_STOP_CHECK_INTERVAL) + # Listen for new connections allowing queue_size number of + # connections to wait before rejecting a connection. + listener.listen(queue_size) + + self.listener = listener + + self.ready = True + + def wrap_socket(self, sock): + try: + sock = ssl.wrap_socket(sock, + keyfile = self.interface[2], + certfile = self.interface[3], + server_side = True, + ssl_version = ssl.PROTOCOL_SSLv23) + except SSLError: + # Generally this happens when an HTTP request is received on a + # secure socket. We don't do anything because it will be detected + # by Worker and dealt with appropriately. + pass + + return sock + + + def run(self): + if not self.ready: + self.err_log.warning('Listener started when not ready.') + return + + if __debug__: + self.err_log.debug('Entering main loop.') + while True: + try: + sock, addr = self.listener.accept() + + if self.secure: + sock = self.wrap_socket(sock) + + self.active_queue.put(((sock, addr), + self.interface[1], + self.secure)) + + except socket.timeout: + # socket.timeout will be raised every THREAD_STOP_CHECK_INTERVAL + # seconds. When that happens, we check if it's time to die. + + if not self.ready: + if __debug__: + self.err_log.debug('Listener exiting.') + return + else: + continue + except: + self.err_log.error(str(traceback.format_exc())) + +# Monolithic build...end of module: rocket\listener.py +# Monolithic build...start of module: rocket\main.py + +# Import System Modules +import sys +import time +import socket +import logging +import traceback + +try: + from queue import Queue +except ImportError: + from Queue import Queue + +# Import Package Modules +# package imports removed in monolithic build + + + + + +# Setup Logging +log = logging.getLogger('Rocket') +log.addHandler(NullHandler()) + +class Rocket(object): + """The Rocket class is responsible for handling threads and accepting and + dispatching connections.""" + + def __init__(self, + interfaces = ('127.0.0.1', 8000), + method = 'wsgi', + app_info = None, + min_threads = None, + max_threads = None, + queue_size = None, + timeout = 600, + handle_signals = True): + + self.handle_signals = handle_signals + + if not isinstance(interfaces, list): + self.interfaces = [interfaces] + else: + self.interfaces = interfaces + + if min_threads is None: + min_threads = DEFAULTS['MIN_THREADS'] + + if max_threads is None: + max_threads = DEFAULTS['MAX_THREADS'] + + if not queue_size: + if hasattr(socket, 'SOMAXCONN'): + queue_size = socket.SOMAXCONN + else: + queue_size = DEFAULTS['LISTEN_QUEUE_SIZE'] + + if max_threads and queue_size > max_threads: + queue_size = max_threads + + if isinstance(app_info, dict): + app_info['server_software'] = SERVER_SOFTWARE + + monitor_queue = Queue() + active_queue = Queue() + + self._monitor = Monitor(monitor_queue, active_queue, timeout) + + self._threadpool = ThreadPool(get_method(method), + app_info = app_info, + active_queue=active_queue, + monitor_queue = monitor_queue, + min_threads=min_threads, + max_threads=max_threads) + + # Build our socket listeners + self.listeners = [Listener(i, queue_size, active_queue) for i in self.interfaces] + for ndx in range(len(self.listeners)-1, 0, -1): + if not self.listeners[ndx].ready: + del self.listeners[ndx] + + if not self.listeners: + log.critical("No interfaces to listen on...closing.") + sys.exit(1) + + def _sigterm(self, signum, frame): + log.info('Received SIGTERM') + self.stop() + + def _sighup(self, signum, frame): + log.info('Received SIGHUP') + self.restart() + + def start(self): + log.info('Starting %s' % SERVER_SOFTWARE) + + # Set up our shutdown signals + if self.handle_signals: + try: + import signal + signal.signal(signal.SIGTERM, self._sigterm) + signal.signal(signal.SIGUSR1, self._sighup) + except: + log.debug('This platform does not support signals.') + + # Start our worker threads + self._threadpool.start() + + # Start our monitor thread + self._monitor.setDaemon(True) + self._monitor.start() + + # I know that EXPR and A or B is bad but I'm keeping it for Py2.4 + # compatibility. + str_extract = lambda l: (l.addr, l.port, l.secure and '*' or '') + + msg = 'Listening on sockets: ' + msg += ', '.join(['%s:%i%s' % str_extract(l) for l in self.listeners]) + log.info(msg) + + for l in self.listeners: + l.start() + + tp = self._threadpool + dynamic_resize = tp.dynamic_resize + + while not tp.stop_server: + try: + dynamic_resize() + time.sleep(THREAD_STOP_CHECK_INTERVAL) + except KeyboardInterrupt: + # Capture a keyboard interrupt when running from a console + break + except: + if not tp.stop_server: + log.error(str(traceback.format_exc())) + continue + + return self.stop() + + def stop(self, stoplogging = True): + log.info("Stopping Server") + + # Stop listeners + for l in self.listeners: + l.ready = False + if l.isAlive(): + l.join() + + # Stop Worker threads + self._threadpool.stop() + + # Stop Monitor + self._monitor.stop() + if self._monitor.isAlive(): + self._monitor.join() + + if stoplogging: + logging.shutdown() + + def restart(self): + self.stop(False) + self.start() + +def CherryPyWSGIServer(bind_addr, + wsgi_app, + numthreads = 10, + server_name = None, + max = -1, + request_queue_size = 5, + timeout = 10, + shutdown_timeout = 5): + """ A Cherrypy wsgiserver-compatible wrapper. """ + max_threads = max + if max_threads < 0: + max_threads = 0 + return Rocket(bind_addr, 'wsgi', {'wsgi_app': wsgi_app}, + min_threads = numthreads, + max_threads = max_threads, + queue_size = request_queue_size, + timeout = timeout) + +# Monolithic build...end of module: rocket\main.py +# Monolithic build...start of module: rocket\monitor.py + +# Import System Modules +import time +import logging +import select +from threading import Thread + +# Import Package Modules +# package imports removed in monolithic build + +class Monitor(Thread): + # Monitor worker class. + + def __init__(self, + monitor_queue, + active_queue, + timeout, + *args, + **kwargs): + + Thread.__init__(self, *args, **kwargs) + + # Instance Variables + self.monitor_queue = monitor_queue + self.active_queue = active_queue + self.timeout = timeout + + self.connections = set() + self.active = False + + def run(self): + self.name = self.getName() + self.log = logging.getLogger('Rocket.Monitor') + self.log.addHandler(NullHandler()) + + self.active = True + conn_list = list() + list_changed = False + + if __debug__: + self.log.debug('Entering monitor loop.') + + # Enter thread main loop + while self.active: + # Move the queued connections to the selection pool + while not self.monitor_queue.empty() or not len(self.connections): + if __debug__: + self.log.debug('In "receive timed-out connections" loop.') + + c = self.monitor_queue.get() + + if c is None: + # A non-client is a signal to die + if __debug__: + self.log.debug('Received a death threat.') + return + + self.log.debug('Received a timed out connection.') + + if __debug__: + assert(c not in self.connections) + + if IS_JYTHON: + # Jython requires a socket to be in Non-blocking mode in + # order to select on it. + c.setblocking(False) + + if __debug__: + self.log.debug('Adding connection to monitor list.') + + self.connections.add(c) + list_changed = True + + # Wait on those connections + self.log.debug('Blocking on connections') + if list_changed: + conn_list = list(self.connections) + list_changed = False + + try: + readable = select.select(conn_list, + [], + [], + THREAD_STOP_CHECK_INTERVAL)[0] + except: + if self.active: + raise + else: + break + + # If we have any readable connections, put them back + for r in readable: + if __debug__: + self.log.debug('Restoring readable connection') + + if IS_JYTHON: + # Jython requires a socket to be in Non-blocking mode in + # order to select on it, but the rest of the code requires + # that it be in blocking mode. + r.setblocking(True) + + r.start_time = time.time() + self.active_queue.put(r) + + self.connections.remove(r) + list_changed = True + + # If we have any stale connections, kill them off. + if self.timeout: + now = time.time() + stale = set() + for c in self.connections: + if (now - c.start_time) >= self.timeout: + stale.add(c) + + for c in stale: + if __debug__: + # "EXPR and A or B" kept for Py2.4 compatibility + data = (c.client_addr, c.server_port, c.ssl and '*' or '') + self.log.debug('Flushing stale connection: %s:%i%s' % data) + + self.connections.remove(c) + list_changed = True + + try: + c.close() + finally: + del c + + def stop(self): + self.active = False + + if __debug__: + self.log.debug('Flushing waiting connections') + + for c in self.connections: + try: + c.close() + finally: + del c + + if __debug__: + self.log.debug('Flushing queued connections') + + while not self.monitor_queue.empty(): + c = self.monitor_queue.get() + + if c is None: + continue + + try: + c.close() + finally: + del c + + # Place a None sentry value to cause the monitor to die. + self.monitor_queue.put(None) + +# Monolithic build...end of module: rocket\monitor.py +# Monolithic build...start of module: rocket\threadpool.py + +# Import System Modules +import logging +# Import Package Modules +# package imports removed in monolithic build + +# Setup Logging +log = logging.getLogger('Rocket.Errors.ThreadPool') +log.addHandler(NullHandler()) + +class ThreadPool: + """The ThreadPool class is a container class for all the worker threads. It + manages the number of actively running threads.""" + + def __init__(self, + method, + app_info, + active_queue, + monitor_queue, + min_threads=DEFAULTS['MIN_THREADS'], + max_threads=DEFAULTS['MAX_THREADS'], + ): + + if __debug__: + log.debug("Initializing ThreadPool.") + + self.check_for_dead_threads = 0 + self.active_queue = active_queue + + self.worker_class = method + self.min_threads = min_threads + self.max_threads = max_threads + self.monitor_queue = monitor_queue + self.stop_server = False + + # TODO - Optimize this based on some real-world usage data + self.grow_threshold = int(max_threads/10) + 2 + + if not isinstance(app_info, dict): + app_info = dict() + + app_info.update(max_threads=max_threads, + min_threads=min_threads) + + self.app_info = app_info + + self.threads = set() + for x in range(min_threads): + worker = self.worker_class(app_info, + self.active_queue, + self.monitor_queue) + self.threads.add(worker) + + def start(self): + self.stop_server = False + if __debug__: + log.debug("Starting threads.") + + for thread in self.threads: + thread.setDaemon(True) + thread.start() + + def stop(self): + if __debug__: + log.debug("Stopping threads.") + + self.stop_server = True + + # Prompt the threads to die + for t in self.threads: + self.active_queue.put(None) + + # Give them the gun + for t in self.threads: + t.kill() + + # Wait until they pull the trigger + for t in self.threads: + t.join() + + # Clean up the mess + self.bring_out_your_dead() + + def bring_out_your_dead(self): + # Remove dead threads from the pool + + dead_threads = [t for t in self.threads if not t.isAlive()] + for t in dead_threads: + if __debug__: + log.debug("Removing dead thread: %s." % t.getName()) + try: + # Py2.4 complains here so we put it in a try block + self.threads.remove(t) + except: + pass + self.check_for_dead_threads -= len(dead_threads) + + def grow(self, amount=None): + if self.stop_server: + return + + if not amount: + amount = self.max_threads + + amount = min([amount, self.max_threads - len(self.threads)]) + + if __debug__: + log.debug("Growing by %i." % amount) + + for x in range(amount): + worker = self.worker_class(self.app_info, + self.active_queue, + self.monitor_queue) + + worker.setDaemon(True) + self.threads.add(worker) + worker.start() + + def shrink(self, amount=1): + if __debug__: + log.debug("Shrinking by %i." % amount) + + self.check_for_dead_threads += amount + + for x in range(amount): + self.active_queue.put(None) + + def dynamic_resize(self): + if (self.max_threads > self.min_threads or self.max_threads == 0): + if self.check_for_dead_threads > 0: + self.bring_out_your_dead() + + queueSize = self.active_queue.qsize() + threadCount = len(self.threads) + + if __debug__: + log.debug("Examining ThreadPool. %i threads and %i Q'd conxions" + % (threadCount, queueSize)) + + if queueSize == 0 and threadCount > self.min_threads: + self.shrink() + + elif queueSize > self.grow_threshold: + + self.grow(queueSize) + +# Monolithic build...end of module: rocket\threadpool.py +# Monolithic build...start of module: rocket\worker.py + +# Import System Modules +import re +import sys +import socket +import logging +import traceback +#from wsgiref.headers import Headers +from threading import Thread +from datetime import datetime + +try: + from urllib import unquote +except ImportError: + from urllib.parse import unquote + +try: + from io import StringIO +except ImportError: + try: + from cStringIO import StringIO + except ImportError: + from StringIO import StringIO + +try: + from ssl import SSLError +except ImportError: + class SSLError(socket.error): + pass +# Import Package Modules +# package imports removed in monolithic build + + +# Define Constants +re_SLASH = re.compile('%2F', re.IGNORECASE) +re_REQUEST_LINE = re.compile(r"""^ +(?P<method>OPTIONS|GET|HEAD|POST|PUT|DELETE|TRACE|CONNECT) # Request Method +\ # (single space) +( + (?P<scheme>[^:/]+) # Scheme + (://) # + (?P<host>[^/]+) # Host +)? # +(?P<path>(\*|/[^ \?]*)) # Path +(\? (?P<query_string>[^ ]+))? # Query String +\ # (single space) +(?P<protocol>HTTPS?/1\.[01]) # Protocol +$ +""", re.X) +LOG_LINE = '%(client_ip)s - "%(request_line)s" - %(status)s %(size)s' +RESPONSE = '''\ +HTTP/1.1 %s +Content-Length: %i +Content-Type: %s + +%s +''' +if IS_JYTHON: + HTTP_METHODS = set(['OPTIONS', 'GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'TRACE', 'CONNECT']) + +### +# The Headers and FileWrapper classes are ripped straight from the Python +# Standard Library. I've removed some docstrings and integrated my BUF_SIZE. +# See the Python License here: http://docs.python.org/license.html +### + +# Regular expression that matches `special' characters in parameters, the +# existance of which force quoting of the parameter value. +import re +_tspecials = re.compile(r'[ \(\)<>@,;:\\"/\[\]\?=]') + +def _formatparam(param, value=None, quote=1): + """Convenience function to format and return a key=value pair. + + This will quote the value if needed or if quote is true. + """ + if value is not None and len(value) > 0: + if quote or _tspecials.search(value): + value = value.replace('\\', '\\\\').replace('"', r'\"') + return '%s="%s"' % (param, value) + else: + return '%s=%s' % (param, value) + else: + return param + +class Headers: + def __init__(self,headers): + if type(headers) is not type([]): + raise TypeError("Headers must be a list of name/value tuples") + self._headers = headers + + def __len__(self): + return len(self._headers) + + def __setitem__(self, name, val): + del self[name] + self._headers.append((name, val)) + + def __delitem__(self,name): + name = name.lower() + self._headers[:] = [kv for kv in self._headers if kv[0].lower() != name] + + def __getitem__(self,name): + return self.get(name) + + def has_key(self, name): + return self.get(name) is not None + + __contains__ = has_key + + def get_all(self, name): + name = name.lower() + return [kv[1] for kv in self._headers if kv[0].lower()==name] + + def get(self,name,default=None): + name = name.lower() + for k,v in self._headers: + if k.lower()==name: + return v + return default + + def keys(self): + return [k for k, v in self._headers] + + def values(self): + return [v for k, v in self._headers] + + def items(self): + return self._headers[:] + + def __repr__(self): + return "Headers(%r)" % self._headers + + def __str__(self): + return '\r\n'.join(["%s: %s" % kv for kv in self._headers]+['','']) + + def setdefault(self,name,value): + result = self.get(name) + if result is None: + self._headers.append((name,value)) + return value + else: + return result + + + def add_header(self, _name, _value, **_params): + parts = [] + if _value is not None: + parts.append(_value) + for k, v in _params.items(): + if v is None: + parts.append(k.replace('_', '-')) + else: + parts.append(_formatparam(k.replace('_', '-'), v)) + self._headers.append((_name, "; ".join(parts))) + +class FileWrapper: + """Wrapper to convert file-like objects to iterables""" + + def __init__(self, filelike, blksize=BUF_SIZE): + self.filelike = filelike + self.blksize = blksize + if hasattr(filelike,'close'): + self.close = filelike.close + + def __getitem__(self,key): + data = self.filelike.read(self.blksize) + if data: + return data + raise IndexError + + def __iter__(self): + return self + + def next(self): + data = self.filelike.read(self.blksize) + if data: + return data + raise StopIteration + +class Worker(Thread): + """The Worker class is a base class responsible for receiving connections + and (a subclass) will run an application to process the the connection """ + + def __init__(self, + app_info, + active_queue, + monitor_queue, + *args, + **kwargs): + + Thread.__init__(self, *args, **kwargs) + + # Instance Variables + self.app_info = app_info + self.active_queue = active_queue + self.monitor_queue = monitor_queue + + self.size = 0 + self.status = "200 OK" + self.closeConnection = True + + # Request Log + self.req_log = logging.getLogger('Rocket.Requests') + self.req_log.addHandler(NullHandler()) + + # Error Log + self.err_log = logging.getLogger('Rocket.Errors.'+self.getName()) + self.err_log.addHandler(NullHandler()) + + def _handleError(self, typ, val, tb): + if typ == SSLError: + if 'timed out' in val.args[0]: + typ = SocketTimeout + if typ == SocketTimeout: + if __debug__: + self.err_log.debug('Socket timed out') + self.monitor_queue.put(self.conn) + return True + if typ == SocketClosed: + self.closeConnection = True + if __debug__: + self.err_log.debug('Client closed socket') + return False + if typ == BadRequest: + self.closeConnection = True + if __debug__: + self.err_log.debug('Client sent a bad request') + return True + if typ == socket.error: + self.closeConnection = True + if val.args[0] in IGNORE_ERRORS_ON_CLOSE: + if __debug__: + self.err_log.debug('Ignorable socket Error received...' + 'closing connection.') + return False + else: + self.status = "999 Utter Server Failure" + tb_fmt = traceback.format_exception(typ, val, tb) + self.err_log.error('Unhandled Error when serving ' + 'connection:\n' + '\n'.join(tb_fmt)) + return False + + self.closeConnection = True + tb_fmt = traceback.format_exception(typ, val, tb) + self.err_log.error('\n'.join(tb_fmt)) + self.send_response('500 Server Error') + return False + + def run(self): + if __debug__: + self.err_log.debug('Entering main loop.') + + # Enter thread main loop + while True: + conn = self.active_queue.get() + + if not conn: + # A non-client is a signal to die + if __debug__: + self.err_log.debug('Received a death threat.') + return conn + + if isinstance(conn, tuple): + conn = Connection(*conn) + + self.conn = conn + + if conn.ssl != conn.secure: + self.err_log.info('Received HTTP connection on HTTPS port.') + self.send_response('400 Bad Request') + self.closeConnection = True + conn.close() + continue + else: + if __debug__: + self.err_log.debug('Received a connection.') + self.closeConnection = False + + # Enter connection serve loop + while True: + if __debug__: + self.err_log.debug('Serving a request') + try: + self.run_app(conn) + log_info = dict(client_ip = conn.client_addr, + time = datetime.now().strftime('%c'), + status = self.status.split(' ')[0], + size = self.size, + request_line = self.request_line) + self.req_log.info(LOG_LINE % log_info) + except: + exc = sys.exc_info() + handled = self._handleError(*exc) + if handled: + break + else: + if self.request_line: + log_info = dict(client_ip = conn.client_addr, + time = datetime.now().strftime('%c'), + status = self.status.split(' ')[0], + size = self.size, + request_line = self.request_line + ' - not stopping') + self.req_log.info(LOG_LINE % log_info) + + if self.closeConnection: + try: + conn.close() + except: + self.err_log.error(str(traceback.format_exc())) + + break + + def run_app(self, conn): + # Must be overridden with a method reads the request from the socket + # and sends a response. + self.closeConnection = True + raise NotImplementedError('Overload this method!') + + def send_response(self, status): + stat_msg = status.split(' ', 1)[1] + msg = RESPONSE % (status, + len(stat_msg), + 'text/plain', + stat_msg) + try: + self.conn.sendall(b(msg)) + except socket.error: + self.closeConnection = True + self.err_log.error('Tried to send "%s" to client but received socket' + ' error' % status) + + def kill(self): + if self.isAlive() and hasattr(self, 'conn'): + try: + self.conn.shutdown(socket.SHUT_RDWR) + except socket.error: + info = sys.exc_info() + if info[1].args[0] != socket.EBADF: + self.err_log.debug('Error on shutdown: '+str(info)) + + def read_request_line(self, sock_file): + self.request_line = '' + try: + # Grab the request line + d = sock_file.readline() + if PY3K: + d = d.decode('ISO-8859-1') + + if d == '\r\n': + # Allow an extra NEWLINE at the beginning per HTTP 1.1 spec + if __debug__: + self.err_log.debug('Client sent newline') + + d = sock_file.readline() + if PY3K: + d = d.decode('ISO-8859-1') + except socket.timeout: + raise SocketTimeout("Socket timed out before request.") + + d = d.strip() + + if not d: + if __debug__: + self.err_log.debug('Client did not send a recognizable request.') + raise SocketClosed('Client closed socket.') + + self.request_line = d + + # NOTE: I've replaced the traditional method of procedurally breaking + # apart the request line with a (rather unsightly) regular expression. + # However, Java's regexp support sucks so bad that it actually takes + # longer in Jython to process the regexp than procedurally. So I've + # left the old code here for Jython's sake...for now. + if IS_JYTHON: + return self._read_request_line_jython(d) + + match = re_REQUEST_LINE.match(d) + + if not match: + self.send_response('400 Bad Request') + raise BadRequest + + req = match.groupdict() + for k,v in req.items(): + if not v: + req[k] = "" + if k == 'path': + req['path'] = r'%2F'.join([unquote(x) for x in re_SLASH.split(v)]) + + return req + + def _read_request_line_jython(self, d): + d = d.strip() + try: + method, uri, proto = d.split(' ') + if not proto.startswith('HTTP') or \ + proto[-3:] not in ('1.0', '1.1') or \ + method not in HTTP_METHODS: + self.send_response('400 Bad Request') + raise BadRequest + except ValueError: + self.send_response('400 Bad Request') + raise BadRequest + + req = dict(method=method, protocol = proto) + scheme = '' + host = '' + if uri == '*' or uri.startswith('/'): + path = uri + elif '://' in uri: + scheme, rest = uri.split('://') + host, path = rest.split('/', 1) + path = '/' + path + else: + self.send_response('400 Bad Request') + raise BadRequest + + query_string = '' + if '?' in path: + path, query_string = path.split('?', 1) + + path = r'%2F'.join([unquote(x) for x in re_SLASH.split(path)]) + + req.update(path=path, + query_string=query_string, + scheme=scheme.lower(), + host=host) + return req + + + def read_headers(self, sock_file): + headers = dict() + l = sock_file.readline() + + lname = None + lval = None + while True: + if PY3K: + try: + l = str(l, 'ISO-8859-1') + except UnicodeDecodeError: + self.err_log.warning('Client sent invalid header: ' + repr(l)) + + if l == '\r\n': + break + + if l[0] in ' \t' and lname: + # Some headers take more than one line + lval += ',' + l.strip() + else: + # HTTP header values are latin-1 encoded + l = l.split(':', 1) + # HTTP header names are us-ascii encoded + + lname = l[0].strip().upper().replace('-', '_') + lval = l[-1].strip() + headers[str(lname)] = str(lval) + + l = sock_file.readline() + return headers + +class SocketTimeout(Exception): + "Exception for when a socket times out between requests." + pass + +class BadRequest(Exception): + "Exception for when a client sends an incomprehensible request." + pass + +class SocketClosed(Exception): + "Exception for when a socket is closed by the client." + pass + +class ChunkedReader(object): + def __init__(self, sock_file): + self.stream = sock_file + self.chunk_size = 0 + + def _read_header(self): + chunk_len = "" + try: + while "" == chunk_len: + chunk_len = self.stream.readline().strip() + return int(chunk_len, 16) + except ValueError: + return 0 + + def read(self, size): + data = b('') + chunk_size = self.chunk_size + while size: + if not chunk_size: + chunk_size = self._read_header() + + if size < chunk_size: + data += self.stream.read(size) + chunk_size -= size + break + else: + if not chunk_size: + break + data += self.stream.read(chunk_size) + size -= chunk_size + chunk_size = 0 + + self.chunk_size = chunk_size + return data + + def readline(self): + data = b('') + c = self.read(1) + while c and c != b('\n'): + data += c + c = self.read(1) + data += c + return data + + def readlines(self): + yield self.readline() + +def get_method(method): + + methods = dict(wsgi=WSGIWorker) + return methods[method.lower()] + +# Monolithic build...end of module: rocket\worker.py +# Monolithic build...start of module: rocket\methods\__init__.py + +# Monolithic build...end of module: rocket\methods\__init__.py +# Monolithic build...start of module: rocket\methods\wsgi.py + +# Import System Modules +import sys +import socket +#from wsgiref.headers import Headers +#from wsgiref.util import FileWrapper +# Import Package Modules +# package imports removed in monolithic build + + +if PY3K: + from email.utils import formatdate +else: + # Caps Utils for Py2.4 compatibility + from email.Utils import formatdate + +# Define Constants +NEWLINE = b('\r\n') +HEADER_RESPONSE = '''HTTP/1.1 %s\r\n%s''' +BASE_ENV = {'SERVER_NAME': SERVER_NAME, + 'SCRIPT_NAME': '', # Direct call WSGI does not need a name + 'wsgi.errors': sys.stderr, + 'wsgi.version': (1, 0), + 'wsgi.multiprocess': False, + 'wsgi.run_once': False, + 'wsgi.file_wrapper': FileWrapper + } + +class WSGIWorker(Worker): + def __init__(self, *args, **kwargs): + """Builds some instance variables that will last the life of the + thread.""" + Worker.__init__(self, *args, **kwargs) + + if isinstance(self.app_info, dict): + multithreaded = self.app_info.get('max_threads') != 1 + else: + multithreaded = False + self.base_environ = dict({'SERVER_SOFTWARE': self.app_info['server_software'], + 'wsgi.multithread': multithreaded, + }) + self.base_environ.update(BASE_ENV) + # Grab our application + self.app = self.app_info.get('wsgi_app') + + if not hasattr(self.app, "__call__"): + raise TypeError("The wsgi_app specified (%s) is not a valid WSGI application." % repr(self.app)) + + + def build_environ(self, sock_file, conn): + """ Build the execution environment. """ + # Grab the request line + request = self.read_request_line(sock_file) + + # Copy the Base Environment + environ = self.base_environ.copy() + + # Grab the headers + for k, v in self.read_headers(sock_file).items(): + environ[str('HTTP_'+k)] = v + + # Add CGI Variables + environ['REQUEST_METHOD'] = request['method'] + environ['PATH_INFO'] = request['path'] + environ['SERVER_PROTOCOL'] = request['protocol'] + environ['SERVER_PORT'] = str(conn.server_port) + environ['REMOTE_PORT'] = str(conn.client_port) + environ['REMOTE_ADDR'] = str(conn.client_addr) + environ['QUERY_STRING'] = request['query_string'] + if 'HTTP_CONTENT_LENGTH' in environ: + environ['CONTENT_LENGTH'] = environ['HTTP_CONTENT_LENGTH'] + if 'HTTP_CONTENT_TYPE' in environ: + environ['CONTENT_TYPE'] = environ['HTTP_CONTENT_TYPE'] + + # Save the request method for later + self.request_method = environ['REQUEST_METHOD'] + + # Add Dynamic WSGI Variables + if conn.ssl: + environ['wsgi.url_scheme'] = 'https' + environ['HTTPS'] = 'on' + else: + environ['wsgi.url_scheme'] = 'http' + + if environ.get('HTTP_TRANSFER_ENCODING', '') == 'chunked': + environ['wsgi.input'] = ChunkedReader(sock_file) + else: + environ['wsgi.input'] = sock_file + + return environ + + def send_headers(self, data, sections): + h_set = self.header_set + + # Does the app want us to send output chunked? + self.chunked = h_set.get('transfer-encoding', '').lower() == 'chunked' + + # Add a Date header if it's not there already + if not 'date' in h_set: + h_set['Date'] = formatdate(usegmt=True) + + # Add a Server header if it's not there already + if not 'server' in h_set: + h_set['Server'] = HTTP_SERVER_SOFTWARE + + if 'content-length' in h_set: + self.size = int(h_set['content-length']) + else: + s = int(self.status.split(' ')[0]) + if s < 200 or s not in (204, 205, 304): + if not self.chunked: + if sections == 1: + # Add a Content-Length header if it's not there already + h_set['Content-Length'] = str(len(data)) + self.size = len(data) + else: + # If they sent us more than one section, we blow chunks + h_set['Transfer-Encoding'] = 'Chunked' + self.chunked = True + if __debug__: + self.err_log.debug('Adding header...' + 'Transfer-Encoding: Chunked') + + if 'connection' not in h_set: + # If the application did not provide a connection header, fill it in + client_conn = self.environ.get('HTTP_CONNECTION', '').lower() + if self.environ['SERVER_PROTOCOL'] == 'HTTP/1.1': + # HTTP = 1.1 defaults to keep-alive connections + if client_conn: + h_set['Connection'] = client_conn + else: + h_set['Connection'] = 'keep-alive' + else: + # HTTP < 1.1 supports keep-alive but it's quirky so we don't support it + h_set['Connection'] = 'close' + + # Close our connection if we need to. + self.closeConnection = h_set.get('connection', '').lower() == 'close' + + # Build our output headers + header_data = HEADER_RESPONSE % (self.status, str(h_set)) + + # Send the headers + if __debug__: + self.err_log.debug('Sending Headers: %s' % repr(header_data)) + self.conn.sendall(b(header_data)) + self.headers_sent = True + + def write_warning(self, data, sections=None): + self.err_log.warning('WSGI app called write method directly. This is ' + 'deprecated behavior. Please update your app.') + return self.write(data, sections) + + def write(self, data, sections=None): + """ Write the data to the output socket. """ + + if self.error[0]: + self.status = self.error[0] + data = b(self.error[1]) + + if not self.headers_sent: + self.send_headers(data, sections) + + if self.request_method != 'HEAD': + try: + if self.chunked: + self.conn.sendall(b('%x\r\n%s\r\n' % (len(data), data))) + else: + self.conn.sendall(data) + except socket.error: + # But some clients will close the connection before that + # resulting in a socket error. + self.closeConnection = True + + def start_response(self, status, response_headers, exc_info=None): + """ Store the HTTP status and headers to be sent when self.write is + called. """ + if exc_info: + try: + if self.headers_sent: + # Re-raise original exception if headers sent + # because this violates WSGI specification. + raise + finally: + exc_info = None + elif self.header_set: + raise AssertionError("Headers already set!") + + if PY3K and not isinstance(status, str): + self.status = str(status, 'ISO-8859-1') + else: + self.status = status + # Make sure headers are bytes objects + try: + self.header_set = Headers(response_headers) + except UnicodeDecodeError: + self.error = ('500 Internal Server Error', + 'HTTP Headers should be bytes') + self.err_log.error('Received HTTP Headers from client that contain' + ' invalid characters for Latin-1 encoding.') + + return self.write_warning + + def run_app(self, conn): + self.size = 0 + self.header_set = Headers([]) + self.headers_sent = False + self.error = (None, None) + self.chunked = False + sections = None + output = None + + if __debug__: + self.err_log.debug('Getting sock_file') + + # Build our file-like object + sock_file = conn.makefile('rb',BUF_SIZE) + + try: + # Read the headers and build our WSGI environment + self.environ = environ = self.build_environ(sock_file, conn) + + # Handle 100 Continue + if environ.get('HTTP_EXPECT', '') == '100-continue': + res = environ['SERVER_PROTOCOL'] + ' 100 Continue\r\n\r\n' + conn.sendall(b(res)) + + # Send it to our WSGI application + output = self.app(environ, self.start_response) + + if not hasattr(output, '__len__') and not hasattr(output, '__iter__'): + self.error = ('500 Internal Server Error', + 'WSGI applications must return a list or ' + 'generator type.') + + if hasattr(output, '__len__'): + sections = len(output) + + for data in output: + # Don't send headers until body appears + if data: + self.write(data, sections) + + if self.chunked: + # If chunked, send our final chunk length + self.conn.sendall(b('0\r\n\r\n')) + elif not self.headers_sent: + # Send headers if the body was empty + self.send_headers('', sections) + + # Don't capture exceptions here. The Worker class handles + # them appropriately. + finally: + if __debug__: + self.err_log.debug('Finally closing output and sock_file') + + if hasattr(output,'close'): + output.close() + + sock_file.close() + +# Monolithic build...end of module: rocket\methods\wsgi.py + +# +# the following code is not part of Rocket but was added in web2py for testing purposes +# + +def demo_app(environ, start_response): + global static_folder + import os + types = {'htm': 'text/html','html': 'text/html','gif': 'image/gif', + 'jpg': 'image/jpeg','png': 'image/png','pdf': 'applications/pdf'} + if static_folder: + if not static_folder.startswith('/'): + static_folder = os.path.join(os.getcwd(),static_folder) + path = os.path.join(static_folder, environ['PATH_INFO'][1:] or 'index.html') + type = types.get(path.split('.')[-1],'text') + if os.path.exists(path): + try: + pathfile = open(path,'rb') + try: + data = pathfile.read() + finally: + pathfile.close() + start_response('200 OK', [('Content-Type', type)]) + except IOError: + start_response('404 NOT FOUND', []) + data = '404 NOT FOUND' + else: + start_response('500 INTERNAL SERVER ERROR', []) + data = '500 INTERNAL SERVER ERROR' + else: + start_response('200 OK', [('Content-Type', 'text/html')]) + data = '<html><body><h1>Hello from Rocket Web Server</h1></body></html>' + return [data] + +def demo(): + from optparse import OptionParser + parser = OptionParser() + parser.add_option("-i", "--ip", dest="ip",default="127.0.0.1", + help="ip address of the network interface") + parser.add_option("-p", "--port", dest="port",default="8000", + help="post where to run web server") + parser.add_option("-s", "--static", dest="static",default=None, + help="folder containing static files") + (options, args) = parser.parse_args() + global static_folder + static_folder = options.static + print 'Rocket running on %s:%s' % (options.ip, options.port) + r=Rocket((options.ip,int(options.port)),'wsgi', {'wsgi_app':demo_app}) + r.start() + +if __name__=='__main__': + demo() + ADDED gluon/sanitizer.py Index: gluon/sanitizer.py ================================================================== --- gluon/sanitizer.py +++ gluon/sanitizer.py @@ -0,0 +1,225 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +:: + + # from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/496942 + # Title: Cross-site scripting (XSS) defense + # Submitter: Josh Goldfoot (other recipes) + # Last Updated: 2006/08/05 + # Version no: 1.0 + +""" + + +from htmllib import HTMLParser +from cgi import escape +from urlparse import urlparse +from formatter import AbstractFormatter +from htmlentitydefs import entitydefs +from xml.sax.saxutils import quoteattr + +__all__ = ['sanitize'] + + +def xssescape(text): + """Gets rid of < and > and & and, for good measure, :""" + + return escape(text, quote=True).replace(':', ':') + + +class XssCleaner(HTMLParser): + + def __init__( + self, + 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']}, + fmt=AbstractFormatter, + strip_disallowed = False + ): + + HTMLParser.__init__(self, fmt) + self.result = '' + self.open_tags = [] + self.permitted_tags = [i for i in permitted_tags if i[-1] != '/'] + self.requires_no_close = [i[:-1] for i in permitted_tags + if i[-1] == '/'] + self.permitted_tags += self.requires_no_close + self.allowed_attributes = allowed_attributes + + # The only schemes allowed in URLs (for href and src attributes). + # Adding "javascript" or "vbscript" to this list would not be smart. + + self.allowed_schemes = ['http', 'https', 'ftp'] + + #to strip or escape disallowed tags? + self.strip_disallowed = strip_disallowed + self.in_disallowed = False + + def handle_data(self, data): + if data and not self.in_disallowed: + self.result += xssescape(data) + + def handle_charref(self, ref): + if self.in_disallowed: + return + elif len(ref) < 7 and ref.isdigit(): + self.result += '&#%s;' % ref + else: + self.result += xssescape('&#%s' % ref) + + def handle_entityref(self, ref): + if self.in_disallowed: + return + elif ref in entitydefs: + self.result += '&%s;' % ref + else: + self.result += xssescape('&%s' % ref) + + def handle_comment(self, comment): + if self.in_disallowed: + return + elif comment: + self.result += xssescape('<!--%s-->' % comment) + + def handle_starttag( + self, + tag, + method, + attrs, + ): + if tag not in self.permitted_tags: + if self.strip_disallowed: + self.in_disallowed = True + else: + self.result += xssescape('<%s>' % tag) + else: + bt = '<' + tag + if tag in self.allowed_attributes: + attrs = dict(attrs) + self.allowed_attributes_here = [x for x in + self.allowed_attributes[tag] if x in attrs + and len(attrs[x]) > 0] + for attribute in self.allowed_attributes_here: + if attribute in ['href', 'src', 'background']: + if self.url_is_acceptable(attrs[attribute]): + bt += ' %s="%s"' % (attribute, + attrs[attribute]) + else: + bt += ' %s=%s' % (xssescape(attribute), + quoteattr(attrs[attribute])) + if bt == '<a' or bt == '<img': + return + if tag in self.requires_no_close: + bt += ' /' + bt += '>' + self.result += bt + self.open_tags.insert(0, tag) + + def handle_endtag(self, tag, attrs): + bracketed = '</%s>' % tag + if tag not in self.permitted_tags: + if self.strip_disallowed: + self.in_disallowed = False + else: + self.result += xssescape(bracketed) + elif tag in self.open_tags: + self.result += bracketed + self.open_tags.remove(tag) + + def unknown_starttag(self, tag, attributes): + self.handle_starttag(tag, None, attributes) + + def unknown_endtag(self, tag): + self.handle_endtag(tag, None) + + def url_is_acceptable(self, url): + """ + Accepts relative and absolute urls + """ + + parsed = urlparse(url) + return (parsed[0] in self.allowed_schemes and '.' in parsed[1]) \ + or (parsed[0] == '' and parsed[2].startswith('/')) + + def strip(self, rawstring, escape=True): + """ + Returns the argument stripped of potentially harmful + HTML or Javascript code + + @type escape: boolean + @param escape: If True (default) it escapes the potentially harmful + content, otherwise remove it + """ + + if not isinstance(rawstring, str): return str(rawstring) + for tag in self.requires_no_close: + rawstring = rawstring.replace("<%s/>" % tag, "<%s />" % tag) + if not escape: + self.strip_disallowed = True + self.result = '' + self.feed(rawstring) + for endtag in self.open_tags: + if endtag not in self.requires_no_close: + self.result += '</%s>' % endtag + return self.result + + def xtags(self): + """ + Returns a printable string informing the user which tags are allowed + """ + + tg = '' + for x in sorted(self.permitted_tags): + tg += '<' + x + if x in self.allowed_attributes: + for y in self.allowed_attributes[x]: + tg += ' %s=""' % y + tg += '> ' + return xssescape(tg.strip()) + + +def sanitize(text, permitted_tags=[ + 'a', + 'b', + 'blockquote', + 'br/', + 'i', + 'li', + 'ol', + 'ul', + 'p', + 'cite', + 'code', + 'pre', + 'img/', + 'h1','h2','h3','h4','h5','h6', + 'table','tr','td','div', + ], + allowed_attributes = { + 'a': ['href', 'title'], + 'img': ['src', 'alt'], + 'blockquote': ['type'], + 'td': ['colspan'], + }, + escape=True): + if not isinstance(text, str): return str(text) + return XssCleaner(permitted_tags=permitted_tags, + allowed_attributes=allowed_attributes).strip(text, escape) + ADDED gluon/serializers.py Index: gluon/serializers.py ================================================================== --- gluon/serializers.py +++ gluon/serializers.py @@ -0,0 +1,78 @@ +""" +This file is part of the web2py Web Framework +Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> +License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) +""" +import datetime +from storage import Storage +from html import TAG +from html import xmlescape +from languages import lazyT +import contrib.simplejson as simplejson +import contrib.rss2 as rss2 + +def custom_json(o): + if hasattr(o,'custom_json') and callable(o.custom_json): + return o.custom_json() + if isinstance(o, (datetime.date, + datetime.datetime, + datetime.time)): + return o.isoformat()[:19].replace('T',' ') + elif isinstance(o, (int, long)): + return int(o) + elif isinstance(o, lazyT): + return str(o) + elif hasattr(o,'as_list') and callable(o.as_list): + return o.as_list() + elif hasattr(o,'as_dict') and callable(o.as_dict): + return o.as_dict() + else: + raise TypeError(repr(o) + " is not JSON serializable") + + +def xml_rec(value, key): + if hasattr(value,'custom_xml') and callable(value.custom_xml): + return value.custom_xml() + elif isinstance(value, (dict, Storage)): + return TAG[key](*[TAG[k](xml_rec(v, '')) for k, v in value.items()]) + elif isinstance(value, list): + return TAG[key](*[TAG.item(xml_rec(item, '')) for item in value]) + elif hasattr(value,'as_list') and callable(value.as_list): + return str(xml_rec(value.as_list(),'')) + elif hasattr(value,'as_dict') and callable(value.as_dict): + return str(xml_rec(value.as_dict(),'')) + else: + return xmlescape(value) + + +def xml(value, encoding='UTF-8', key='document'): + return ('<?xml version="1.0" encoding="%s"?>' % encoding) + str(xml_rec(value,key)) + + +def json(value,default=custom_json): + return simplejson.dumps(value,default=default) + + +def csv(value): + return '' + + +def rss(feed): + if not 'entries' in feed and 'items' in feed: + feed['entries'] = feed['items'] + now=datetime.datetime.now() + rss = rss2.RSS2(title = feed['title'], + link = str(feed['link']), + description = feed['description'], + lastBuildDate = feed.get('created_on', now), + items = [rss2.RSSItem(\ + title=entry['title'], + link=str(entry['link']), + description=entry['description'], + pubDate=entry.get('created_on', now) + )\ + for entry in feed['entries'] + ] + ) + return rss2.dumps(rss) + ADDED gluon/settings.py Index: gluon/settings.py ================================================================== --- gluon/settings.py +++ gluon/settings.py @@ -0,0 +1,11 @@ +""" +This file is part of the web2py Web Framework +Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> +License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) +""" + +from storage import Storage + +global_settings = Storage() +settings = global_settings # legacy compatibility + ADDED gluon/shell.py Index: gluon/shell.py ================================================================== --- gluon/shell.py +++ gluon/shell.py @@ -0,0 +1,400 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +This file is part of the web2py Web Framework +Developed by Massimo Di Pierro <mdipierro@cs.depaul.edu>, +limodou <limodou@gmail.com> and srackham <srackham@gmail.com>. +License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) + +""" + +import os +import sys +import code +import logging +import types +import re +import optparse +import glob + +import fileutils +import settings +from utils import web2py_uuid +from compileapp import build_environment, read_pyc, run_models_in +from restricted import RestrictedError +from globals import Request, Response, Session +from storage import Storage +from admin import w2p_unpack + + +logger = logging.getLogger("web2py") + +def exec_environment( + pyfile='', + request=None, + response=None, + session=None, + ): + """ + .. function:: gluon.shell.exec_environment([pyfile=''[, request=Request() + [, response=Response[, session=Session()]]]]) + + Environment builder and module loader. + + + Builds a web2py environment and optionally executes a Python + file into the environment. + A Storage dictionary containing the resulting environment is returned. + The working directory must be web2py root -- this is the web2py default. + + """ + + if request==None: request = Request() + if response==None: response = Response() + if session==None: session = Session() + + if request.folder is None: + mo = re.match(r'(|.*/)applications/(?P<appname>[^/]+)', pyfile) + if mo: + appname = mo.group('appname') + request.folder = os.path.join('applications', appname) + else: + request.folder = '' + env = build_environment(request, response, session, store_current=False) + if pyfile: + pycfile = pyfile + 'c' + if os.path.isfile(pycfile): + exec read_pyc(pycfile) in env + else: + execfile(pyfile, env) + return Storage(env) + + +def env( + a, + import_models=False, + c=None, + f=None, + dir='', + extra_request={}, + ): + """ + Return web2py execution environment for application (a), controller (c), + function (f). + If import_models is True the exec all application models into the + environment. + + extra_request allows you to pass along any extra + variables to the request object before your models + get executed. This was mainly done to support + web2py_utils.test_runner, however you can use it + with any wrapper scripts that need access to the + web2py environment. + """ + + request = Request() + response = Response() + session = Session() + request.application = a + + # Populate the dummy environment with sensible defaults. + + if not dir: + request.folder = os.path.join('applications', a) + else: + request.folder = dir + request.controller = c or 'default' + request.function = f or 'index' + response.view = '%s/%s.html' % (request.controller, + request.function) + request.env.path_info = '/%s/%s/%s' % (a, c, f) + request.env.http_host = '127.0.0.1:8000' + request.env.remote_addr = '127.0.0.1' + request.env.web2py_runtime_gae = settings.global_settings.web2py_runtime_gae + + for k,v in extra_request.items(): + request[k] = v + + # Monkey patch so credentials checks pass. + + def check_credentials(request, other_application='admin'): + return True + + fileutils.check_credentials = check_credentials + + environment = build_environment(request, response, session) + + if import_models: + try: + run_models_in(environment) + except RestrictedError, e: + sys.stderr.write(e.traceback+'\n') + sys.exit(1) + + environment['__name__'] = '__main__' + return environment + + +def exec_pythonrc(): + pythonrc = os.environ.get('PYTHONSTARTUP') + if pythonrc and os.path.isfile(pythonrc): + try: + execfile(pythonrc) + except NameError: + pass + + +def run( + appname, + plain=False, + import_models=False, + startfile=None, + bpython=False + ): + """ + Start interactive shell or run Python script (startfile) in web2py + controller environment. appname is formatted like: + + a web2py application name + a/c exec the controller c into the application environment + """ + + (a, c, f) = parse_path_info(appname) + errmsg = 'invalid application name: %s' % appname + if not a: + die(errmsg) + adir = os.path.join('applications', a) + if not os.path.exists(adir): + if raw_input('application %s does not exist, create (y/n)?' + % a).lower() in ['y', 'yes']: + os.mkdir(adir) + w2p_unpack('welcome.w2p', adir) + for subfolder in ['models','views','controllers', 'databases', + 'modules','cron','errors','sessions', + 'languages','static','private','uploads']: + subpath = os.path.join(adir,subfolder) + if not os.path.exists(subpath): + os.mkdir(subpath) + db = os.path.join(adir,'models/db.py') + if os.path.exists(db): + data = fileutils.read_file(db) + data = data.replace('<your secret key>','sha512:'+web2py_uuid()) + fileutils.write_file(db, data) + + if c: + import_models = True + _env = env(a, c=c, import_models=import_models) + if c: + cfile = os.path.join('applications', a, 'controllers', c + '.py') + if not os.path.isfile(cfile): + cfile = os.path.join('applications', a, 'compiled', "controllers_%s_%s.pyc" % (c,f)) + if not os.path.isfile(cfile): + die(errmsg) + else: + exec read_pyc(cfile) in _env + else: + execfile(cfile, _env) + + if f: + exec ('print %s()' % f, _env) + elif startfile: + exec_pythonrc() + try: + execfile(startfile, _env) + except RestrictedError, e: + print e.traceback + else: + if not plain: + if bpython: + try: + import bpython + bpython.embed(locals_=_env) + return + except: + logger.warning( + 'import bpython error; trying ipython...') + else: + try: + import IPython + # following 2 lines fix a problem with IPython; thanks Michael Toomim + if '__builtins__' in _env: + del _env['__builtins__'] + shell = IPython.Shell.IPShell(argv=[], user_ns=_env) + shell.mainloop() + return + except: + logger.warning( + 'import IPython error; use default python shell') + try: + import readline + import rlcompleter + except ImportError: + pass + else: + readline.set_completer(rlcompleter.Completer(_env).complete) + readline.parse_and_bind('tab:complete') + exec_pythonrc() + code.interact(local=_env) + + +def parse_path_info(path_info): + """ + Parse path info formatted like a/c/f where c and f are optional + and a leading / accepted. + Return tuple (a, c, f). If invalid path_info a is set to None. + If c or f are omitted they are set to None. + """ + + mo = re.match(r'^/?(?P<a>\w+)(/(?P<c>\w+)(/(?P<f>\w+))?)?$', + path_info) + if mo: + return (mo.group('a'), mo.group('c'), mo.group('f')) + else: + return (None, None, None) + + +def die(msg): + print >> sys.stderr, msg + sys.exit(1) + + +def test(testpath, import_models=True, verbose=False): + """ + Run doctests in web2py environment. testpath is formatted like: + + a tests all controllers in application a + a/c tests controller c in application a + a/c/f test function f in controller c, application a + + Where a, c and f are application, controller and function names + respectively. If the testpath is a file name the file is tested. + If a controller is specified models are executed by default. + """ + + import doctest + if os.path.isfile(testpath): + mo = re.match(r'(|.*/)applications/(?P<a>[^/]+)', testpath) + if not mo: + die('test file is not in application directory: %s' + % testpath) + a = mo.group('a') + c = f = None + files = [testpath] + else: + (a, c, f) = parse_path_info(testpath) + errmsg = 'invalid test path: %s' % testpath + if not a: + die(errmsg) + cdir = os.path.join('applications', a, 'controllers') + if not os.path.isdir(cdir): + die(errmsg) + if c: + cfile = os.path.join(cdir, c + '.py') + if not os.path.isfile(cfile): + die(errmsg) + files = [cfile] + else: + files = glob.glob(os.path.join(cdir, '*.py')) + for testfile in files: + globs = env(a, import_models) + ignores = globs.keys() + execfile(testfile, globs) + + def doctest_object(name, obj): + """doctest obj and enclosed methods and classes.""" + + if type(obj) in (types.FunctionType, types.TypeType, + types.ClassType, types.MethodType, + types.UnboundMethodType): + + # Reload environment before each test. + + globs = env(a, c=c, f=f, import_models=import_models) + execfile(testfile, globs) + doctest.run_docstring_examples(obj, globs=globs, + name='%s: %s' % (os.path.basename(testfile), + name), verbose=verbose) + if type(obj) in (types.TypeType, types.ClassType): + for attr_name in dir(obj): + + # Execute . operator so decorators are executed. + + o = eval('%s.%s' % (name, attr_name), globs) + doctest_object(attr_name, o) + + for (name, obj) in globs.items(): + if name not in ignores and (f is None or f == name): + doctest_object(name, obj) + + +def get_usage(): + usage = """ + %prog [options] pythonfile +""" + return usage + + +def execute_from_command_line(argv=None): + if argv is None: + argv = sys.argv + + parser = optparse.OptionParser(usage=get_usage()) + + parser.add_option('-S', '--shell', dest='shell', metavar='APPNAME', + help='run web2py in interactive shell or IPython(if installed) ' + \ + 'with specified appname') + msg = 'run web2py in interactive shell or bpython (if installed) with' + msg += ' specified appname (if app does not exist it will be created).' + msg += '\n Use combined with --shell' + parser.add_option( + '-B', + '--bpython', + action='store_true', + default=False, + dest='bpython', + help=msg, + ) + parser.add_option( + '-P', + '--plain', + action='store_true', + default=False, + dest='plain', + help='only use plain python shell, should be used with --shell option', + ) + parser.add_option( + '-M', + '--import_models', + action='store_true', + default=False, + dest='import_models', + help='auto import model files, default is False, ' + \ + ' should be used with --shell option', + ) + parser.add_option( + '-R', + '--run', + dest='run', + metavar='PYTHON_FILE', + default='', + help='run PYTHON_FILE in web2py environment, ' + \ + 'should be used with --shell option', + ) + + (options, args) = parser.parse_args(argv[1:]) + + if len(sys.argv) == 1: + parser.print_help() + sys.exit(0) + + if len(args) > 0: + startfile = args[0] + else: + startfile = '' + run(options.shell, options.plain, startfile=startfile, bpython=options.bpython) + + +if __name__ == '__main__': + execute_from_command_line() + ADDED gluon/sql.py Index: gluon/sql.py ================================================================== --- gluon/sql.py +++ gluon/sql.py @@ -0,0 +1,6 @@ +# this file exists for backward compatibility + +__all__ = ['DAL','Field','drivers'] + +from dal import DAL, Field, Table, Query, Set, Expression, Row, Rows, drivers, BaseAdapter, SQLField, SQLTable, SQLXorable, SQLQuery, SQLSet, SQLRows, SQLStorage, SQLDB, GQLDB, SQLALL, SQLCustomType + ADDED gluon/sqlhtml.py Index: gluon/sqlhtml.py ================================================================== --- gluon/sqlhtml.py +++ gluon/sqlhtml.py @@ -0,0 +1,1530 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +This file is part of the web2py Web Framework +Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> +License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) + +Holds: + +- SQLFORM: provide a form for a table (with/without record) +- SQLTABLE: provides a table for a set of records +- form_factory: provides a SQLFORM for an non-db backed table + +""" + +from http import HTTP +from html import XML, SPAN, TAG, A, DIV, UL, LI, TEXTAREA, BR, IMG, SCRIPT +from html import FORM, INPUT, LABEL, OPTION, SELECT +from html import TABLE, THEAD, TBODY, TR, TD, TH +from html import URL as Url +from dal import DAL, Table, Row, CALLABLETYPES +from storage import Storage +from utils import md5_hash +from validators import IS_EMPTY_OR + +import urllib +import re +import cStringIO + + +table_field = re.compile('[\w_]+\.[\w_]+') +widget_class = re.compile('^\w*') + +def represent(field,value,record): + f = field.represent + if not callable(f): + return str(value) + n = f.func_code.co_argcount-len(f.func_defaults or []) + if n==1: + return f(value) + elif n==2: + return f(value,record) + else: + raise RuntimeError, "field representation must take 1 or 2 args" + +def safe_int(x): + try: + return int(x) + except ValueError: + return 0 + +def safe_float(x): + try: + return float(x) + except ValueError: + return 0 + +class FormWidget(object): + """ + helper for SQLFORM to generate form input fields (widget), + related to the fieldtype + """ + + @staticmethod + def _attributes(field, widget_attributes, **attributes): + """ + helper to build a common set of attributes + + :param field: the field involved, some attributes are derived from this + :param widget_attributes: widget related attributes + :param attributes: any other supplied attributes + """ + attr = dict( + _id = '%s_%s' % (field._tablename, field.name), + _class = widget_class.match(str(field.type)).group(), + _name = field.name, + requires = field.requires, + ) + attr.update(widget_attributes) + attr.update(attributes) + return attr + + @staticmethod + def widget(field, value, **attributes): + """ + generates the widget for the field. + + When serialized, will provide an INPUT tag: + + - id = tablename_fieldname + - class = field.type + - name = fieldname + + :param field: the field needing the widget + :param value: value + :param attributes: any other attributes to be applied + """ + + raise NotImplementedError + +class StringWidget(FormWidget): + + @staticmethod + def widget(field, value, **attributes): + """ + generates an INPUT text tag. + + see also: :meth:`FormWidget.widget` + """ + + default = dict( + _type = 'text', + value = (value!=None and str(value)) or '', + ) + attr = StringWidget._attributes(field, default, **attributes) + + return INPUT(**attr) + + +class IntegerWidget(StringWidget): + + pass + + +class DoubleWidget(StringWidget): + + pass + + +class DecimalWidget(StringWidget): + + pass + + +class TimeWidget(StringWidget): + + pass + + +class DateWidget(StringWidget): + + pass + + +class DatetimeWidget(StringWidget): + + pass + + +class TextWidget(FormWidget): + + @staticmethod + def widget(field, value, **attributes): + """ + generates a TEXTAREA tag. + + see also: :meth:`FormWidget.widget` + """ + + default = dict( + value = value, + ) + attr = TextWidget._attributes(field, default, **attributes) + + return TEXTAREA(**attr) + + +class BooleanWidget(FormWidget): + + @staticmethod + def widget(field, value, **attributes): + """ + generates an INPUT checkbox tag. + + see also: :meth:`FormWidget.widget` + """ + + default=dict( + _type='checkbox', + value=value, + ) + attr = BooleanWidget._attributes(field, default, **attributes) + + return INPUT(**attr) + + +class OptionsWidget(FormWidget): + + @staticmethod + def has_options(field): + """ + checks if the field has selectable options + + :param field: the field needing checking + :returns: True if the field has options + """ + + return hasattr(field.requires, 'options') + + @staticmethod + def widget(field, value, **attributes): + """ + generates a SELECT tag, including OPTIONs (only 1 option allowed) + + see also: :meth:`FormWidget.widget` + """ + default = dict( + value=value, + ) + attr = OptionsWidget._attributes(field, default, **attributes) + + requires = field.requires + if not isinstance(requires, (list, tuple)): + requires = [requires] + if requires: + if hasattr(requires[0], 'options'): + options = requires[0].options() + else: + raise SyntaxError, 'widget cannot determine options of %s' \ + % field + opts = [OPTION(v, _value=k) for (k, v) in options] + + return SELECT(*opts, **attr) + +class ListWidget(StringWidget): + @staticmethod + def widget(field,value,**attributes): + _id = '%s_%s' % (field._tablename, field.name) + _name = field.name + if field.type=='list:integer': _class = 'integer' + else: _class = 'string' + items=[LI(INPUT(_id=_id,_class=_class,_name=_name,value=v,hideerror=True)) \ + for v in value or ['']] + script=SCRIPT(""" +// from http://refactormycode.com/codes/694-expanding-input-list-using-jquery +(function(){ +jQuery.fn.grow_input = function() { + return this.each(function() { + var ul = this; + jQuery(ul).find(":text").after('<a href="javascript:void(0)>+</a>').keypress(function (e) { return (e.which == 13) ? pe(ul) : true; }).next().click(function(){ pe(ul) }); + }); +}; +function pe(ul) { + var new_line = ml(ul); + rel(ul); + new_line.appendTo(ul); + new_line.find(":text").focus(); + return false; +} +function ml(ul) { + var line = jQuery(ul).find("li:first").clone(true); + line.find(':text').val(''); + return line; +} +function rel(ul) { + jQuery(ul).find("li").each(function() { + var trimmed = jQuery.trim(jQuery(this.firstChild).val()); + if (trimmed=='') jQuery(this).remove(); else jQuery(this.firstChild).val(trimmed); + }); +} +})(); +jQuery(document).ready(function(){jQuery('#%s_grow_input').grow_input();}); +""" % _id) + attributes['_id']=_id+'_grow_input' + return TAG[''](UL(*items,**attributes),script) + + +class MultipleOptionsWidget(OptionsWidget): + + @staticmethod + def widget(field, value, size=5, **attributes): + """ + generates a SELECT tag, including OPTIONs (multiple options allowed) + + see also: :meth:`FormWidget.widget` + + :param size: optional param (default=5) to indicate how many rows must + be shown + """ + + attributes.update(dict(_size=size, _multiple=True)) + + return OptionsWidget.widget(field, value, **attributes) + + +class RadioWidget(OptionsWidget): + + @staticmethod + def widget(field, value, **attributes): + """ + generates a TABLE tag, including INPUT radios (only 1 option allowed) + + see also: :meth:`FormWidget.widget` + """ + + attr = OptionsWidget._attributes(field, {}, **attributes) + + requires = field.requires + if not isinstance(requires, (list, tuple)): + requires = [requires] + if requires: + if hasattr(requires[0], 'options'): + options = requires[0].options() + else: + raise SyntaxError, 'widget cannot determine options of %s' \ + % field + + options = [(k, v) for k, v in options if str(v)] + opts = [] + cols = attributes.get('cols',1) + totals = len(options) + mods = totals%cols + rows = totals/cols + if mods: + rows += 1 + + for r_index in range(rows): + tds = [] + for k, v in options[r_index*cols:(r_index+1)*cols]: + tds.append(TD(INPUT(_type='radio', _name=field.name, + requires=attr.get('requires',None), + hideerror=True, _value=k, + value=value), v)) + opts.append(TR(tds)) + + if opts: + opts[-1][0][0]['hideerror'] = False + return TABLE(*opts, **attr) + + +class CheckboxesWidget(OptionsWidget): + + @staticmethod + def widget(field, value, **attributes): + """ + generates a TABLE tag, including INPUT checkboxes (multiple allowed) + + see also: :meth:`FormWidget.widget` + """ + + # was values = re.compile('[\w\-:]+').findall(str(value)) + if isinstance(value, (list, tuple)): + values = [str(v) for v in value] + else: + values = [str(value)] + + attr = OptionsWidget._attributes(field, {}, **attributes) + + requires = field.requires + if not isinstance(requires, (list, tuple)): + requires = [requires] + if requires: + if hasattr(requires[0], 'options'): + options = requires[0].options() + else: + raise SyntaxError, 'widget cannot determine options of %s' \ + % field + + options = [(k, v) for k, v in options if k != ''] + opts = [] + cols = attributes.get('cols', 1) + totals = len(options) + mods = totals % cols + rows = totals / cols + if mods: + rows += 1 + + for r_index in range(rows): + tds = [] + for k, v in options[r_index*cols:(r_index+1)*cols]: + if k in values: + r_value = k + else: + r_value = [] + tds.append(TD(INPUT(_type='checkbox', _name=field.name, + requires=attr.get('requires', None), + hideerror=True, _value=k, + value=r_value), v)) + opts.append(TR(tds)) + + if opts: + opts[-1][0][0]['hideerror'] = False + return TABLE(*opts, **attr) + + +class PasswordWidget(FormWidget): + + DEFAULT_PASSWORD_DISPLAY = 8*('*') + + @staticmethod + def widget(field, value, **attributes): + """ + generates a INPUT password tag. + If a value is present it will be shown as a number of '*', not related + to the length of the actual value. + + see also: :meth:`FormWidget.widget` + """ + + default=dict( + _type='password', + _value=(value and PasswordWidget.DEFAULT_PASSWORD_DISPLAY) or '', + ) + attr = PasswordWidget._attributes(field, default, **attributes) + + return INPUT(**attr) + + +class UploadWidget(FormWidget): + + DEFAULT_WIDTH = '150px' + ID_DELETE_SUFFIX = '__delete' + GENERIC_DESCRIPTION = 'file' + DELETE_FILE = 'delete' + + @staticmethod + def widget(field, value, download_url=None, **attributes): + """ + generates a INPUT file tag. + + Optionally provides an A link to the file, including a checkbox so + the file can be deleted. + All is wrapped in a DIV. + + see also: :meth:`FormWidget.widget` + + :param download_url: Optional URL to link to the file (default = None) + """ + + default=dict( + _type='file', + ) + attr = UploadWidget._attributes(field, default, **attributes) + + inp = INPUT(**attr) + + if download_url and value: + url = download_url + '/' + value + (br, image) = ('', '') + if UploadWidget.is_image(value): + br = BR() + image = IMG(_src = url, _width = UploadWidget.DEFAULT_WIDTH) + + requires = attr["requires"] + if requires == [] or isinstance(requires, IS_EMPTY_OR): + inp = DIV(inp, '[', + A(UploadWidget.GENERIC_DESCRIPTION, _href = url), + '|', + INPUT(_type='checkbox', + _name=field.name + UploadWidget.ID_DELETE_SUFFIX), + UploadWidget.DELETE_FILE, + ']', br, image) + else: + inp = DIV(inp, '[', + A(UploadWidget.GENERIC_DESCRIPTION, _href = url), + ']', br, image) + return inp + + @staticmethod + def represent(field, value, download_url=None): + """ + how to represent the file: + + - with download url and if it is an image: <A href=...><IMG ...></A> + - otherwise with download url: <A href=...>file</A> + - otherwise: file + + :param field: the field + :param value: the field value + :param download_url: url for the file download (default = None) + """ + + inp = UploadWidget.GENERIC_DESCRIPTION + + if download_url and value: + url = download_url + '/' + value + if UploadWidget.is_image(value): + inp = IMG(_src = url, _width = UploadWidget.DEFAULT_WIDTH) + inp = A(inp, _href = url) + + return inp + + @staticmethod + def is_image(value): + """ + Tries to check if the filename provided references to an image + + Checking is based on filename extension. Currently recognized: + gif, png, jp(e)g, bmp + + :param value: filename + """ + + extension = value.split('.')[-1].lower() + if extension in ['gif', 'png', 'jpg', 'jpeg', 'bmp']: + return True + return False + + +class AutocompleteWidget(object): + + def __init__(self, request, field, id_field=None, db=None, + orderby=None, limitby=(0,10), + keyword='_autocomplete_%(fieldname)s', + min_length=2): + self.request = request + self.keyword = keyword % dict(fieldname=field.name) + self.db = db or field._db + self.orderby = orderby + self.limitby = limitby + self.min_length = min_length + self.fields=[field] + if id_field: + self.is_reference = True + self.fields.append(id_field) + else: + self.is_reference = False + if hasattr(request,'application'): + self.url = Url(r=request, args=request.args) + self.callback() + else: + self.url = request + def callback(self): + if self.keyword in self.request.vars: + field = self.fields[0] + rows = self.db(field.like(self.request.vars[self.keyword]+'%'))\ + .select(orderby=self.orderby,limitby=self.limitby,*self.fields) + if rows: + if self.is_reference: + id_field = self.fields[1] + raise HTTP(200,SELECT(_id=self.keyword,_class='autocomplete', + _size=len(rows),_multiple=(len(rows)==1), + *[OPTION(s[field.name],_value=s[id_field.name], + _selected=(k==0)) \ + for k,s in enumerate(rows)]).xml()) + else: + raise HTTP(200,SELECT(_id=self.keyword,_class='autocomplete', + _size=len(rows),_multiple=(len(rows)==1), + *[OPTION(s[field.name], + _selected=(k==0)) \ + for k,s in enumerate(rows)]).xml()) + else: + + raise HTTP(200,'') + def __call__(self,field,value,**attributes): + default = dict( + _type = 'text', + value = (value!=None and str(value)) or '', + ) + attr = StringWidget._attributes(field, default, **attributes) + div_id = self.keyword+'_div' + attr['_autocomplete']='off' + if self.is_reference: + key2 = self.keyword+'_aux' + key3 = self.keyword+'_auto' + attr['_class']='string' + name = attr['_name'] + if 'requires' in attr: del attr['requires'] + attr['_name'] = key2 + value = attr['value'] + record = self.db(self.fields[1]==value).select(self.fields[0]).first() + attr['value'] = record and record[self.fields[0].name] + attr['_onblur']="jQuery('#%(div_id)s').delay(3000).fadeOut('slow');" % \ + dict(div_id=div_id,u='F'+self.keyword) + attr['_onkeyup'] = "jQuery('#%(key3)s').val('');var e=event.which?event.which:event.keyCode; function %(u)s(){jQuery('#%(id)s').val(jQuery('#%(key)s :selected').text());jQuery('#%(key3)s').val(jQuery('#%(key)s').val())}; if(e==39) %(u)s(); else if(e==40) {if(jQuery('#%(key)s option:selected').next().length)jQuery('#%(key)s option:selected').attr('selected',null).next().attr('selected','selected'); %(u)s();} else if(e==38) {if(jQuery('#%(key)s option:selected').prev().length)jQuery('#%(key)s option:selected').attr('selected',null).prev().attr('selected','selected'); %(u)s();} else if(jQuery('#%(id)s').val().length>=%(min_length)s) jQuery.get('%(url)s?%(key)s='+escape(jQuery('#%(id)s').val()),function(data){if(data=='')jQuery('#%(key3)s').val('');else{jQuery('#%(id)s').next('.error').hide();jQuery('#%(div_id)s').html(data).show().focus();jQuery('#%(div_id)s select').css('width',jQuery('#%(id)s').css('width'));jQuery('#%(key3)s').val(jQuery('#%(key)s').val());jQuery('#%(key)s').change(%(u)s);jQuery('#%(key)s').click(%(u)s);};}); else jQuery('#%(div_id)s').fadeOut('slow');" % \ + dict(url=self.url,min_length=self.min_length, + key=self.keyword,id=attr['_id'],key2=key2,key3=key3, + name=name,div_id=div_id,u='F'+self.keyword) + if self.min_length==0: + attr['_onfocus'] = attr['_onkeyup'] + return TAG[''](INPUT(**attr),INPUT(_type='hidden',_id=key3,_value=value, + _name=name,requires=field.requires), + DIV(_id=div_id,_style='position:absolute;')) + else: + attr['_name']=field.name + attr['_onblur']="jQuery('#%(div_id)s').delay(3000).fadeOut('slow');" % \ + dict(div_id=div_id,u='F'+self.keyword) + attr['_onkeyup'] = "var e=event.which?event.which:event.keyCode; function %(u)s(){jQuery('#%(id)s').val(jQuery('#%(key)s').val())}; if(e==39) %(u)s(); else if(e==40) {if(jQuery('#%(key)s option:selected').next().length)jQuery('#%(key)s option:selected').attr('selected',null).next().attr('selected','selected'); %(u)s();} else if(e==38) {if(jQuery('#%(key)s option:selected').prev().length)jQuery('#%(key)s option:selected').attr('selected',null).prev().attr('selected','selected'); %(u)s();} else if(jQuery('#%(id)s').val().length>=%(min_length)s) jQuery.get('%(url)s?%(key)s='+escape(jQuery('#%(id)s').val()),function(data){jQuery('#%(id)s').next('.error').hide();jQuery('#%(div_id)s').html(data).show().focus();jQuery('#%(div_id)s select').css('width',jQuery('#%(id)s').css('width'));jQuery('#%(key)s').change(%(u)s);jQuery('#%(key)s').click(%(u)s);}); else jQuery('#%(div_id)s').fadeOut('slow');" % \ + dict(url=self.url,min_length=self.min_length, + key=self.keyword,id=attr['_id'],div_id=div_id,u='F'+self.keyword) + if self.min_length==0: + attr['_onfocus'] = attr['_onkeyup'] + return TAG[''](INPUT(**attr),DIV(_id=div_id,_style='position:absolute;')) + + +class SQLFORM(FORM): + + """ + SQLFORM is used to map a table (and a current record) into an HTML form + + given a SQLTable stored in db.table + + generates an insert form:: + + SQLFORM(db.table) + + generates an update form:: + + record=db.table[some_id] + SQLFORM(db.table, record) + + generates an update with a delete button:: + + SQLFORM(db.table, record, deletable=True) + + if record is an int:: + + record=db.table[record] + + optional arguments: + + :param fields: a list of fields that should be placed in the form, + default is all. + :param labels: a dictionary with labels for each field, keys are the field + names. + :param col3: a dictionary with content for an optional third column + (right of each field). keys are field names. + :param linkto: the URL of a controller/function to access referencedby + records + see controller appadmin.py for examples + :param upload: the URL of a controller/function to download an uploaded file + see controller appadmin.py for examples + + any named optional attribute is passed to the <form> tag + for example _class, _id, _style, _action, _method, etc. + + """ + + # usability improvements proposal by fpp - 4 May 2008 : + # - correct labels (for points to field id, not field name) + # - add label for delete checkbox + # - add translatable label for record ID + # - add third column to right of fields, populated from the col3 dict + + widgets = Storage(dict( + string = StringWidget, + text = TextWidget, + password = PasswordWidget, + integer = IntegerWidget, + double = DoubleWidget, + decimal = DecimalWidget, + time = TimeWidget, + date = DateWidget, + datetime = DatetimeWidget, + upload = UploadWidget, + boolean = BooleanWidget, + blob = None, + options = OptionsWidget, + multiple = MultipleOptionsWidget, + radio = RadioWidget, + checkboxes = CheckboxesWidget, + autocomplete = AutocompleteWidget, + list = ListWidget, + )) + + FIELDNAME_REQUEST_DELETE = 'delete_this_record' + FIELDKEY_DELETE_RECORD = 'delete_record' + ID_LABEL_SUFFIX = '__label' + ID_ROW_SUFFIX = '__row' + + def __init__( + self, + table, + record = None, + deletable = False, + linkto = None, + upload = None, + fields = None, + labels = None, + col3 = {}, + submit_button = 'Submit', + delete_label = 'Check to delete:', + showid = True, + readonly = False, + comments = True, + keepopts = [], + ignore_rw = False, + record_id = None, + formstyle = 'table3cols', + buttons = ['submit'], + separator = ': ', + **attributes + ): + """ + SQLFORM(db.table, + record=None, + fields=['name'], + labels={'name': 'Your name'}, + linkto=URL(r=request, f='table/db/') + """ + + self.ignore_rw = ignore_rw + self.formstyle = formstyle + nbsp = XML(' ') # Firefox2 does not display fields with blanks + FORM.__init__(self, *[], **attributes) + ofields = fields + keyed = hasattr(table,'_primarykey') + + # if no fields are provided, build it from the provided table + # will only use writable or readable fields, unless forced to ignore + if fields == None: + fields = [f.name for f in table if (ignore_rw or f.writable or f.readable) and not f.compute] + self.fields = fields + + # make sure we have an id + if self.fields[0] != table.fields[0] and \ + isinstance(table,Table) and not keyed: + self.fields.insert(0, table.fields[0]) + + self.table = table + + # try to retrieve the indicated record using its id + # otherwise ignore it + if record and isinstance(record, (int, long, str, unicode)): + if not str(record).isdigit(): + raise HTTP(404, "Object not found") + record = table._db(table._id == record).select().first() + if not record: + raise HTTP(404, "Object not found") + self.record = record + + self.record_id = record_id + if keyed: + if record: + self.record_id = dict([(k,record[k]) for k in table._primarykey]) + else: + self.record_id = dict([(k,None) for k in table._primarykey]) + self.field_parent = {} + xfields = [] + self.fields = fields + self.custom = Storage() + self.custom.dspval = Storage() + self.custom.inpval = Storage() + self.custom.label = Storage() + self.custom.comment = Storage() + self.custom.widget = Storage() + self.custom.linkto = Storage() + + sep = separator or '' + + for fieldname in self.fields: + if fieldname.find('.') >= 0: + continue + + field = self.table[fieldname] + comment = None + + if comments: + comment = col3.get(fieldname, field.comment) + if comment == None: + comment = '' + self.custom.comment[fieldname] = comment + + if labels != None and fieldname in labels: + label = labels[fieldname] + else: + label = field.label + self.custom.label[fieldname] = label + + field_id = '%s_%s' % (table._tablename, fieldname) + + label = LABEL(label, sep, _for=field_id, + _id=field_id+SQLFORM.ID_LABEL_SUFFIX) + + row_id = field_id+SQLFORM.ID_ROW_SUFFIX + if field.type == 'id': + self.custom.dspval.id = nbsp + self.custom.inpval.id = '' + widget = '' + if record: + if showid and 'id' in fields and field.readable: + v = record['id'] + widget = SPAN(v, _id=field_id) + self.custom.dspval.id = str(v) + xfields.append((row_id,label, widget,comment)) + self.record_id = str(record['id']) + self.custom.widget.id = widget + continue + + if readonly and not ignore_rw and not field.readable: + continue + + if record: + default = record[fieldname] + else: + default = field.default + if isinstance(default,CALLABLETYPES): + default=default() + + cond = readonly or \ + (not ignore_rw and not field.writable and field.readable) + + if default and not cond: + default = field.formatter(default) + dspval = default + inpval = default + + if cond: + + # ## if field.represent is available else + # ## ignore blob and preview uploaded images + # ## format everything else + + if field.represent: + inp = represent(field,default,record) + elif field.type in ['blob']: + continue + elif field.type == 'upload': + inp = UploadWidget.represent(field, default, upload) + elif field.type == 'boolean': + inp = self.widgets.boolean.widget(field, default, _disabled=True) + else: + inp = field.formatter(default) + elif field.type == 'upload': + if hasattr(field, 'widget') and field.widget: + inp = field.widget(field, default, upload) + else: + inp = self.widgets.upload.widget(field, default, upload) + elif hasattr(field, 'widget') and field.widget: + inp = field.widget(field, default) + elif field.type == 'boolean': + inp = self.widgets.boolean.widget(field, default) + if default: + inpval = 'checked' + else: + inpval = '' + elif OptionsWidget.has_options(field): + if not field.requires.multiple: + inp = self.widgets.options.widget(field, default) + else: + inp = self.widgets.multiple.widget(field, default) + if fieldname in keepopts: + inpval = TAG[''](*inp.components) + elif field.type.startswith('list:'): + inp = self.widgets.list.widget(field,default) + elif field.type == 'text': + inp = self.widgets.text.widget(field, default) + elif field.type == 'password': + inp = self.widgets.password.widget(field, default) + if self.record: + dspval = PasswordWidget.DEFAULT_PASSWORD_DISPLAY + else: + dspval = '' + elif field.type == 'blob': + continue + else: + inp = self.widgets.string.widget(field, default) + + xfields.append((row_id,label,inp,comment)) + self.custom.dspval[fieldname] = dspval or nbsp + self.custom.inpval[fieldname] = inpval or '' + self.custom.widget[fieldname] = inp + + # if a record is provided and found, as is linkto + # build a link + if record and linkto: + db = linkto.split('/')[-1] + for (rtable, rfield) in table._referenced_by: + if keyed: + rfld = table._db[rtable][rfield] + query = urllib.quote('%s.%s==%s' % (db,rfld,record[rfld.type[10:].split('.')[1]])) + else: + query = urllib.quote('%s.%s==%s' % (db,table._db[rtable][rfield],record.id)) + lname = olname = '%s.%s' % (rtable, rfield) + if ofields and not olname in ofields: + continue + if labels and lname in labels: + lname = labels[lname] + widget = A(lname, + _class='reference', + _href='%s/%s?query=%s' % (linkto, rtable, query)) + xfields.append((olname.replace('.', '__')+SQLFORM.ID_ROW_SUFFIX, + '',widget,col3.get(olname,''))) + self.custom.linkto[olname.replace('.', '__')] = widget +# </block> + + # when deletable, add delete? checkbox + self.custom.deletable = '' + if record and deletable: + widget = INPUT(_type='checkbox', + _class='delete', + _id=self.FIELDKEY_DELETE_RECORD, + _name=self.FIELDNAME_REQUEST_DELETE, + ) + xfields.append((self.FIELDKEY_DELETE_RECORD+SQLFORM.ID_ROW_SUFFIX, + LABEL( + delete_label, + _for=self.FIELDKEY_DELETE_RECORD, + _id=self.FIELDKEY_DELETE_RECORD+SQLFORM.ID_LABEL_SUFFIX), + widget, + col3.get(self.FIELDKEY_DELETE_RECORD, ''))) + self.custom.deletable = widget + # when writable, add submit button + self.custom.submit = '' + if (not readonly) and ('submit' in buttons): + widget = INPUT(_type='submit', + _value=submit_button) + xfields.append(('submit_record'+SQLFORM.ID_ROW_SUFFIX, + '', widget,col3.get('submit_button', ''))) + self.custom.submit = widget + # if a record is provided and found + # make sure it's id is stored in the form + if record: + if not self['hidden']: + self['hidden'] = {} + if not keyed: + self['hidden']['id'] = record['id'] + + (begin, end) = self._xml() + self.custom.begin = XML("<%s %s>" % (self.tag, begin)) + self.custom.end = XML("%s</%s>" % (end, self.tag)) + table = self.createform(xfields) + self.components = [table] + + def createform(self, xfields): + if self.formstyle == 'table3cols': + table = TABLE() + for id,a,b,c in xfields: + td_b = self.field_parent[id] = TD(b,_class='w2p_fw') + table.append(TR(TD(a,_class='w2p_fl'), + td_b, + TD(c,_class='w2p_fc'),_id=id)) + elif self.formstyle == 'table2cols': + table = TABLE() + for id,a,b,c in xfields: + td_b = self.field_parent[id] = TD(b,_class='w2p_fw',_colspan="2") + table.append(TR(TD(a,_class='w2p_fl'), + TD(c,_class='w2p_fc'),_id=id + +'1',_class='even')) + table.append(TR(td_b,_id=id+'2',_class='odd')) + elif self.formstyle == 'divs': + table = TAG['']() + for id,a,b,c in xfields: + div_b = self.field_parent[id] = DIV(b,_class='w2p_fw') + table.append(DIV(DIV(a,_class='w2p_fl'), + div_b, + DIV(c,_class='w2p_fc'),_id=id)) + elif self.formstyle == 'ul': + table = UL() + for id,a,b,c in xfields: + div_b = self.field_parent[id] = DIV(b,_class='w2p_fw') + table.append(LI(DIV(a,_class='w2p_fl'), + div_b, + DIV(c,_class='w2p_fc'),_id=id)) + elif type(self.formstyle) == type(lambda:None): + table = TABLE() + for id,a,b,c in xfields: + td_b = self.field_parent[id] = TD(b,_class='w2p_fw') + newrows = self.formstyle(id,a,td_b,c) + if type(newrows).__name__ != "tuple": + newrows = [newrows] + for newrow in newrows: + table.append(newrow) + else: + raise RuntimeError, 'formstyle not supported' + return table + + + def accepts( + self, + request_vars, + session=None, + formname='%(tablename)s/%(record_id)s', + keepvalues=False, + onvalidation=None, + dbio=True, + hideerror=False, + detect_record_change=False, + ): + + """ + similar FORM.accepts but also does insert, update or delete in DAL. + but if detect_record_change == True than: + form.record_changed = False (record is properly validated/submitted) + form.record_changed = True (record cannot be submitted because changed) + elseif detect_record_change == False than: + form.record_changed = None + """ + + if request_vars.__class__.__name__ == 'Request': + request_vars = request_vars.post_vars + + keyed = hasattr(self.table, '_primarykey') + + # implement logic to detect whether record exist but has been modified + # server side + self.record_changed = None + if detect_record_change: + if self.record: + self.record_changed = False + serialized = '|'.join(str(self.record[k]) for k in self.table.fields()) + self.record_hash = md5_hash(serialized) + + # logic to deal with record_id for keyed tables + if self.record: + if keyed: + formname_id = '.'.join(str(self.record[k]) + for k in self.table._primarykey + if hasattr(self.record,k)) + record_id = dict((k, request_vars[k]) for k in self.table._primarykey) + else: + (formname_id, record_id) = (self.record.id, + request_vars.get('id', None)) + keepvalues = True + else: + if keyed: + formname_id = 'create' + record_id = dict([(k, None) for k in self.table._primarykey]) + else: + (formname_id, record_id) = ('create', None) + + if not keyed and isinstance(record_id, (list, tuple)): + record_id = record_id[0] + + if formname: + formname = formname % dict(tablename = self.table._tablename, + record_id = formname_id) + + # ## THIS IS FOR UNIQUE RECORDS, read IS_NOT_IN_DB + + for fieldname in self.fields: + field = self.table[fieldname] + requires = field.requires or [] + if not isinstance(requires, (list, tuple)): + requires = [requires] + [item.set_self_id(self.record_id) for item in requires + if hasattr(item, 'set_self_id') and self.record_id] + + # ## END + + fields = {} + for key in self.vars: + fields[key] = self.vars[key] + + ret = FORM.accepts( + self, + request_vars, + session, + formname, + keepvalues, + onvalidation, + hideerror=hideerror, + ) + + if not ret and self.record and self.errors: + ### if there are errors in update mode + # and some errors refers to an already uploaded file + # delete error if + # - user not trying to upload a new file + # - there is existing file and user is not trying to delete it + # this is because removing the file may not pass validation + for key in self.errors.keys(): + if key in self.table \ + and self.table[key].type == 'upload' \ + and request_vars.get(key, None) in (None, '') \ + and self.record[key] \ + and not key + UploadWidget.ID_DELETE_SUFFIX in request_vars: + del self.errors[key] + if not self.errors: + ret = True + + requested_delete = \ + request_vars.get(self.FIELDNAME_REQUEST_DELETE, False) + + self.custom.end = TAG[''](self.hidden_fields(), self.custom.end) + + auch = record_id and self.errors and requested_delete + + # auch is true when user tries to delete a record + # that does not pass validation, yet it should be deleted + + if not ret and not auch: + for fieldname in self.fields: + field = self.table[fieldname] + ### this is a workaround! widgets should always have default not None! + if not field.widget and field.type.startswith('list:') and \ + not OptionsWidget.has_options(field): + field.widget = self.widgets.list.widget + if hasattr(field, 'widget') and field.widget and fieldname in request_vars: + if fieldname in self.vars: + value = self.vars[fieldname] + elif self.record: + value = self.record[fieldname] + else: + value = self.table[fieldname].default + row_id = '%s_%s%s' % (self.table, fieldname, SQLFORM.ID_ROW_SUFFIX) + widget = field.widget(field, value) + self.field_parent[row_id].components = [ widget ] + if not field.type.startswith('list:'): + self.field_parent[row_id]._traverse(False, hideerror) + self.custom.widget[ fieldname ] = widget + return ret + + if record_id and str(record_id) != str(self.record_id): + raise SyntaxError, 'user is tampering with form\'s record_id: ' \ + '%s != %s' % (record_id, self.record_id) + + if record_id and dbio: + if keyed: + self.vars.update(record_id) + else: + self.vars.id = self.record.id + + if requested_delete and self.custom.deletable: + if dbio: + if keyed: + qry = reduce(lambda x, y: x & y, + [self.table[k] == record_id[k] for k in self.table._primarykey]) + else: + qry = self.table._id == self.record.id + self.table._db(qry).delete() + self.errors.clear() + for component in self.elements('input, select, textarea'): + component['_disabled'] = True + return True + + for fieldname in self.fields: + if not fieldname in self.table.fields: + continue + + if not self.ignore_rw and not self.table[fieldname].writable: + ### this happens because FORM has no knowledge of writable + ### and thinks that a missing boolean field is a None + if self.table[fieldname].type == 'boolean' and \ + self.vars.get(fieldname, True) == None: + del self.vars[fieldname] + continue + + field = self.table[fieldname] + if field.type == 'id': + continue + if field.type == 'boolean': + if self.vars.get(fieldname, False): + self.vars[fieldname] = fields[fieldname] = True + else: + self.vars[fieldname] = fields[fieldname] = False + elif field.type == 'password' and self.record\ + and request_vars.get(fieldname, None) == \ + PasswordWidget.DEFAULT_PASSWORD_DISPLAY: + continue # do not update if password was not changed + elif field.type == 'upload': + f = self.vars[fieldname] + fd = '%s__delete' % fieldname + if f == '' or f == None: + if self.vars.get(fd, False) or not self.record: + fields[fieldname] = '' + else: + fields[fieldname] = self.record[fieldname] + self.vars[fieldname] = fields[fieldname] + continue + elif hasattr(f, 'file'): + (source_file, original_filename) = (f.file, f.filename) + elif isinstance(f, (str, unicode)): + ### do not know why this happens, it should not + (source_file, original_filename) = \ + (cStringIO.StringIO(f), 'file.txt') + newfilename = field.store(source_file, original_filename) + # this line is for backward compatibility only + self.vars['%s_newfilename' % fieldname] = newfilename + fields[fieldname] = newfilename + if isinstance(field.uploadfield, str): + fields[field.uploadfield] = source_file.read() + # proposed by Hamdy (accept?) do we need fields at this point? + self.vars[fieldname] = fields[fieldname] + continue + elif fieldname in self.vars: + fields[fieldname] = self.vars[fieldname] + elif field.default == None and field.type != 'blob': + self.errors[fieldname] = 'no data' + return False + value = fields.get(fieldname,None) + if field.type == 'list:string': + if not isinstance(value, (tuple, list)): + fields[fieldname] = value and [value] or [] + elif isinstance(field.type,str) and field.type.startswith('list:'): + if not isinstance(value, list): + fields[fieldname] = [safe_int(x) for x in (value and [value] or [])] + elif field.type == 'integer': + if value != None: + fields[fieldname] = safe_int(value) + elif field.type.startswith('reference'): + if value != None and isinstance(self.table, Table) and not keyed: + fields[fieldname] = safe_int(value) + elif field.type == 'double': + if value != None: + fields[fieldname] = safe_float(value) + + for fieldname in self.vars: + if fieldname != 'id' and fieldname in self.table.fields\ + and not fieldname in fields and not fieldname\ + in request_vars: + fields[fieldname] = self.vars[fieldname] + + if dbio: + if 'delete_this_record' in fields: + # this should never happen but seems to happen to some + del fields['delete_this_record'] + for field in self.table: + if not field.name in fields and field.writable==False: + if record_id: + fields[field.name] = self.record[field.name] + elif self.table[field.name].default!=None: + fields[field.name] = self.table[field.name].default + if keyed: + if reduce(lambda x, y: x and y, record_id.values()): # if record_id + if fields: + qry = reduce(lambda x, y: x & y, + [self.table[k] == self.record[k] for k in self.table._primarykey]) + self.table._db(qry).update(**fields) + else: + pk = self.table.insert(**fields) + if pk: + self.vars.update(pk) + else: + ret = False + else: + if record_id: + self.vars.id = self.record.id + if fields: + self.table._db(self.table._id == self.record.id).update(**fields) + else: + self.vars.id = self.table.insert(**fields) + return ret + + @staticmethod + def factory(*fields, **attributes): + """ + generates a SQLFORM for the given fields. + + Internally will build a non-database based data model + to hold the fields. + """ + # Define a table name, this way it can be logical to our CSS. + # And if you switch from using SQLFORM to SQLFORM.factory + # your same css definitions will still apply. + + table_name = attributes.get('table_name', 'no_table') + + # So it won't interfear with SQLDB.define_table + if 'table_name' in attributes: + del attributes['table_name'] + + return SQLFORM(DAL(None).define_table(table_name, *fields), **attributes) + + +class SQLTABLE(TABLE): + + """ + given a Rows object, as returned by a db().select(), generates + an html table with the rows. + + optional arguments: + + :param linkto: URL (or lambda to generate a URL) to edit individual records + :param upload: URL to download uploaded files + :param orderby: Add an orderby link to column headers. + :param headers: dictionary of headers to headers redefinions + headers can also be a string to gerenare the headers from data + for now only headers="fieldname:capitalize", + headers="labels" and headers=None are supported + :param truncate: length at which to truncate text in table cells. + Defaults to 16 characters. + :param columns: a list or dict contaning the names of the columns to be shown + Defaults to all + + Optional names attributes for passed to the <table> tag + + The keys of headers and columns must be of the form "tablename.fieldname" + + Simple linkto example:: + + rows = db.select(db.sometable.ALL) + table = SQLTABLE(rows, linkto='someurl') + + This will link rows[id] to .../sometable/value_of_id + + + More advanced linkto example:: + + def mylink(field, type, ref): + return URL(r=request, args=[field]) + + rows = db.select(db.sometable.ALL) + table = SQLTABLE(rows, linkto=mylink) + + This will link rows[id] to + current_app/current_controlle/current_function/value_of_id + + New Implements: 24 June 2011: + ----------------------------- + + :param selectid: The id you want to select + :param renderstyle: Boolean render the style with the table + + :param extracolums = [{'label':A('Extra',_href='#'), + 'class': '', #class name of the header + 'width':'', #width in pixels or % + 'content':lambda row, rc: A('Edit',_href='edit/%s'%row.id), + 'selected': False #agregate class selected to this column + }] + + + :param headers = {'table.id':{'label':'Id', + 'class':'', #class name of the header + 'width':'', #width in pixels or % + 'truncate': 16, #truncate the content to... + 'selected': False #agregate class selected to this column + }, + 'table.myfield':{'label':'My field', + 'class':'', #class name of the header + 'width':'', #width in pixels or % + 'truncate': 16, #truncate the content to... + 'selected': False #agregate class selected to this column + }, + } + + table = SQLTABLE(rows, headers=headers, extracolums=extracolums) + + + """ + + def __init__( + self, + sqlrows, + linkto=None, + upload=None, + orderby=None, + headers={}, + truncate=16, + columns=None, + th_link='', + extracolumns=None, + selectid=None, + renderstyle=False, + **attributes + ): + + TABLE.__init__(self, **attributes) + self.components = [] + self.attributes = attributes + self.sqlrows = sqlrows + (components, row) = (self.components, []) + if not sqlrows: + return + if not columns: + columns = sqlrows.colnames + if headers=='fieldname:capitalize': + headers = {} + for c in columns: + headers[c] = ' '.join([w.capitalize() for w in c.split('.')[-1].split('_')]) + elif headers=='labels': + headers = {} + for c in columns: + (t,f) = c.split('.') + field = sqlrows.db[t][f] + headers[c] = field.label + if headers!=None: + for c in columns:#new implement dict + if isinstance(headers.get(c, c), dict): + coldict = headers.get(c, c) + attrcol = dict() + if coldict['width']!="": + attrcol.update(_width=coldict['width']) + if coldict['class']!="": + attrcol.update(_class=coldict['class']) + row.append(TH(coldict['label'],**attrcol)) + elif orderby: + row.append(TH(A(headers.get(c, c), + _href=th_link+'?orderby=' + c))) + else: + row.append(TH(headers.get(c, c))) + + if extracolumns:#new implement dict + for c in extracolumns: + attrcol = dict() + if c['width']!="": + attrcol.update(_width=c['width']) + if c['class']!="": + attrcol.update(_class=c['class']) + row.append(TH(c['label'],**attrcol)) + + components.append(THEAD(TR(*row))) + + + tbody = [] + for (rc, record) in enumerate(sqlrows): + row = [] + if rc % 2 == 0: + _class = 'even' + else: + _class = 'odd' + + if selectid!=None:#new implement + if record.id==selectid: + _class += ' rowselected' + + for colname in columns: + if not table_field.match(colname): + if "_extra" in record and colname in record._extra: + r = record._extra[colname] + row.append(TD(r)) + continue + else: + raise KeyError("Column %s not found (SQLTABLE)" % colname) + (tablename, fieldname) = colname.split('.') + try: + field = sqlrows.db[tablename][fieldname] + except KeyError: + field = None + if tablename in record \ + and isinstance(record,Row) \ + and isinstance(record[tablename],Row): + r = record[tablename][fieldname] + elif fieldname in record: + r = record[fieldname] + else: + raise SyntaxError, 'something wrong in Rows object' + r_old = r + if not field: + pass + elif linkto and field.type == 'id': + try: + href = linkto(r, 'table', tablename) + except TypeError: + href = '%s/%s/%s' % (linkto, tablename, r_old) + r = A(r, _href=href) + elif field.type.startswith('reference'): + if linkto: + ref = field.type[10:] + try: + href = linkto(r, 'reference', ref) + except TypeError: + href = '%s/%s/%s' % (linkto, ref, r_old) + if ref.find('.') >= 0: + tref,fref = ref.split('.') + if hasattr(sqlrows.db[tref],'_primarykey'): + href = '%s/%s?%s' % (linkto, tref, urllib.urlencode({fref:r})) + r = A(represent(field,r,record), _href=str(href)) + elif field.represent: + r = represent(field,r,record) + elif linkto and hasattr(field._table,'_primarykey') and fieldname in field._table._primarykey: + # have to test this with multi-key tables + key = urllib.urlencode(dict( [ \ + ((tablename in record \ + and isinstance(record, Row) \ + and isinstance(record[tablename], Row)) and + (k, record[tablename][k])) or (k, record[k]) \ + for k in field._table._primarykey ] )) + r = A(r, _href='%s/%s?%s' % (linkto, tablename, key)) + elif field.type.startswith('list:'): + r = represent(field,r or [],record) + elif field.represent: + r = represent(field,r,record) + elif field.type == 'blob' and r: + r = 'DATA' + elif field.type == 'upload': + if upload and r: + r = A('file', _href='%s/%s' % (upload, r)) + elif r: + r = 'file' + else: + r = '' + elif field.type in ['string','text']: + r = str(field.formatter(r)) + ur = unicode(r, 'utf8') + if headers!={}: #new implement dict + if isinstance(headers[colname],dict): + if isinstance(headers[colname]['truncate'], int) \ + and len(ur)>headers[colname]['truncate']: + r = ur[:headers[colname]['truncate'] - 3] + r = r.encode('utf8') + '...' + elif truncate!=None and len(ur) > truncate: + r = ur[:truncate - 3].encode('utf8') + '...' + + attrcol = dict()#new implement dict + if headers!={}: + if isinstance(headers[colname],dict): + colclass=headers[colname]['class'] + if headers[colname]['selected']: + colclass= str(headers[colname]['class'] + " colselected").strip() + if colclass!="": + attrcol.update(_class=colclass) + + row.append(TD(r,**attrcol)) + + if extracolumns:#new implement dict + for c in extracolumns: + attrcol = dict() + colclass=c['class'] + if c['selected']: + colclass= str(c['class'] + " colselected").strip() + if colclass!="": + attrcol.update(_class=colclass) + contentfunc = c['content'] + row.append(TD(contentfunc(record, rc),**attrcol)) + + tbody.append(TR(_class=_class, *row)) + + if renderstyle: + components.append(STYLE(self.style())) + + components.append(TBODY(*tbody)) + + + def style(self): + + css = ''' + table tbody tr.odd { + background-color: #DFD; + } + table tbody tr.even { + background-color: #EFE; + } + table tbody tr.rowselected { + background-color: #FDD; + } + table tbody tr td.colselected { + background-color: #FDD; + } + table tbody tr:hover { + background: #DDF; + } + ''' + + return css + +form_factory = SQLFORM.factory # for backward compatibility, deprecated + + ADDED gluon/storage.py Index: gluon/storage.py ================================================================== --- gluon/storage.py +++ gluon/storage.py @@ -0,0 +1,224 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +This file is part of the web2py Web Framework +Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> +License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) + +Provides: + +- List; like list but returns None instead of IndexOutOfBounds +- Storage; like dictionary allowing also for `obj.foo` for `obj['foo']` +""" + +import cPickle +import portalocker + +__all__ = ['List', 'Storage', 'Settings', 'Messages', + 'StorageList', 'load_storage', 'save_storage'] + + +class List(list): + """ + Like a regular python list but a[i] if i is out of bounds return None + instead of IndexOutOfBounds + """ + + def __call__(self, i, default=None): + if 0<=i<len(self): + return self[i] + else: + return default + +class Storage(dict): + + """ + A Storage object is like a dictionary except `obj.foo` can be used + in addition to `obj['foo']`. + + >>> o = Storage(a=1) + >>> print o.a + 1 + + >>> o['a'] + 1 + + >>> o.a = 2 + >>> print o['a'] + 2 + + >>> del o.a + >>> print o.a + None + + """ + + def __getattr__(self, key): + if key in self: + return self[key] + else: + return None + + def __setattr__(self, key, value): + if value == None: + if key in self: + del self[key] + else: + self[key] = value + + def __delattr__(self, key): + if key in self: + del self[key] + else: + raise AttributeError, "missing key=%s" % key + + def __repr__(self): + return '<Storage ' + dict.__repr__(self) + '>' + + def __getstate__(self): + return dict(self) + + def __setstate__(self, value): + for (k, v) in value.items(): + self[k] = v + + def getlist(self, key): + """Return a Storage value as a list. + + If the value is a list it will be returned as-is. + If object is None, an empty list will be returned. + Otherwise, [value] will be returned. + + Example output for a query string of ?x=abc&y=abc&y=def + >>> request = Storage() + >>> request.vars = Storage() + >>> request.vars.x = 'abc' + >>> request.vars.y = ['abc', 'def'] + >>> request.vars.getlist('x') + ['abc'] + >>> request.vars.getlist('y') + ['abc', 'def'] + >>> request.vars.getlist('z') + [] + + """ + value = self.get(key, None) + if isinstance(value, (list, tuple)): + return value + elif value is None: + return [] + return [value] + + def getfirst(self, key): + """Return the first or only value when given a request.vars-style key. + + If the value is a list, its first item will be returned; + otherwise, the value will be returned as-is. + + Example output for a query string of ?x=abc&y=abc&y=def + >>> request = Storage() + >>> request.vars = Storage() + >>> request.vars.x = 'abc' + >>> request.vars.y = ['abc', 'def'] + >>> request.vars.getfirst('x') + 'abc' + >>> request.vars.getfirst('y') + 'abc' + >>> request.vars.getfirst('z') + + """ + value = self.getlist(key) + if len(value): + return value[0] + return None + + def getlast(self, key): + """Returns the last or only single value when given a request.vars-style key. + + If the value is a list, the last item will be returned; + otherwise, the value will be returned as-is. + + Simulated output with a query string of ?x=abc&y=abc&y=def + >>> request = Storage() + >>> request.vars = Storage() + >>> request.vars.x = 'abc' + >>> request.vars.y = ['abc', 'def'] + >>> request.vars.getlast('x') + 'abc' + >>> request.vars.getlast('y') + 'def' + >>> request.vars.getlast('z') + + """ + value = self.getlist(key) + if len(value): + return value[-1] + return None + +class StorageList(Storage): + """ + like Storage but missing elements default to [] instead of None + """ + def __getattr__(self, key): + if key in self: + return self[key] + else: + self[key] = [] + return self[key] + +def load_storage(filename): + fp = open(filename, 'rb') + try: + portalocker.lock(fp, portalocker.LOCK_EX) + storage = cPickle.load(fp) + portalocker.unlock(fp) + finally: + fp.close() + return Storage(storage) + + +def save_storage(storage, filename): + fp = open(filename, 'wb') + try: + portalocker.lock(fp, portalocker.LOCK_EX) + cPickle.dump(dict(storage), fp) + portalocker.unlock(fp) + finally: + fp.close() + + +class Settings(Storage): + + def __setattr__(self, key, value): + if key != 'lock_keys' and self.get('lock_keys', None)\ + and not key in self: + raise SyntaxError, 'setting key \'%s\' does not exist' % key + if key != 'lock_values' and self.get('lock_values', None): + raise SyntaxError, 'setting value cannot be changed: %s' % key + self[key] = value + + +class Messages(Storage): + + def __init__(self, T): + self['T'] = T + + def __setattr__(self, key, value): + if key != 'lock_keys' and self.get('lock_keys', None)\ + and not key in self: + raise SyntaxError, 'setting key \'%s\' does not exist' % key + if key != 'lock_values' and self.get('lock_values', None): + raise SyntaxError, 'setting value cannot be changed: %s' % key + self[key] = value + + def __getattr__(self, key): + value = self[key] + if isinstance(value, str): + return str(self['T'](value)) + return value + +if __name__ == '__main__': + import doctest + doctest.testmod() + ADDED gluon/streamer.py Index: gluon/streamer.py ================================================================== --- gluon/streamer.py +++ gluon/streamer.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +This file is part of the web2py Web Framework +Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> +License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) +""" + +import os +import stat +import time +import re +import errno +import rewrite +from http import HTTP +from contenttype import contenttype + + +regex_start_range = re.compile('\d+(?=\-)') +regex_stop_range = re.compile('(?<=\-)\d+') + +DEFAULT_CHUNK_SIZE = 64*1024 + +def streamer(stream, chunk_size = DEFAULT_CHUNK_SIZE, bytes = None): + offset = 0 + while bytes == None or offset < bytes: + if bytes != None and bytes - offset < chunk_size: + chunk_size = bytes - offset + data = stream.read(chunk_size) + length = len(data) + if not length: + break + else: + yield data + if length < chunk_size: + break + offset += length + stream.close() + +def stream_file_or_304_or_206( + static_file, + chunk_size = DEFAULT_CHUNK_SIZE, + request = None, + headers = {}, + error_message = None, + ): + if error_message is None: + error_message = rewrite.thread.routes.error_message % 'invalid request' + try: + fp = open(static_file) + except IOError, e: + if e[0] == errno.EISDIR: + raise HTTP(403, error_message, web2py_error='file is a directory') + elif e[0] == errno.EACCES: + raise HTTP(403, error_message, web2py_error='inaccessible file') + else: + raise HTTP(404, error_message, web2py_error='invalid file') + else: + fp.close() + stat_file = os.stat(static_file) + fsize = stat_file[stat.ST_SIZE] + mtime = time.strftime('%a, %d %b %Y %H:%M:%S GMT', + time.gmtime(stat_file[stat.ST_MTIME])) + headers['Content-Type'] = contenttype(static_file) + headers['Last-Modified'] = mtime + headers['Pragma'] = 'cache' + headers['Cache-Control'] = 'private' + + if request and request.env.http_if_modified_since == mtime: + raise HTTP(304, **{'Content-Type': headers['Content-Type']}) + + elif request and request.env.http_range: + start_items = regex_start_range.findall(request.env.http_range) + if not start_items: + start_items = [0] + stop_items = regex_stop_range.findall(request.env.http_range) + if not stop_items or int(stop_items[0]) > fsize - 1: + stop_items = [fsize - 1] + part = (int(start_items[0]), int(stop_items[0]), fsize) + bytes = part[1] - part[0] + 1 + try: + stream = open(static_file, 'rb') + except IOError, e: + if e[0] in (errno.EISDIR, errno.EACCES): + raise HTTP(403) + else: + raise HTTP(404) + stream.seek(part[0]) + headers['Content-Range'] = 'bytes %i-%i/%i' % part + headers['Content-Length'] = '%i' % bytes + status = 206 + else: + try: + stream = open(static_file, 'rb') + except IOError, e: + if e[0] in (errno.EISDIR, errno.EACCES): + raise HTTP(403) + else: + raise HTTP(404) + headers['Content-Length'] = fsize + bytes = None + status = 200 + if request and request.env.web2py_use_wsgi_file_wrapper: + wrapped = request.env.wsgi_file_wrapper(stream, chunk_size) + else: + wrapped = streamer(stream, chunk_size=chunk_size, bytes=bytes) + raise HTTP(status, wrapped, **headers) + ADDED gluon/template.py Index: gluon/template.py ================================================================== --- gluon/template.py +++ gluon/template.py @@ -0,0 +1,931 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +This file is part of the web2py Web Framework (Copyrighted, 2007-2011). +License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) + +Author: Thadeus Burgess + +Contributors: + +- Thank you to Massimo Di Pierro for creating the original gluon/template.py +- Thank you to Jonathan Lundell for extensively testing the regex on Jython. +- Thank you to Limodou (creater of uliweb) who inspired the block-element support for web2py. +""" + +import os +import re +import cgi +import cStringIO +import logging +try: + from restricted import RestrictedError +except: + def RestrictedError(a,b,c): + logging.error(str(a)+':'+str(b)+':'+str(c)) + return RuntimeError + +class Node(object): + """ + Basic Container Object + """ + def __init__(self, value = None, pre_extend = False): + self.value = value + self.pre_extend = pre_extend + + def __str__(self): + return str(self.value) + +class SuperNode(Node): + def __init__(self, name = '', pre_extend = False): + self.name = name + self.value = None + self.pre_extend = pre_extend + + def __str__(self): + if self.value: + return str(self.value) + else: + raise SyntaxError("Undefined parent block ``%s``. \n" % self.name + \ +"You must define a block before referencing it.\nMake sure you have not left out an ``{{end}}`` tag." ) + + def __repr__(self): + return "%s->%s" % (self.name, self.value) + +class BlockNode(Node): + """ + Block Container. + + This Node can contain other Nodes and will render in a hierarchical order + of when nodes were added. + + ie:: + + {{ block test }} + This is default block test + {{ end }} + """ + def __init__(self, name = '', pre_extend = False, delimiters = ('{{','}}')): + """ + name - Name of this Node. + """ + self.nodes = [] + self.name = name + self.pre_extend = pre_extend + self.left, self.right = delimiters + + def __repr__(self): + lines = ['%sblock %s%s' % (self.left,self.name,self.right)] + for node in self.nodes: + lines.append(str(node)) + lines.append('%send%s' % (self.left, self.right)) + return ''.join(lines) + + def __str__(self): + """ + Get this BlockNodes content, not including child Nodes + """ + lines = [] + for node in self.nodes: + if not isinstance(node, BlockNode): + lines.append(str(node)) + return ''.join(lines) + + def append(self, node): + """ + Add an element to the nodes. + + Keyword Arguments + + - node -- Node object or string to append. + """ + if isinstance(node, str) or isinstance(node, Node): + self.nodes.append(node) + else: + raise TypeError("Invalid type; must be instance of ``str`` or ``BlockNode``. %s" % node) + + def extend(self, other): + """ + Extend the list of nodes with another BlockNode class. + + Keyword Arguments + + - other -- BlockNode or Content object to extend from. + """ + if isinstance(other, BlockNode): + self.nodes.extend(other.nodes) + else: + raise TypeError("Invalid type; must be instance of ``BlockNode``. %s" % other) + + def output(self, blocks): + """ + Merges all nodes into a single string. + + blocks -- Dictionary of blocks that are extending + from this template. + """ + lines = [] + # Get each of our nodes + for node in self.nodes: + # If we have a block level node. + if isinstance(node, BlockNode): + # If we can override this block. + if node.name in blocks: + # Override block from vars. + lines.append(blocks[node.name].output(blocks)) + # Else we take the default + else: + lines.append(node.output(blocks)) + # Else its just a string + else: + lines.append(str(node)) + # Now combine all of our lines together. + return ''.join(lines) + +class Content(BlockNode): + """ + Parent Container -- Used as the root level BlockNode. + + Contains functions that operate as such. + """ + def __init__(self, name = "ContentBlock", pre_extend = False): + """ + Keyword Arguments + + name -- Unique name for this BlockNode + """ + self.name = name + self.nodes = [] + self.blocks = {} + self.pre_extend = pre_extend + + def __str__(self): + lines = [] + # For each of our nodes + for node in self.nodes: + # If it is a block node. + if isinstance(node, BlockNode): + # And the node has a name that corresponds with a block in us + if node.name in self.blocks: + # Use the overriding output. + lines.append(self.blocks[node.name].output(self.blocks)) + else: + # Otherwise we just use the nodes output. + lines.append(node.output(self.blocks)) + else: + # It is just a string, so include it. + lines.append(str(node)) + # Merge our list together. + return ''.join(lines) + + def _insert(self, other, index = 0): + """ + Inserts object at index. + """ + if isinstance(other, str) or isinstance(other, Node): + self.nodes.insert(index, other) + else: + raise TypeError("Invalid type, must be instance of ``str`` or ``Node``.") + + def insert(self, other, index = 0): + """ + Inserts object at index. + + You may pass a list of objects and have them inserted. + """ + if isinstance(other, (list, tuple)): + # Must reverse so the order stays the same. + other.reverse() + for item in other: + self._insert(item, index) + else: + self._insert(other, index) + + def append(self, node): + """ + Adds a node to list. If it is a BlockNode then we assign a block for it. + """ + if isinstance(node, str) or isinstance(node, Node): + self.nodes.append(node) + if isinstance(node, BlockNode): + self.blocks[node.name] = node + else: + raise TypeError("Invalid type, must be instance of ``str`` or ``BlockNode``. %s" % node) + + def extend(self, other): + """ + Extends the objects list of nodes with another objects nodes + """ + if isinstance(other, BlockNode): + self.nodes.extend(other.nodes) + self.blocks.update(other.blocks) + else: + raise TypeError("Invalid type; must be instance of ``BlockNode``. %s" % other) + + def clear_content(self): + self.nodes = [] + +class TemplateParser(object): + + r_tag = re.compile(r'(\{\{.*?\}\})', re.DOTALL) + + r_multiline = re.compile(r'(""".*?""")|(\'\'\'.*?\'\'\')', re.DOTALL) + + # These are used for re-indentation. + # Indent + 1 + re_block = re.compile('^(elif |else:|except:|except |finally:).*$', + re.DOTALL) + # Indent - 1 + re_unblock = re.compile('^(return|continue|break|raise)( .*)?$', re.DOTALL) + # Indent - 1 + re_pass = re.compile('^pass( .*)?$', re.DOTALL) + + def __init__(self, text, + name = "ParserContainer", + context = dict(), + path = 'views/', + writer = 'response.write', + lexers = {}, + delimiters = ('{{','}}'), + _super_nodes = [], + ): + """ + text -- text to parse + context -- context to parse in + path -- folder path to templates + writer -- string of writer class to use + lexers -- dict of custom lexers to use. + delimiters -- for example ('{{','}}') + _super_nodes -- a list of nodes to check for inclusion + this should only be set by "self.extend" + It contains a list of SuperNodes from a child + template that need to be handled. + """ + + # Keep a root level name. + self.name = name + # Raw text to start parsing. + self.text = text + # Writer to use (refer to the default for an example). + # This will end up as + # "%s(%s, escape=False)" % (self.writer, value) + self.writer = writer + + # Dictionary of custom name lexers to use. + if isinstance(lexers, dict): + self.lexers = lexers + else: + self.lexers = {} + + # Path of templates + self.path = path + # Context for templates. + self.context = context + + # allow optional alternative delimiters + self.delimiters = delimiters + if delimiters!=('{{','}}'): + escaped_delimiters = (re.escape(delimiters[0]),re.escape(delimiters[1])) + self.r_tag = re.compile(r'(%s.*?%s)' % escaped_delimiters, re.DOTALL) + + + # Create a root level Content that everything will go into. + self.content = Content(name=name) + + # Stack will hold our current stack of nodes. + # As we descend into a node, it will be added to the stack + # And when we leave, it will be removed from the stack. + # self.content should stay on the stack at all times. + self.stack = [self.content] + + # This variable will hold a reference to every super block + # that we come across in this template. + self.super_nodes = [] + + # This variable will hold a reference to the child + # super nodes that need handling. + self.child_super_nodes = _super_nodes + + # This variable will hold a reference to every block + # that we come across in this template + self.blocks = {} + + # Begin parsing. + self.parse(text) + + def to_string(self): + """ + Return the parsed template with correct indentation. + + Used to make it easier to port to python3. + """ + return self.reindent(str(self.content)) + + def __str__(self): + "Make sure str works exactly the same as python 3" + return self.to_string() + + def __unicode__(self): + "Make sure str works exactly the same as python 3" + return self.to_string() + + def reindent(self, text): + """ + Reindents a string of unindented python code. + """ + + # Get each of our lines into an array. + lines = text.split('\n') + + # Our new lines + new_lines = [] + + # Keeps track of how many indents we have. + # Used for when we need to drop a level of indentation + # only to reindent on the next line. + credit = 0 + + # Current indentation + k = 0 + + ################# + # THINGS TO KNOW + ################# + + # k += 1 means indent + # k -= 1 means unindent + # credit = 1 means unindent on the next line. + + for raw_line in lines: + line = raw_line.strip() + + # ignore empty lines + if not line: + continue + + # If we have a line that contains python code that + # should be unindented for this line of code. + # and then reindented for the next line. + if TemplateParser.re_block.match(line): + k = k + credit - 1 + + # We obviously can't have a negative indentation + k = max(k,0) + + # Add the indentation! + new_lines.append(' '*(4*k)+line) + + # Bank account back to 0 again :( + credit = 0 + + # If we are a pass block, we obviously de-dent. + if TemplateParser.re_pass.match(line): + k -= 1 + + # If we are any of the following, de-dent. + # However, we should stay on the same level + # But the line right after us will be de-dented. + # So we add one credit to keep us at the level + # while moving back one indentation level. + if TemplateParser.re_unblock.match(line): + credit = 1 + k -= 1 + + # If we are an if statement, a try, or a semi-colon we + # probably need to indent the next line. + if line.endswith(':') and not line.startswith('#'): + k += 1 + + # This must come before so that we can raise an error with the + # right content. + new_text = '\n'.join(new_lines) + + if k > 0: + self._raise_error('missing "pass" in view', new_text) + elif k < 0: + self._raise_error('too many "pass" in view', new_text) + + return new_text + + def _raise_error(self, message='', text=None): + """ + Raise an error using itself as the filename and textual content. + """ + raise RestrictedError(self.name, text or self.text, message) + + def _get_file_text(self, filename): + """ + Attempt to open ``filename`` and retrieve its text. + + This will use self.path to search for the file. + """ + + # If they didn't specify a filename, how can we find one! + if not filename.strip(): + self._raise_error('Invalid template filename') + + # Get the filename; filename looks like ``"template.html"``. + # We need to eval to remove the quotes and get the string type. + filename = eval(filename, self.context) + + # Get the path of the file on the system. + filepath = os.path.join(self.path, filename) + + # try to read the text. + try: + fileobj = open(filepath, 'rb') + text = fileobj.read() + fileobj.close() + except IOError: + self._raise_error('Unable to open included view file: ' + filepath) + + return text + + def include(self, content, filename): + """ + Include ``filename`` here. + """ + text = self._get_file_text(filename) + + t = TemplateParser(text, + name = filename, + context = self.context, + path = self.path, + writer = self.writer, + delimiters = self.delimiters) + + content.append(t.content) + + def extend(self, filename): + """ + Extend ``filename``. Anything not declared in a block defined by the + parent will be placed in the parent templates ``{{include}}`` block. + """ + text = self._get_file_text(filename) + + # Create out nodes list to send to the parent + super_nodes = [] + # We want to include any non-handled nodes. + super_nodes.extend(self.child_super_nodes) + # And our nodes as well. + super_nodes.extend(self.super_nodes) + + t = TemplateParser(text, + name = filename, + context = self.context, + path = self.path, + writer = self.writer, + delimiters = self.delimiters, + _super_nodes = super_nodes) + + # Make a temporary buffer that is unique for parent + # template. + buf = BlockNode(name='__include__' + filename, delimiters=self.delimiters) + pre = [] + + # Iterate through each of our nodes + for node in self.content.nodes: + # If a node is a block + if isinstance(node, BlockNode): + # That happens to be in the parent template + if node.name in t.content.blocks: + # Do not include it + continue + + if isinstance(node, Node): + # Or if the node was before the extension + # we should not include it + if node.pre_extend: + pre.append(node) + continue + + # Otherwise, it should go int the + # Parent templates {{include}} section. + buf.append(node) + else: + buf.append(node) + + # Clear our current nodes. We will be replacing this with + # the parent nodes. + self.content.nodes = [] + + # Set our include, unique by filename + t.content.blocks['__include__' + filename] = buf + + # Make sure our pre_extended nodes go first + t.content.insert(pre) + + # Then we extend our blocks + t.content.extend(self.content) + + # Work off the parent node. + self.content = t.content + + def parse(self, text): + + # Basically, r_tag.split will split the text into + # an array containing, 'non-tag', 'tag', 'non-tag', 'tag' + # so if we alternate this variable, we know + # what to look for. This is alternate to + # line.startswith("{{") + in_tag = False + extend = None + pre_extend = True + + # Use a list to store everything in + # This is because later the code will "look ahead" + # for missing strings or brackets. + ij = self.r_tag.split(text) + # j = current index + # i = current item + for j in range(len(ij)): + i = ij[j] + + if i: + if len(self.stack) == 0: + self._raise_error('The "end" tag is unmatched, please check if you have a starting "block" tag') + + # Our current element in the stack. + top = self.stack[-1] + + if in_tag: + line = i + + # If we are missing any strings!!!! + # This usually happens with the following example + # template code + # + # {{a = '}}'}} + # or + # {{a = '}}blahblah{{'}} + # + # This will fix these + # This is commented out because the current template + # system has this same limitation. Since this has a + # performance hit on larger templates, I do not recommend + # using this code on production systems. This is still here + # for "i told you it *can* be fixed" purposes. + # + # +# if line.count("'") % 2 != 0 or line.count('"') % 2 != 0: +# +# # Look ahead +# la = 1 +# nextline = ij[j+la] +# +# # As long as we have not found our ending +# # brackets keep going +# while '}}' not in nextline: +# la += 1 +# nextline += ij[j+la] +# # clear this line, so we +# # don't attempt to parse it +# # this is why there is an "if i" +# # around line 530 +# ij[j+la] = '' +# +# # retrieve our index. +# index = nextline.index('}}') +# +# # Everything before the new brackets +# before = nextline[:index+2] +# +# # Everything after +# after = nextline[index+2:] +# +# # Make the next line everything after +# # so it parses correctly, this *should* be +# # all html +# ij[j+1] = after +# +# # Add everything before to the current line +# line += before + + # Get rid of '{{' and '}}' + line = line[2:-2].strip() + + # This is bad juju, but let's do it anyway + if not line: + continue + + # We do not want to replace the newlines in code, + # only in block comments. + def remove_newline(re_val): + # Take the entire match and replace newlines with + # escaped newlines. + return re_val.group(0).replace('\n', '\\n') + + # Perform block comment escaping. + # This performs escaping ON anything + # in between """ and """ + line = re.sub(TemplateParser.r_multiline, + remove_newline, + line) + + if line.startswith('='): + # IE: {{=response.title}} + name, value = '=', line[1:].strip() + else: + v = line.split(' ', 1) + if len(v) == 1: + # Example + # {{ include }} + # {{ end }} + name = v[0] + value = '' + else: + # Example + # {{ block pie }} + # {{ include "layout.html" }} + # {{ for i in range(10): }} + name = v[0] + value = v[1] + + # This will replace newlines in block comments + # with the newline character. This is so that they + # retain their formatting, but squish down to one + # line in the rendered template. + + # First check if we have any custom lexers + if name in self.lexers: + # Pass the information to the lexer + # and allow it to inject in the environment + + # You can define custom names such as + # '{{<<variable}}' which could potentially + # write unescaped version of the variable. + self.lexers[name](parser = self, + value = value, + top = top, + stack = self.stack,) + + elif name == '=': + # So we have a variable to insert into + # the template + buf = "\n%s(%s)" % (self.writer, value) + top.append(Node(buf, pre_extend = pre_extend)) + + elif name == 'block' and not value.startswith('='): + # Make a new node with name. + node = BlockNode(name = value.strip(), + pre_extend = pre_extend, + delimiters = self.delimiters) + + # Append this node to our active node + top.append(node) + + # Make sure to add the node to the stack. + # so anything after this gets added + # to this node. This allows us to + # "nest" nodes. + self.stack.append(node) + + elif name == 'end' and not value.startswith('='): + # We are done with this node. + + # Save an instance of it + self.blocks[top.name] = top + + # Pop it. + self.stack.pop() + + elif name == 'super' and not value.startswith('='): + # Get our correct target name + # If they just called {{super}} without a name + # attempt to assume the top blocks name. + if value: + target_node = value + else: + target_node = top.name + + # Create a SuperNode instance + node = SuperNode(name = target_node, + pre_extend = pre_extend) + + # Add this to our list to be taken care of + self.super_nodes.append(node) + + # And put in in the tree + top.append(node) + + elif name == 'include' and not value.startswith('='): + # If we know the target file to include + if value: + self.include(top, value) + + # Otherwise, make a temporary include node + # That the child node will know to hook into. + else: + include_node = BlockNode(name = '__include__' + self.name, + pre_extend = pre_extend, + delimiters = self.delimiters) + top.append(include_node) + + elif name == 'extend' and not value.startswith('='): + # We need to extend the following + # template. + extend = value + pre_extend = False + + else: + # If we don't know where it belongs + # we just add it anyways without formatting. + if line and in_tag: + + # Split on the newlines >.< + tokens = line.split('\n') + + # We need to look for any instances of + # for i in range(10): + # = i + # pass + # So we can properly put a response.write() in place. + continuation = False + len_parsed = 0 + for k in range(len(tokens)): + + tokens[k] = tokens[k].strip() + len_parsed += len(tokens[k]) + + if tokens[k].startswith('='): + if tokens[k].endswith('\\'): + continuation = True + tokens[k] = "\n%s(%s" % (self.writer, tokens[k][1:].strip()) + else: + tokens[k] = "\n%s(%s)" % (self.writer, tokens[k][1:].strip()) + elif continuation: + tokens[k] += ')' + continuation = False + + + buf = "\n%s" % '\n'.join(tokens) + top.append(Node(buf, pre_extend = pre_extend)) + + else: + # It is HTML so just include it. + buf = "\n%s(%r, escape=False)" % (self.writer, i) + top.append(Node(buf, pre_extend = pre_extend)) + + # Remember: tag, not tag, tag, not tag + in_tag = not in_tag + + # Make a list of items to remove from child + to_rm = [] + + # Go through each of the children nodes + for node in self.child_super_nodes: + # If we declared a block that this node wants to include + if node.name in self.blocks: + # Go ahead and include it! + node.value = self.blocks[node.name] + # Since we processed this child, we don't need to + # pass it along to the parent + to_rm.append(node) + + # Remove some of the processed nodes + for node in to_rm: + # Since this is a pointer, it works beautifully. + # Sometimes I miss C-Style pointers... I want my asterisk... + self.child_super_nodes.remove(node) + + # If we need to extend a template. + if extend: + self.extend(extend) + +# We need this for integration with gluon +def parse_template(filename, + path = 'views/', + context = dict(), + lexers = {}, + delimiters = ('{{','}}') + ): + """ + filename can be a view filename in the views folder or an input stream + path is the path of a views folder + context is a dictionary of symbols used to render the template + """ + + # First, if we have a str try to open the file + if isinstance(filename, str): + try: + fp = open(os.path.join(path, filename), 'rb') + text = fp.read() + fp.close() + except IOError: + raise RestrictedError(filename, '', 'Unable to find the file') + else: + text = filename.read() + + # Use the file contents to get a parsed template and return it. + return str(TemplateParser(text, context=context, path=path, lexers=lexers, delimiters=delimiters)) + +def get_parsed(text): + """ + Returns the indented python code of text. Useful for unit testing. + + """ + return str(TemplateParser(text)) + +# And this is a generic render function. +# Here for integration with gluon. +def render(content = "hello world", + stream = None, + filename = None, + path = None, + context = {}, + lexers = {}, + delimiters = ('{{','}}') + ): + """ + >>> render() + 'hello world' + >>> render(content='abc') + 'abc' + >>> render(content='abc\\'') + "abc'" + >>> render(content='a"\\'bc') + 'a"\\'bc' + >>> render(content='a\\nbc') + 'a\\nbc' + >>> render(content='a"bcd"e') + 'a"bcd"e' + >>> render(content="'''a\\nc'''") + "'''a\\nc'''" + >>> render(content="'''a\\'c'''") + "'''a\'c'''" + >>> render(content='{{for i in range(a):}}{{=i}}<br />{{pass}}', context=dict(a=5)) + '0<br />1<br />2<br />3<br />4<br />' + >>> render(content='{%for i in range(a):%}{%=i%}<br />{%pass%}', context=dict(a=5),delimiters=('{%','%}')) + '0<br />1<br />2<br />3<br />4<br />' + >>> render(content="{{='''hello\\nworld'''}}") + 'hello\\nworld' + >>> render(content='{{for i in range(3):\\n=i\\npass}}') + '012' + """ + # Here to avoid circular Imports + try: + from globals import Response + except: + # Working standalone. Build a mock Response object. + class Response(): + def __init__(self): + self.body = cStringIO.StringIO() + def write(self, data, escape=True): + if not escape: + self.body.write(str(data)) + elif hasattr(data,'xml') and callable(data.xml): + self.body.write(data.xml()) + else: + # make it a string + if not isinstance(data, (str, unicode)): + data = str(data) + elif isinstance(data, unicode): + data = data.encode('utf8', 'xmlcharrefreplace') + data = cgi.escape(data, True).replace("'","'") + self.body.write(data) + + # A little helper to avoid escaping. + class NOESCAPE(): + def __init__(self, text): + self.text = text + def xml(self): + return self.text + # Add it to the context so we can use it. + context['NOESCAPE'] = NOESCAPE + + # If we don't have anything to render, why bother? + if not content and not stream and not filename: + raise SyntaxError, "Must specify a stream or filename or content" + + # Here for legacy purposes, probably can be reduced to something more simple. + close_stream = False + if not stream: + if filename: + stream = open(filename, 'rb') + close_stream = True + elif content: + stream = cStringIO.StringIO(content) + + # Get a response class. + context['response'] = Response() + + # Execute the template. + code = str(TemplateParser(stream.read(), context=context, path=path, lexers=lexers, delimiters=delimiters)) + try: + exec(code) in context + except Exception: + # for i,line in enumerate(code.split('\n')): print i,line + raise + + if close_stream: + stream.close() + + # Returned the rendered content. + return context['response'].body.getvalue() + + +if __name__ == '__main__': + import doctest + doctest.testmod() + ADDED gluon/tests/__init__.py Index: gluon/tests/__init__.py ================================================================== --- gluon/tests/__init__.py +++ gluon/tests/__init__.py @@ -0,0 +1,1 @@ + ADDED gluon/tests/test.sh Index: gluon/tests/test.sh ================================================================== --- gluon/tests/test.sh +++ gluon/tests/test.sh @@ -0,0 +1,45 @@ +#!/bin/sh +# +# run unit tests under nose if available, +# optionally with coverage +# +# test.sh [cover [gluon.rewrite]] +# +# easy_install nose +# easy_install coverage +# +NOSETESTS=nosetests +COVER=gluon # change to (eg) gluon.rewrite to collect coverage stats on a single module +PROCESSES=4 + +WHICH=`which $NOSETESTS` +if [ "$WHICH" == "" ]; then + # if nose isn't available, run the tests directly + for testmod in test_*.py; do + python $testmod + done +else + if [ "$1" = "cover" ]; then + # note: coverage doesn't handle multiple processes + if [ "$2" != "" ]; then + COVER=$2 + fi + $NOSETESTS --with-coverage --cover-package=$COVER --cover-erase + elif [ "$1" = "doctest" ]; then + # this has to run in gluon's parent; needs work + # + # the problem is that doctests run this way have a very different environment, + # apparently due to imports that don't happen in the normal course of running + # doctest via __main__. + # + echo doctest not supported >&2 + exit 1 + if [ ! -d gluon ]; then + cd ../.. + fi + $NOSETESTS --with-doctest + else + $NOSETESTS --processes=$PROCESSES + fi +fi + ADDED gluon/tests/test_dal.py Index: gluon/tests/test_dal.py ================================================================== --- gluon/tests/test_dal.py +++ gluon/tests/test_dal.py @@ -0,0 +1,486 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + Unit tests for gluon.sql +""" + +import sys +import os +if os.path.isdir('gluon'): + sys.path.append(os.path.realpath('gluon')) +else: + sys.path.append(os.path.realpath('../')) + +import unittest +import datetime +from dal import DAL, Field, Table, SQLALL + +ALLOWED_DATATYPES = [ + 'string', + 'text', + 'integer', + 'boolean', + 'double', + 'blob', + 'date', + 'time', + 'datetime', + 'upload', + 'password', + ] + + +def setUpModule(): + pass + +def tearDownModule(): + if os.path.isfile('sql.log'): + os.unlink('sql.log') + + +class TestFields(unittest.TestCase): + + def testFieldName(self): + + # Check that Fields cannot start with underscores + self.assertRaises(SyntaxError, Field, '_abc', 'string') + + # Check that Fields cannot contain punctuation other than underscores + self.assertRaises(SyntaxError, Field, 'a.bc', 'string') + + # Check that Fields cannot be a name of a method or property of Table + for x in ['drop', 'on', 'truncate']: + self.assertRaises(SyntaxError, Field, x, 'string') + + # Check that Fields allows underscores in the body of a field name. + self.assert_(Field('a_bc', 'string'), + "Field isn't allowing underscores in fieldnames. It should.") + + def testFieldTypes(self): + + # Check that string, text, and password default length is 512 + for typ in ['string', 'password']: + self.assert_(Field('abc', typ).length == 512, + "Default length for type '%s' is not 512 or 255" % typ) + + # Check that upload default length is 512 + self.assert_(Field('abc', 'upload').length == 512, + "Default length for type 'upload' is not 128") + + # Check that Tables passed in the type creates a reference + self.assert_(Field('abc', Table(None, 'temp')).type + == 'reference temp', + 'Passing an Table does not result in a reference type.') + + def testFieldLabels(self): + + # Check that a label is successfully built from the supplied fieldname + self.assert_(Field('abc', 'string').label == 'Abc', + 'Label built is incorrect') + self.assert_(Field('abc_def', 'string').label == 'Abc Def', + 'Label built is incorrect') + + def testFieldFormatters(self): # Formatter should be called Validator + + # Test the default formatters + for typ in ALLOWED_DATATYPES: + f = Field('abc', typ) + if typ not in ['date', 'time', 'datetime']: + isinstance(f.formatter('test'), str) + else: + isinstance(f.formatter(datetime.datetime.now()), str) + + def testRun(self): + db = DAL('sqlite:memory:') + for ft in ['string', 'text', 'password', 'upload', 'blob']: + db.define_table('t', Field('a', ft, default='')) + self.assertEqual(db.t.insert(a='x'), 1) + self.assertEqual(db().select(db.t.a)[0].a, 'x') + db.t.drop() + db.define_table('t', Field('a', 'integer', default=1)) + self.assertEqual(db.t.insert(a=3), 1) + self.assertEqual(db().select(db.t.a)[0].a, 3) + db.t.drop() + db.define_table('t', Field('a', 'double', default=1)) + self.assertEqual(db.t.insert(a=3.1), 1) + self.assertEqual(db().select(db.t.a)[0].a, 3.1) + db.t.drop() + db.define_table('t', Field('a', 'boolean', default=True)) + self.assertEqual(db.t.insert(a=True), 1) + self.assertEqual(db().select(db.t.a)[0].a, True) + db.t.drop() + db.define_table('t', Field('a', 'date', + default=datetime.date.today())) + t0 = datetime.date.today() + self.assertEqual(db.t.insert(a=t0), 1) + self.assertEqual(db().select(db.t.a)[0].a, t0) + db.t.drop() + db.define_table('t', Field('a', 'datetime', + default=datetime.datetime.today())) + t0 = datetime.datetime( + 1971, + 12, + 21, + 10, + 30, + 55, + 0, + ) + self.assertEqual(db.t.insert(a=t0), 1) + self.assertEqual(db().select(db.t.a)[0].a, t0) + db.t.drop() + db.define_table('t', Field('a', 'time', default='11:30')) + t0 = datetime.time(10, 30, 55) + self.assertEqual(db.t.insert(a=t0), 1) + self.assertEqual(db().select(db.t.a)[0].a, t0) + db.t.drop() + + +class TestAll(unittest.TestCase): + + def setUp(self): + self.pt = Table(None,'PseudoTable',Field('name'),Field('birthdate')) + + def testSQLALL(self): + ans = 'PseudoTable.id, PseudoTable.name, PseudoTable.birthdate' + self.assertEqual(str(SQLALL(self.pt)), ans) + + +class TestTable(unittest.TestCase): + + def testTableCreation(self): + + # Check for error when not passing type other than Field or Table + + self.assertRaises(SyntaxError, Table, None, 'test', None) + + persons = Table(None, 'persons', + Field('firstname','string'), + Field('lastname', 'string')) + + # Does it have the correct fields? + + self.assert_(set(persons.fields).issuperset(set(['firstname', + 'lastname']))) + + # ALL is set correctly + + self.assert_('persons.firstname, persons.lastname' + in str(persons.ALL)) + + def testTableAlias(self): + db = DAL('sqlite:memory:') + persons = Table(db, 'persons', Field('firstname', + 'string'), Field('lastname', 'string')) + aliens = persons.with_alias('aliens') + + # Are the different table instances with the same fields + + self.assert_(persons is not aliens) + self.assert_(set(persons.fields) == set(aliens.fields)) + + def testTableInheritance(self): + persons = Table(None, 'persons', Field('firstname', + 'string'), Field('lastname', 'string')) + customers = Table(None, 'customers', + Field('items_purchased', 'integer'), + persons) + self.assert_(set(customers.fields).issuperset(set( + ['items_purchased', 'firstname', 'lastname']))) + + +class TestInsert(unittest.TestCase): + + def testRun(self): + db = DAL('sqlite:memory:') + db.define_table('t', Field('a')) + self.assertEqual(db.t.insert(a='1'), 1) + self.assertEqual(db.t.insert(a='1'), 2) + self.assertEqual(db.t.insert(a='1'), 3) + self.assertEqual(db(db.t.a == '1').count(), 3) + self.assertEqual(db(db.t.a == '1').update(a='2'), 3) + self.assertEqual(db(db.t.a == '2').count(), 3) + self.assertEqual(db(db.t.a == '2').delete(), 3) + db.t.drop() + + +class TestSelect(unittest.TestCase): + + def testRun(self): + db = DAL('sqlite:memory:') + db.define_table('t', Field('a')) + self.assertEqual(db.t.insert(a='1'), 1) + self.assertEqual(db.t.insert(a='2'), 2) + self.assertEqual(db.t.insert(a='3'), 3) + self.assertEqual(len(db(db.t.id > 0).select()), 3) + self.assertEqual(db(db.t.id > 0).select(orderby=~db.t.a + | db.t.id)[0].a, '3') + self.assertEqual(len(db(db.t.id > 0).select(limitby=(1, 2))), 1) + self.assertEqual(db(db.t.id > 0).select(limitby=(1, 2))[0].a, + '2') + self.assertEqual(len(db().select(db.t.ALL)), 3) + self.assertEqual(len(db(db.t.a == None).select()), 0) + self.assertEqual(len(db(db.t.a != None).select()), 3) + self.assertEqual(len(db(db.t.a > '1').select()), 2) + self.assertEqual(len(db(db.t.a >= '1').select()), 3) + self.assertEqual(len(db(db.t.a == '1').select()), 1) + self.assertEqual(len(db(db.t.a != '1').select()), 2) + self.assertEqual(len(db(db.t.a < '3').select()), 2) + self.assertEqual(len(db(db.t.a <= '3').select()), 3) + self.assertEqual(len(db(db.t.a > '1')(db.t.a < '3').select()), 1) + self.assertEqual(len(db((db.t.a > '1') & (db.t.a < '3')).select()), 1) + self.assertEqual(len(db((db.t.a > '1') | (db.t.a < '3')).select()), 3) + self.assertEqual(len(db((db.t.a > '1') & ~(db.t.a > '2')).select()), 1) + self.assertEqual(len(db(~(db.t.a > '1') & (db.t.a > '2')).select()), 0) + db.t.drop() + + +class TestBelongs(unittest.TestCase): + + def testRun(self): + db = DAL('sqlite:memory:') + db.define_table('t', Field('a')) + self.assertEqual(db.t.insert(a='1'), 1) + self.assertEqual(db.t.insert(a='2'), 2) + self.assertEqual(db.t.insert(a='3'), 3) + self.assertEqual(len(db(db.t.a.belongs(('1', '3'))).select()), + 2) + self.assertEqual(len(db(db.t.a.belongs(db(db.t.id + > 2)._select(db.t.a))).select()), 1) + self.assertEqual(len(db(db.t.a.belongs(db(db.t.a.belongs(('1', + '3')))._select(db.t.a))).select()), 2) + self.assertEqual(len(db(db.t.a.belongs(db(db.t.a.belongs(db + (db.t.a.belongs(('1', '3')))._select(db.t.a)))._select( + db.t.a))).select()), + 2) + db.t.drop() + + +class TestLike(unittest.TestCase): + + def testRun(self): + db = DAL('sqlite:memory:') + db.define_table('t', Field('a')) + self.assertEqual(db.t.insert(a='abc'), 1) + self.assertEqual(len(db(db.t.a.like('a%')).select()), 1) + self.assertEqual(len(db(db.t.a.like('%b%')).select()), 1) + self.assertEqual(len(db(db.t.a.like('%c')).select()), 1) + self.assertEqual(len(db(db.t.a.like('%d%')).select()), 0) + self.assertEqual(len(db(db.t.a.lower().like('A%')).select()), 1) + self.assertEqual(len(db(db.t.a.lower().like('%B%')).select()), + 1) + self.assertEqual(len(db(db.t.a.lower().like('%C')).select()), 1) + self.assertEqual(len(db(db.t.a.upper().like('A%')).select()), 1) + self.assertEqual(len(db(db.t.a.upper().like('%B%')).select()), + 1) + self.assertEqual(len(db(db.t.a.upper().like('%C')).select()), 1) + db.t.drop() + + +class TestDatetime(unittest.TestCase): + + def testRun(self): + db = DAL('sqlite:memory:') + db.define_table('t', Field('a', 'datetime')) + self.assertEqual(db.t.insert(a=datetime.datetime(1971, 12, 21, + 11, 30)), 1) + self.assertEqual(db.t.insert(a=datetime.datetime(1971, 11, 21, + 10, 30)), 2) + self.assertEqual(db.t.insert(a=datetime.datetime(1970, 12, 21, + 9, 30)), 3) + self.assertEqual(len(db(db.t.a == datetime.datetime(1971, 12, + 21, 11, 30)).select()), 1) + self.assertEqual(len(db(db.t.a.year() == 1971).select()), 2) + self.assertEqual(len(db(db.t.a.month() == 12).select()), 2) + self.assertEqual(len(db(db.t.a.day() == 21).select()), 3) + self.assertEqual(len(db(db.t.a.hour() == 11).select()), 1) + self.assertEqual(len(db(db.t.a.minutes() == 30).select()), 3) + self.assertEqual(len(db(db.t.a.seconds() == 0).select()), 3) + db.t.drop() + + +class TestExpressions(unittest.TestCase): + + def testRun(self): + db = DAL('sqlite:memory:') + db.define_table('t', Field('a', 'integer')) + self.assertEqual(db.t.insert(a=1), 1) + self.assertEqual(db.t.insert(a=2), 2) + self.assertEqual(db.t.insert(a=3), 3) + self.assertEqual(db(db.t.a == 3).update(a=db.t.a + 1), 1) + self.assertEqual(len(db(db.t.a == 4).select()), 1) + db.t.drop() + + +class TestJoin(unittest.TestCase): + + def testRun(self): + db = DAL('sqlite:memory:') + db.define_table('t1', Field('a')) + db.define_table('t2', Field('a'), Field('b', db.t1)) + i1 = db.t1.insert(a='1') + i2 = db.t1.insert(a='2') + i3 = db.t1.insert(a='3') + db.t2.insert(a='4', b=i1) + db.t2.insert(a='5', b=i2) + db.t2.insert(a='6', b=i2) + self.assertEqual(len(db(db.t1.id + == db.t2.b).select(orderby=db.t1.a + | db.t2.a)), 3) + self.assertEqual(db(db.t1.id == db.t2.b).select(orderby=db.t1.a + | db.t2.a)[2].t1.a, '2') + self.assertEqual(db(db.t1.id == db.t2.b).select(orderby=db.t1.a + | db.t2.a)[2].t2.a, '6') + self.assertEqual(len(db().select(db.t1.ALL, db.t2.ALL, + left=db.t2.on(db.t1.id == db.t2.b), + orderby=db.t1.a | db.t2.a)), 4) + self.assertEqual(db().select(db.t1.ALL, db.t2.ALL, + left=db.t2.on(db.t1.id == db.t2.b), + orderby=db.t1.a | db.t2.a)[2].t1.a, '2') + self.assertEqual(db().select(db.t1.ALL, db.t2.ALL, + left=db.t2.on(db.t1.id == db.t2.b), + orderby=db.t1.a | db.t2.a)[2].t2.a, '6') + self.assertEqual(db().select(db.t1.ALL, db.t2.ALL, + left=db.t2.on(db.t1.id == db.t2.b), + orderby=db.t1.a | db.t2.a)[3].t1.a, '3') + self.assertEqual(db().select(db.t1.ALL, db.t2.ALL, + left=db.t2.on(db.t1.id == db.t2.b), + orderby=db.t1.a | db.t2.a)[3].t2.a, None) + self.assertEqual(len(db().select(db.t1.ALL, db.t2.id.count(), + left=db.t2.on(db.t1.id == db.t2.b), + orderby=db.t1.a | db.t2.a, groupby=db.t1.a)), + 3) + self.assertEqual(db().select(db.t1.ALL, db.t2.id.count(), + left=db.t2.on(db.t1.id == db.t2.b), + orderby=db.t1.a | db.t2.a, + groupby=db.t1.a)[0]._extra[db.t2.id.count()], + 1) + self.assertEqual(db().select(db.t1.ALL, db.t2.id.count(), + left=db.t2.on(db.t1.id == db.t2.b), + orderby=db.t1.a | db.t2.a, + groupby=db.t1.a)[1]._extra[db.t2.id.count()], + 2) + self.assertEqual(db().select(db.t1.ALL, db.t2.id.count(), + left=db.t2.on(db.t1.id == db.t2.b), + orderby=db.t1.a | db.t2.a, + groupby=db.t1.a)[2]._extra[db.t2.id.count()], + 0) + db.t1.drop() + db.t2.drop() + + +class TestMinMaxSum(unittest.TestCase): + + def testRun(self): + db = DAL('sqlite:memory:') + db.define_table('t', Field('a', 'integer')) + self.assertEqual(db.t.insert(a=1), 1) + self.assertEqual(db.t.insert(a=2), 2) + self.assertEqual(db.t.insert(a=3), 3) + s = db.t.a.min() + self.assertEqual(db(db.t.id > 0).select(s)[0]._extra[s], 1) + s = db.t.a.max() + self.assertEqual(db(db.t.id > 0).select(s)[0]._extra[s], 3) + s = db.t.a.sum() + self.assertEqual(db(db.t.id > 0).select(s)[0]._extra[s], 6) + s = db.t.a.count() + self.assertEqual(db(db.t.id > 0).select(s)[0]._extra[s], 3) + db.t.drop() + + +#class TestCache(unittest. +# def testRun(self): +# cache = cache.ram +# db = DAL('sqlite:memory:') +# db.define_table('t', Field('a')) +# db.t.insert(a='1') +# r1 = db().select(db.t.ALL, cache=(cache, 1000)) +# db.t.insert(a='1') +# r2 = db().select(db.t.ALL, cache=(cache, 1000)) +# self.assertEqual(r1.response, r2.response) +# db.t.drop() + + +class TestMigrations(unittest.TestCase): + + def testRun(self): + db = DAL('sqlite://.storage.db') + db.define_table('t', Field('a'), migrate='.storage.table') + db.commit() + db = DAL('sqlite://.storage.db') + db.define_table('t', Field('a'), Field('b'), + migrate='.storage.table') + db.commit() + db = DAL('sqlite://.storage.db') + db.define_table('t', Field('a'), Field('b', 'text'), + migrate='.storage.table') + db.commit() + db = DAL('sqlite://.storage.db') + db.define_table('t', Field('a'), migrate='.storage.table') + db.t.drop() + db.commit() + + def tearDown(self): + os.unlink('.storage.db') + +class TestReferece(unittest.TestCase): + + def testRun(self): + db = DAL('sqlite:memory:') + db.define_table('t', Field('name'), Field('a','reference t')) + db.commit() + x = db.t.insert(name='max') + assert x.id == 1 + assert x['id'] == 1 + x.a = x + assert x.a == 1 + x.update_record() + y = db.t[1] + assert y.a == 1 + assert y.a.a.a.a.a.a.name == 'max' + z=db.t.insert(name='xxx', a = y) + assert z.a == y.id + db.t.drop() + db.commit() + +class TestClientLevelOps(unittest.TestCase): + + def testRun(self): + db = DAL('sqlite:memory:') + db.define_table('t', Field('a')) + db.commit() + db.t.insert(a="test") + rows1 = db(db.t.id>0).select() + rows2 = db(db.t.id>0).select() + rows3 = rows1 & rows2 + assert len(rows3) == 2 + rows4 = rows1 | rows2 + assert len(rows4) == 1 + rows5 = rows1.find(lambda row: row.a=="test") + assert len(rows5) == 1 + rows6 = rows2.exclude(lambda row: row.a=="test") + assert len(rows6) == 1 + rows7 = rows5.sort(lambda row: row.a) + assert len(rows7) == 1 + db.t.drop() + db.commit() + + +class TestVirtualFields(unittest.TestCase): + + def testRun(self): + db = DAL('sqlite:memory:') + db.define_table('t', Field('a')) + db.commit() + db.t.insert(a="test") + class Compute: + def a_upper(row): return row.t.a.upper() + db.t.virtualfields.append(Compute()) + assert db(db.t.id>0).select().first().a_upper == 'TEST' + db.t.drop() + db.commit() + + +if __name__ == '__main__': + unittest.main() + tearDownModule() ADDED gluon/tests/test_html.py Index: gluon/tests/test_html.py ================================================================== --- gluon/tests/test_html.py +++ gluon/tests/test_html.py @@ -0,0 +1,122 @@ +#!/bin/python +# -*- coding: utf-8 -*- + +""" + Unit tests for gluon.html +""" + +import sys +import os +if os.path.isdir('gluon'): + sys.path.append(os.path.realpath('gluon')) +else: + sys.path.append(os.path.realpath('../')) + +import unittest +from html import * + + +class TestBareHelpers(unittest.TestCase): + + def testRun(self): + self.assertEqual(BR(_a='1', _b='2').xml(), '<br a="1" b="2" />') + self.assertEqual(EMBED(_a='1', _b='2').xml(), + '<embed a="1" b="2" />') + self.assertEqual(HR(_a='1', _b='2').xml(), '<hr a="1" b="2" />') + self.assertEqual(IMG(_a='1', _b='2').xml(), + '<img a="1" b="2" />') + self.assertEqual(INPUT(_a='1', _b='2').xml(), + '<input a="1" b="2" type="text" />') + self.assertEqual(LINK(_a='1', _b='2').xml(), + '<link a="1" b="2" />') + self.assertEqual(META(_a='1', _b='2').xml(), + '<meta a="1" b="2" />') + + self.assertEqual(A('<>', _a='1', _b='2').xml(), + '<a a="1" b="2"><></a>') + self.assertEqual(B('<>', _a='1', _b='2').xml(), + '<b a="1" b="2"><></b>') + self.assertEqual(BODY('<>', _a='1', _b='2').xml(), + '<body a="1" b="2"><></body>') + self.assertEqual(CENTER('<>', _a='1', _b='2').xml(), + '<center a="1" b="2"><></center>') + self.assertEqual(DIV('<>', _a='1', _b='2').xml(), + '<div a="1" b="2"><></div>') + self.assertEqual(EM('<>', _a='1', _b='2').xml(), + '<em a="1" b="2"><></em>') + self.assertEqual(FIELDSET('<>', _a='1', _b='2').xml(), + '<fieldset a="1" b="2"><></fieldset>') + self.assertEqual(FORM('<>', _a='1', _b='2').xml(), + '<form a="1" action="" b="2" enctype="multipart/form-data" method="post"><></form>') + self.assertEqual(H1('<>', _a='1', _b='2').xml(), + '<h1 a="1" b="2"><></h1>') + self.assertEqual(H2('<>', _a='1', _b='2').xml(), + '<h2 a="1" b="2"><></h2>') + self.assertEqual(H3('<>', _a='1', _b='2').xml(), + '<h3 a="1" b="2"><></h3>') + self.assertEqual(H4('<>', _a='1', _b='2').xml(), + '<h4 a="1" b="2"><></h4>') + self.assertEqual(H5('<>', _a='1', _b='2').xml(), + '<h5 a="1" b="2"><></h5>') + self.assertEqual(H6('<>', _a='1', _b='2').xml(), + '<h6 a="1" b="2"><></h6>') + self.assertEqual(HEAD('<>', _a='1', _b='2').xml(), + '<head a="1" b="2"><></head>') + self.assertEqual(HTML('<>', _a='1', _b='2').xml(), + '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n<html a="1" b="2" lang="en"><></html>') + self.assertEqual(IFRAME('<>', _a='1', _b='2').xml(), + '<iframe a="1" b="2"><></iframe>') + self.assertEqual(LABEL('<>', _a='1', _b='2').xml(), + '<label a="1" b="2"><></label>') + self.assertEqual(LI('<>', _a='1', _b='2').xml(), + '<li a="1" b="2"><></li>') + self.assertEqual(OBJECT('<>', _a='1', _b='2').xml(), + '<object a="1" b="2"><></object>') + self.assertEqual(OL('<>', _a='1', _b='2').xml(), + '<ol a="1" b="2"><li><></li></ol>') + self.assertEqual(OPTION('<>', _a='1', _b='2').xml(), + '<option a="1" b="2" value="<>"><>' + \ + '</option>') + self.assertEqual(P('<>', _a='1', _b='2').xml(), + '<p a="1" b="2"><></p>') + self.assertEqual(PRE('<>', _a='1', _b='2').xml(), + '<pre a="1" b="2"><></pre>') + self.assertEqual(SCRIPT('<>', _a='1', _b='2').xml(), + '''<script a="1" b="2"><!-- +<> +//--></script>''') + self.assertEqual(SELECT('<>', _a='1', _b='2').xml(), + '<select a="1" b="2">'+ \ + '<option value="<>"><></option></select>') + self.assertEqual(SPAN('<>', _a='1', _b='2').xml(), + '<span a="1" b="2"><></span>') + self.assertEqual(STYLE('<>', _a='1', _b='2').xml(), + '<style a="1" b="2"><!--/*--><![CDATA[/*><!--*/\n<>\n/*]]>*/--></style>') + self.assertEqual(TABLE('<>', _a='1', _b='2').xml(), + '<table a="1" b="2"><tr><td><></td></tr>' + \ + '</table>') + self.assertEqual(TBODY('<>', _a='1', _b='2').xml(), + '<tbody a="1" b="2"><tr><td><></td></tr></tbody>') + self.assertEqual(TD('<>', _a='1', _b='2').xml(), + '<td a="1" b="2"><></td>') + self.assertEqual(TEXTAREA('<>', _a='1', _b='2').xml(), + '<textarea a="1" b="2" cols="40" rows="10"><>' + \ + '</textarea>') + self.assertEqual(TFOOT('<>', _a='1', _b='2').xml(), + '<tfoot a="1" b="2"><tr><td><></td></tr></tfoot>') + self.assertEqual(TH('<>', _a='1', _b='2').xml(), + '<th a="1" b="2"><></th>') + self.assertEqual(THEAD('<>', _a='1', _b='2').xml(), + '<thead a="1" b="2"><tr><td><></td></tr></thead>') + self.assertEqual(TITLE('<>', _a='1', _b='2').xml(), + '<title a="1" b="2"><>') + self.assertEqual(TR('<>', _a='1', _b='2').xml(), + '

    ') + self.assertEqual(TT('<>', _a='1', _b='2').xml(), + '<>') + self.assertEqual(UL('<>', _a='1', _b='2').xml(), + '
    • <>
    ') + + +if __name__ == '__main__': + unittest.main() ADDED gluon/tests/test_is_url.py Index: gluon/tests/test_is_url.py ================================================================== --- gluon/tests/test_is_url.py +++ gluon/tests/test_is_url.py @@ -0,0 +1,644 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Unit tests for IS_URL() +""" + +import sys +import os +if os.path.isdir('gluon'): + sys.path.append(os.path.realpath('gluon')) +else: + sys.path.append(os.path.realpath('../')) + +import unittest +from validators import IS_URL, IS_HTTP_URL, IS_GENERIC_URL, \ + unicode_to_ascii_authority + + +class TestIsUrl(unittest.TestCase): + + def testModeHttp(self): + + # defaults to mode='http' + + x = IS_URL() + self.assertEqual(x('http://google.ca'), ('http://google.ca', + None)) + self.assertEqual(x('google.ca'), ('http://google.ca', None)) + self.assertEqual(x('google.ca:80'), ('http://google.ca:80', + None)) + self.assertEqual(x('unreal.blargg'), ('unreal.blargg', + 'enter a valid URL')) + self.assertEqual(x('google..ca'), ('google..ca', 'enter a valid URL')) + self.assertEqual(x('google.ca..'), ('google.ca..', 'enter a valid URL')) + + # explicit use of 'http' mode + + x = IS_URL(mode='http') + self.assertEqual(x('http://google.ca'), ('http://google.ca', + None)) + self.assertEqual(x('google.ca'), ('http://google.ca', None)) + self.assertEqual(x('google.ca:80'), ('http://google.ca:80', + None)) + self.assertEqual(x('unreal.blargg'), ('unreal.blargg', + 'enter a valid URL')) + + # prepends 'https' instead of 'http' + + x = IS_URL(mode='http', prepend_scheme='https') + self.assertEqual(x('http://google.ca'), ('http://google.ca', + None)) + self.assertEqual(x('google.ca'), ('https://google.ca', None)) + self.assertEqual(x('google.ca:80'), ('https://google.ca:80', + None)) + self.assertEqual(x('unreal.blargg'), ('unreal.blargg', + 'enter a valid URL')) + + # prepending disabled + + x = IS_URL(prepend_scheme=None) + self.assertEqual(x('http://google.ca'), ('http://google.ca', + None)) + self.assertEqual(x('google.ca'), ('google.ca', None)) + self.assertEqual(x('google.ca:80'), ('google.ca:80', None)) + self.assertEqual(x('unreal.blargg'), ('unreal.blargg', + 'enter a valid URL')) + + # custom allowed_schemes + + x = IS_URL(mode='http', allowed_schemes=[None, 'http']) + self.assertEqual(x('http://google.ca'), ('http://google.ca', + None)) + self.assertEqual(x('https://google.ca'), ('https://google.ca', + 'enter a valid URL')) + self.assertEqual(x('google.ca'), ('http://google.ca', None)) + self.assertEqual(x('google.ca:80'), ('http://google.ca:80', + None)) + self.assertEqual(x('unreal.blargg'), ('unreal.blargg', + 'enter a valid URL')) + + # custom allowed_schemes, excluding None + + x = IS_URL(allowed_schemes=['http']) + self.assertEqual(x('http://google.ca'), ('http://google.ca', + None)) + self.assertEqual(x('https://google.ca'), ('https://google.ca', + 'enter a valid URL')) + self.assertEqual(x('google.ca'), ('google.ca', 'enter a valid URL')) + self.assertEqual(x('google.ca:80'), ('google.ca:80', + 'enter a valid URL')) + self.assertEqual(x('unreal.blargg'), ('unreal.blargg', + 'enter a valid URL')) + + # custom allowed_schemes and prepend_scheme + + x = IS_URL(allowed_schemes=[None, 'https'], + prepend_scheme='https') + self.assertEqual(x('http://google.ca'), ('http://google.ca', + 'enter a valid URL')) + self.assertEqual(x('https://google.ca'), ('https://google.ca', + None)) + self.assertEqual(x('google.ca'), ('https://google.ca', None)) + self.assertEqual(x('google.ca:80'), ('https://google.ca:80', + None)) + self.assertEqual(x('unreal.blargg'), ('unreal.blargg', + 'enter a valid URL')) + + # Now any URL requiring prepending will fail, but prepending is still + # enabled! + + x = IS_URL(allowed_schemes=['http']) + self.assertEqual(x('google.ca'), ('google.ca', 'enter a valid URL')) + + def testModeGeneric(self): + + # 'generic' mode + + x = IS_URL(mode='generic') + self.assertEqual(x('http://google.ca'), ('http://google.ca',None)) + self.assertEqual(x('google.ca'), ('google.ca', None)) + self.assertEqual(x('google.ca:80'), ('http://google.ca:80',None)) + self.assertEqual(x('blargg://unreal'), ('blargg://unreal', + 'enter a valid URL')) + + # 'generic' mode with custom allowed_schemes that still includes + # 'http' (the default for prepend_scheme) + + x = IS_URL(mode='generic', allowed_schemes=['http', 'blargg']) + self.assertEqual(x('http://google.ca'), ('http://google.ca', + None)) + self.assertEqual(x('ftp://google.ca'), ('ftp://google.ca', + 'enter a valid URL')) + self.assertEqual(x('google.ca'), ('google.ca', 'enter a valid URL')) + self.assertEqual(x('google.ca:80'), ('google.ca:80', + 'enter a valid URL')) + self.assertEqual(x('blargg://unreal'), ('blargg://unreal', + None)) + + # 'generic' mode with overriden prepend_scheme + + x = IS_URL(mode='generic', prepend_scheme='ftp') + self.assertEqual(x('http://google.ca'), ('http://google.ca', + None)) + self.assertEqual(x('ftp://google.ca'), ('ftp://google.ca', + None)) + self.assertEqual(x('google.ca'), ('google.ca', None)) + self.assertEqual(x('google.ca:80'), ('ftp://google.ca:80', + None)) + self.assertEqual(x('blargg://unreal'), ('blargg://unreal', + 'enter a valid URL')) + + # 'generic' mode with overriden allowed_schemes and prepend_scheme + + x = IS_URL(mode='generic', allowed_schemes=[None, 'ftp', 'ftps' + ], prepend_scheme='ftp') + self.assertEqual(x('http://google.ca'), ('http://google.ca', + 'enter a valid URL')) + self.assertEqual(x('google.ca'), ('google.ca', None)) + self.assertEqual(x('ftp://google.ca'), ('ftp://google.ca', + None)) + self.assertEqual(x('google.ca:80'), ('ftp://google.ca:80', + None)) + self.assertEqual(x('blargg://unreal'), ('blargg://unreal', + 'enter a valid URL')) + + # Now any URL requiring prepending will fail, but prepending is still + # enabled! + + x = IS_URL(mode='generic', allowed_schemes=['http']) + self.assertEqual(x('google.ca'), ('google.ca', 'enter a valid URL')) + + def testExceptionalUse(self): + + # mode must be in set ['http', 'generic'] + + try: + x = IS_URL(mode='ftp') + x('http://www.google.ca') + except Exception, e: + if str(e) != "invalid mode 'ftp' in IS_URL": + self.fail('Wrong exception: ' + str(e)) + else: + self.fail("Accepted invalid mode: 'ftp'") + + # allowed_schemes in 'http' mode must be in set [None, 'http', 'https'] + + try: + x = IS_URL(allowed_schemes=[None, 'ftp', 'ftps'], + prepend_scheme='ftp') + x('http://www.benn.ca') # we can only reasonably know about the + # error at calling time + except Exception, e: + if str(e)\ + != "allowed_scheme value 'ftp' is not in [None, 'http', 'https']": + self.fail('Wrong exception: ' + str(e)) + else: + self.fail("Accepted invalid allowed_schemes: [None, 'ftp', 'ftps']") + + # prepend_scheme's value must be in allowed_schemes (default for 'http' + # mode is [None, 'http', 'https']) + + try: + x = IS_URL(prepend_scheme='ftp') + x('http://www.benn.ca') # we can only reasonably know about the + # error at calling time + except Exception, e: + if str(e)\ + != "prepend_scheme='ftp' is not in allowed_schemes=[None, 'http', 'https']": + self.fail('Wrong exception: ' + str(e)) + else: + self.fail("Accepted invalid prepend_scheme: 'ftp'") + + # custom allowed_schemes that excludes 'http', so prepend_scheme must be + # specified! + + try: + x = IS_URL(allowed_schemes=[None, 'https']) + except Exception, e: + if str(e)\ + != "prepend_scheme='http' is not in allowed_schemes=[None, 'https']": + self.fail('Wrong exception: ' + str(e)) + else: + self.fail("Accepted invalid prepend_scheme: 'http'") + + # prepend_scheme must be in allowed_schemes + + try: + x = IS_URL(allowed_schemes=[None, 'http'], + prepend_scheme='https') + except Exception, e: + if str(e)\ + != "prepend_scheme='https' is not in allowed_schemes=[None, 'http']": + self.fail('Wrong exception: ' + str(e)) + else: + self.fail("Accepted invalid prepend_scheme: 'https'") + + # prepend_scheme's value (default is 'http') must be in allowed_schemes + + try: + x = IS_URL(mode='generic', allowed_schemes=[None, 'ftp', + 'ftps']) + except Exception, e: + if str(e)\ + != "prepend_scheme='http' is not in allowed_schemes=[None, 'ftp', 'ftps']": + self.fail('Wrong exception: ' + str(e)) + else: + self.fail("Accepted invalid prepend_scheme: 'http'") + + # prepend_scheme's value must be in allowed_schemes, which by default + # is all schemes that really exist + + try: + x = IS_URL(mode='generic', prepend_scheme='blargg') + x('http://www.google.ca') # we can only reasonably know about the error at calling time + except Exception, e: + if not str(e).startswith( + "prepend_scheme='blargg' is not in allowed_schemes="): + self.fail('Wrong exception: ' + str(e)) + else: + self.fail("Accepted invalid prepend_scheme: 'blargg'") + + # prepend_scheme's value must be in allowed_schemes + + try: + x = IS_URL(mode='generic', allowed_schemes=[None, 'http'], + prepend_scheme='blargg') + except Exception, e: + if str(e)\ + != "prepend_scheme='blargg' is not in allowed_schemes=[None, 'http']": + self.fail('Wrong exception: ' + str(e)) + else: + self.fail("Accepted invalid prepend_scheme: 'blargg'") + + # Not inluding None in the allowed_schemes essentially disabled + # prepending, so even though + # prepend_scheme has the invalid value 'http', we don't care! + + x = IS_URL(allowed_schemes=['https'], prepend_scheme='https') + self.assertEqual(x('google.ca'), ('google.ca', 'enter a valid URL')) + + # Not inluding None in the allowed_schemes essentially disabled prepending, so even though + # prepend_scheme has the invalid value 'http', we don't care! + + x = IS_URL(mode='generic', allowed_schemes=['https'], + prepend_scheme='https') + self.assertEqual(x('google.ca'), ('google.ca', 'enter a valid URL')) + + +# ############################################################################## + + +class TestIsGenericUrl(unittest.TestCase): + + x = IS_GENERIC_URL() + + def testInvalidUrls(self): + urlsToCheckA = [] + for i in range(0, 32) + [127]: + + # Control characters are disallowed in any part of a URL + + urlsToCheckA.append('http://www.benn' + chr(i) + '.ca') + + urlsToCheckB = [ + None, + '', + 'http://www.no spaces allowed.com', + 'http://www.benn.ca/no spaces allowed/', + 'http://www.benn.ca/angle_bracket/', + 'http://www.benn.ca/invalid%character', + 'http://www.benn.ca/illegal%%20use', + 'http://www.benn.ca/illegaluse%', + 'http://www.benn.ca/illegaluse%0', + 'http://www.benn.ca/illegaluse%x', + 'http://www.benn.ca/ill%egaluse%x', + 'http://www.benn.ca/double"quote/', + 'http://www.curly{brace.com', + 'http://www.benn.ca/curly}brace/', + 'http://www.benn.ca/or|symbol/', + 'http://www.benn.ca/back\slash', + 'http://www.benn.ca/the^carat', + 'http://left[bracket.me', + 'http://www.benn.ca/right]bracket', + 'http://www.benn.ca/angle`quote', + '-ttp://www.benn.ca', + '+ttp://www.benn.ca', + '.ttp://www.benn.ca', + '9ttp://www.benn.ca', + 'ht;tp://www.benn.ca', + 'ht@tp://www.benn.ca', + 'ht&tp://www.benn.ca', + 'ht=tp://www.benn.ca', + 'ht$tp://www.benn.ca', + 'ht,tp://www.benn.ca', + 'ht:tp://www.benn.ca', + 'htp://invalid_scheme.com', + ] + + failures = [] + + for url in urlsToCheckA + urlsToCheckB: + if self.x(url)[1] == None: + failures.append('Incorrectly accepted: ' + str(url)) + + if len(failures) > 0: + self.fail(failures) + + def testValidUrls(self): + urlsToCheck = [ + 'ftp://ftp.is.co.za/rfc/rfc1808.txt', + 'gopher://spinaltap.micro.umn.edu/00/Weather/California/Los%20Angeles', + 'http://www.math.uio.no/faq/compression-faq/part1.html', + 'mailto:mduerst@ifi.unizh.ch', + 'news:comp.infosystems.www.servers.unix', + 'telnet://melvyl.ucop.edu/', + 'hTTp://www.benn.ca', + '%66%74%70://ftp.is.co.za/rfc/rfc1808.txt', + '%46%74%70://ftp.is.co.za/rfc/rfc1808.txt', + '/faq/compression-faq/part1.html', + 'google.com', + 'www.google.com:8080', + '128.127.123.250:8080', + 'blargg:ping', + 'http://www.benn.ca', + 'http://benn.ca', + 'http://amazon.com/books/', + 'https://amazon.com/movies', + 'rtsp://idontknowthisprotocol', + 'HTTP://allcaps.com', + 'http://localhost', + 'http://localhost#fragment', + 'http://localhost/hello', + 'http://localhost/hello?query=True', + 'http://localhost/hello/', + 'http://localhost:8080', + 'http://localhost:8080/', + 'http://localhost:8080/hello', + 'http://localhost:8080/hello/', + 'file:///C:/Documents%20and%20Settings/Jonathan/Desktop/view.py' + , + ] + + failures = [] + + for url in urlsToCheck: + if self.x(url)[1] != None: + failures.append('Incorrectly rejected: ' + str(url)) + + if len(failures) > 0: + self.fail(failures) + + def testPrepending(self): + # Does not prepend scheme for abbreviated domains + self.assertEqual(self.x('google.ca'), ('google.ca', None)) + + # Does not prepend scheme for abbreviated domains + self.assertEqual(self.x('google.ca:8080'), ('google.ca:8080', None)) + + # Does not prepend when scheme already exists + self.assertEqual(self.x('https://google.ca'), + ('https://google.ca', None)) + + # Does not prepend if None type is not specified in allowed_scheme, + # because a scheme is required + + y = IS_GENERIC_URL(allowed_schemes=['http', 'blargg'], + prepend_scheme='http') + self.assertEqual(y('google.ca'), ('google.ca', 'enter a valid URL')) + + +# ############################################################################## + + +class TestIsHttpUrl(unittest.TestCase): + + x = IS_HTTP_URL() + + def testInvalidUrls(self): + urlsToCheck = [ + None, + '', + 'http://invalid' + chr(2) + '.com', + 'htp://invalid_scheme.com', + 'blargg://invalid_scheme.com', + 'http://-123.com', + 'http://abcd-.ca', + 'http://-abc123-.me', + 'http://www.dom&ain.com/', + 'http://www.dom=ain.com/', + 'http://www.benn.ca&', + 'http://%62%65%6E%6E%2E%63%61/path', + 'http://.domain.com', + 'http://.domain.com./path', + 'http://domain..com', + 'http://domain...at..com', + 'http://domain.com..', + 'http://domain.com../path', + 'http://domain.3m', + 'http://domain.-3m', + 'http://domain.3m-', + 'http://domain.-3m-', + 'http://domain.co&m', + 'http://domain.m3456', + 'http://domain.m-3/path#fragment', + 'http://domain.m---k/path?query=value', + 'http://23.32..', + 'http://23..32.56.0', + 'http://38997.222.999', + 'http://23.32.56.99.', + 'http://.23.32.56.99', + 'http://.23.32.56.99.', + 'http://w127.123.0.256:8080', + 'http://23.32.56.99:abcd', + 'http://23.32.56.99:23cd', + 'http://google.com:cd22', + 'http://23.32:1300.56.99', + 'http://www.yahoo:1600.com', + 'path/segment/without/starting/slash', + 'http://www.math.uio.no;param=3', + '://ABC.com:/%7esmith/home.html', + ] + + failures = [] + + for url in urlsToCheck: + if self.x(url)[1] == None: + failures.append('Incorrectly accepted: ' + str(url)) + + if len(failures) > 0: + self.fail(failures) + + def testValidUrls(self): + + urlsToCheck = [ + 'http://abc.com:80/~smith/home.html', + 'http://ABC.com/%7Esmith/home.html', + 'http://ABC.com:/%7esmith/home.html', + 'http://www.math.uio.no/faq/compression-faq/part1.html', + '//google.ca/faq/compression-faq/part1.html', + '//google.ca/faq;param=3', + '//google.ca/faq/index.html?query=5', + '//google.ca/faq/index.html;param=value?query=5', + '/faq/compression-faq/part1.html', + '/faq;param=3', + '/faq/index.html?query=5', + '/faq/index.html;param=value?query=5', + 'google.com', + 'benn.ca/init/default', + 'benn.ca/init;param=value/default?query=value', + 'http://host-name---with-dashes.me', + 'http://www.host-name---with-dashes.me', + 'http://a.com', + 'http://a.3.com', + 'http://a.bl-ck.com', + 'http://bl-e.b.com', + 'http://host123with456numbers.ca', + 'http://1234567890.com.', + 'http://1234567890.com./path', + 'http://google.com./path', + 'http://domain.xn--0zwm56d', + 'http://127.123.0.256', + 'http://127.123.0.256/document/drawer', + '127.123.0.256/document/', + '156.212.123.100', + 'http://www.google.com:180200', + 'http://www.google.com:8080/path', + 'http://www.google.com:8080', + '//www.google.com:8080', + 'www.google.com:8080', + 'http://127.123.0.256:8080/path', + '//127.123.0.256:8080', + '127.123.0.256:8080', + 'http://example.me??query=value?', + 'http://a.com', + 'http://3.com', + 'http://www.benn.ca', + 'http://benn.ca', + 'http://amazon.com/books/', + 'https://amazon.com/movies', + 'hTTp://allcaps.com', + 'http://localhost', + 'HTTPS://localhost.', + 'http://localhost#fragment', + 'http://localhost/hello;param=value', + 'http://localhost/hello;param=value/hi;param2=value2;param3=value3' + , + 'http://localhost/hello?query=True', + 'http://www.benn.ca/hello;param=value/hi;param2=value2;param3=value3/index.html?query=3', + 'http://localhost/hello/?query=1500&five=6', + 'http://localhost:8080', + 'http://localhost:8080/', + 'http://localhost:8080/hello', + 'http://localhost:8080/hello%20world/', + 'http://www.a.3.be-nn.5.ca', + 'http://www.amazon.COM', + ] + + failures = [] + + for url in urlsToCheck: + if self.x(url)[1] != None: + failures.append('Incorrectly rejected: ' + str(url)) + + if len(failures) > 0: + self.fail(failures) + + def testPrepending(self): + # prepends scheme for abbreviated domains + self.assertEqual(self.x('google.ca'), ('http://google.ca', None)) + + # prepends scheme for abbreviated domains + self.assertEqual(self.x('google.ca:8080'), + ('http://google.ca:8080', None)) + + # does not prepend when scheme already exists + self.assertEqual(self.x('https://google.ca'), + ('https://google.ca', None)) + + y = IS_HTTP_URL(prepend_scheme='https', allowed_schemes=[None, 'https']) + self.assertEqual(y('google.ca'), ('https://google.ca', None)) # prepends https if asked + + z = IS_HTTP_URL(prepend_scheme=None) + self.assertEqual(z('google.ca:8080'), ('google.ca:8080', None)) # prepending disabled + + try: + IS_HTTP_URL(prepend_scheme='mailto') + except Exception, e: + if str(e)\ + != "prepend_scheme='mailto' is not in allowed_schemes=[None, 'http', 'https']": + self.fail('Wrong exception: ' + str(e)) + else: + self.fail("Got invalid prepend_scheme: 'mailto'") + + # Does not prepend if None type is not specified in allowed_scheme, because a scheme is required + + a = IS_HTTP_URL(allowed_schemes=['http']) + self.assertEqual(a('google.ca'), ('google.ca', 'enter a valid URL')) + self.assertEqual(a('google.ca:80'), ('google.ca:80', + 'enter a valid URL')) + +class TestUnicode(unittest.TestCase): + x = IS_URL() + y = IS_URL(allowed_schemes=['https'], prepend_scheme='https') #excludes the option for abbreviated URLs with no scheme + z = IS_URL(prepend_scheme=None) # disables prepending the scheme in the return value + + + def testUnicodeToAsciiUrl(self): + self.assertEquals(unicode_to_ascii_authority(u'www.Alliancefran\xe7aise.nu'), 'www.xn--alliancefranaise-npb.nu') + self.assertEquals(unicode_to_ascii_authority(u'www.benn.ca'), 'www.benn.ca') + self.assertRaises(UnicodeError, unicode_to_ascii_authority, u'\u4e2d'*1000) #label is too long + + + def testValidUrls(self): + self.assertEquals(self.x(u'www.Alliancefrancaise.nu'), ('http://www.Alliancefrancaise.nu', None)) + self.assertEquals(self.x(u'www.Alliancefran\xe7aise.nu'), ('http://www.xn--alliancefranaise-npb.nu', None)) + self.assertEquals(self.x(u'www.Alliancefran\xe7aise.nu:8080'), ('http://www.xn--alliancefranaise-npb.nu:8080', None)) + self.assertEquals(self.x(u'http://www.Alliancefran\xe7aise.nu'), ('http://www.xn--alliancefranaise-npb.nu', None)) + self.assertEquals(self.x(u'http://www.Alliancefran\xe7aise.nu/parnaise/blue'), ('http://www.xn--alliancefranaise-npb.nu/parnaise/blue', None)) + self.assertEquals(self.x(u'http://www.Alliancefran\xe7aise.nu/parnaise/blue#fragment'), ('http://www.xn--alliancefranaise-npb.nu/parnaise/blue#fragment', None)) + self.assertEquals(self.x(u'http://www.Alliancefran\xe7aise.nu/parnaise/blue?query=value#fragment'), ('http://www.xn--alliancefranaise-npb.nu/parnaise/blue?query=value#fragment', None)) + self.assertEquals(self.x(u'http://www.Alliancefran\xe7aise.nu:8080/parnaise/blue?query=value#fragment'), ('http://www.xn--alliancefranaise-npb.nu:8080/parnaise/blue?query=value#fragment', None)) + self.assertEquals(self.x(u'www.Alliancefran\xe7aise.nu/parnaise/blue?query=value#fragment'), ('http://www.xn--alliancefranaise-npb.nu/parnaise/blue?query=value#fragment', None)) + self.assertEquals(self.x(u'http://\u4e2d\u4fd4.com'), ('http://xn--fiq13b.com', None)) + self.assertEquals(self.x(u'http://\u4e2d\u4fd4.com/\u4e86'), ('http://xn--fiq13b.com/%4e%86', None)) + self.assertEquals(self.x(u'http://\u4e2d\u4fd4.com/\u4e86?query=\u4e86'), ('http://xn--fiq13b.com/%4e%86?query=%4e%86', None)) + self.assertEquals(self.x(u'http://\u4e2d\u4fd4.com/\u4e86?query=\u4e86#fragment'), ('http://xn--fiq13b.com/%4e%86?query=%4e%86#fragment', None)) + self.assertEquals(self.x(u'http://\u4e2d\u4fd4.com?query=\u4e86#fragment'), ('http://xn--fiq13b.com?query=%4e%86#fragment', None)) + self.assertEquals(self.x(u'http://B\xfccher.ch'), ('http://xn--bcher-kva.ch', None)) + self.assertEquals(self.x(u'http://\xe4\xf6\xfc\xdf.com'), ('http://xn--ss-uia6e4a.com', None)) + self.assertEquals(self.x(u'http://visegr\xe1d.com'), ('http://xn--visegrd-mwa.com', None)) + self.assertEquals(self.x(u'http://h\xe1zipatika.com'), ('http://xn--hzipatika-01a.com', None)) + self.assertEquals(self.x(u'http://www.\xe7ukurova.com'), ('http://www.xn--ukurova-txa.com', None)) + self.assertEquals(self.x(u'http://nixier\xf6hre.nixieclock-tube.com'), ('http://xn--nixierhre-57a.nixieclock-tube.com', None)) + self.assertEquals(self.x(u'google.ca.'), ('http://google.ca.', None)) + + self.assertEquals(self.y(u'https://google.ca'), ('https://google.ca', None)) + self.assertEquals(self.y(u'https://\u4e2d\u4fd4.com'), ('https://xn--fiq13b.com', None)) + + self.assertEquals(self.z(u'google.ca'), ('google.ca', None)) + + + def testInvalidUrls(self): + self.assertEquals(self.x(u'://ABC.com'), (u'://ABC.com', 'enter a valid URL')) + self.assertEquals(self.x(u'http://\u4e2d\u4fd4.dne'), (u'http://\u4e2d\u4fd4.dne', 'enter a valid URL')) + self.assertEquals(self.x(u'https://google.dne'), (u'https://google.dne', 'enter a valid URL')) + self.assertEquals(self.x(u'https://google..ca'), (u'https://google..ca', 'enter a valid URL')) + self.assertEquals(self.x(u'google..ca'), (u'google..ca', 'enter a valid URL')) + self.assertEquals(self.x(u'http://' + u'\u4e2d'*1000 + u'.com'), (u'http://' + u'\u4e2d'*1000 + u'.com', 'enter a valid URL')) + + self.assertEquals(self.x(u'http://google.com#fragment_\u4e86'), (u'http://google.com#fragment_\u4e86', 'enter a valid URL')) + self.assertEquals(self.x(u'http\u4e86://google.com'), (u'http\u4e86://google.com', 'enter a valid URL')) + self.assertEquals(self.x(u'http\u4e86://google.com#fragment_\u4e86'), (u'http\u4e86://google.com#fragment_\u4e86', 'enter a valid URL')) + + self.assertEquals(self.y(u'http://\u4e2d\u4fd4.com/\u4e86'), (u'http://\u4e2d\u4fd4.com/\u4e86', 'enter a valid URL')) + #self.assertEquals(self.y(u'google.ca'), (u'google.ca', 'enter a valid URL')) + + self.assertEquals(self.z(u'invalid.domain..com'), (u'invalid.domain..com', 'enter a valid URL')) + self.assertEquals(self.z(u'invalid.\u4e2d\u4fd4.blargg'), (u'invalid.\u4e2d\u4fd4.blargg', 'enter a valid URL')) + +# ############################################################################## + +if __name__ == '__main__': + unittest.main() ADDED gluon/tests/test_router.py Index: gluon/tests/test_router.py ================================================================== --- gluon/tests/test_router.py +++ gluon/tests/test_router.py @@ -0,0 +1,853 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +"""Unit tests for rewrite.py routers option""" + +import sys +import os +import unittest +import tempfile +import logging + +if os.path.isdir('gluon'): + sys.path.append(os.path.realpath('gluon')) # running from web2py base +else: + sys.path.append(os.path.realpath('../')) # running from gluon/tests/ + +from rewrite import load, filter_url, filter_err, get_effective_router, map_url_out +from html import URL +from fileutils import abspath +from settings import global_settings +from http import HTTP +from storage import Storage + +logger = None +oldcwd = None +root = None + +def setUpModule(): + def make_apptree(): + "build a temporary applications tree" + # applications/ + os.mkdir(abspath('applications')) + # applications/app/ + for app in ('admin', 'examples', 'welcome'): + os.mkdir(abspath('applications', app)) + # applications/app/(controllers, static) + for subdir in ('controllers', 'static'): + os.mkdir(abspath('applications', app, subdir)) + # applications/admin/controllers/*.py + for ctr in ('appadmin', 'default', 'gae', 'mercurial', 'shell', 'wizard'): + open(abspath('applications', 'admin', 'controllers', '%s.py' % ctr), 'w').close() + # applications/examples/controllers/*.py + for ctr in ('ajax_examples', 'appadmin', 'default', 'global', 'spreadsheet'): + open(abspath('applications', 'examples', 'controllers', '%s.py' % ctr), 'w').close() + # applications/welcome/controllers/*.py + for ctr in ('appadmin', 'default'): + open(abspath('applications', 'welcome', 'controllers', '%s.py' % ctr), 'w').close() + # create an app-specific routes.py for examples app + routes = open(abspath('applications', 'examples', 'routes.py'), 'w') + routes.write("routers=dict(examples=dict(default_function='exdef'))") + routes.close() + # create language files for examples app + for lang in ('en', 'it'): + os.mkdir(abspath('applications', 'examples', 'static', lang)) + open(abspath('applications', 'examples', 'static', lang, 'file'), 'w').close() + + global oldcwd + if oldcwd is None: # do this only once + oldcwd = os.getcwd() + if not os.path.isdir('gluon'): + os.chdir(os.path.realpath('../../')) # run from web2py base directory + import main # for initialization after chdir + global logger + logger = logging.getLogger('web2py.rewrite') + global_settings.applications_parent = tempfile.mkdtemp() + global root + root = global_settings.applications_parent + make_apptree() + +def tearDownModule(): + global oldcwd + if oldcwd is not None: + os.chdir(oldcwd) + oldcwd = None + + +class TestRouter(unittest.TestCase): + """ Tests the routers logic from gluon.rewrite """ + + def test_router_syntax(self): + """ Test router syntax error """ + level = logger.getEffectiveLevel() + logger.setLevel(logging.CRITICAL) # disable logging temporarily + self.assertRaises(SyntaxError, load, data='x:y') + self.assertRaises(SyntaxError, load, rdict=dict(BASE=dict(badkey="value"))) + self.assertRaises(SyntaxError, load, rdict=dict(BASE=dict(), app=dict(default_application="name"))) + try: + # 2.7+ only + self.assertRaisesRegexp(SyntaxError, "invalid syntax", + load, data='x:y') + self.assertRaisesRegexp(SyntaxError, "unknown key", + load, rdict=dict(BASE=dict(badkey="value"))) + self.assertRaisesRegexp(SyntaxError, "BASE-only key", + load, rdict=dict(BASE=dict(), app=dict(default_application="name"))) + except AttributeError: + pass + logger.setLevel(level) + + def test_router_null(self): + """ Tests the null router """ + load(rdict=dict()) + # app resolution + self.assertEqual(filter_url('http://domain.com/welcome', app=True), 'welcome') + self.assertEqual(filter_url('http://domain.com/', app=True), 'init') + # incoming + self.assertEqual(filter_url('http://domain.com/favicon.ico'), '%s/applications/init/static/favicon.ico' % root) + self.assertEqual(filter_url('http://domain.com/abc'), '/init/default/abc') + self.assertEqual(filter_url('http://domain.com/index/abc'), "/init/default/index ['abc']") + self.assertEqual(filter_url('http://domain.com/abc/def'), "/init/default/abc ['def']") + self.assertEqual(filter_url('http://domain.com/index/a%20bc'), "/init/default/index ['a bc']") + self.assertEqual(filter_url('http://domain.com/welcome/static/path/to/static'), "%s/applications/welcome/static/path/to/static" % root) + self.assertRaises(HTTP, filter_url, 'http://domain.com/welcome/static/bad/path/to/st~tic') + try: + # 2.7+ only + self.assertRaisesRegexp(HTTP, "400.*invalid static file", filter_url, 'http://domain.com/welcome/static/bad/path/to/st~tic') + except AttributeError: + pass + # outgoing + self.assertEqual(filter_url('http://domain.com/init/default/index', out=True), '/') + self.assertEqual(filter_url('http://domain.com/init/default/index/arg1', out=True), '/index/arg1') + self.assertEqual(filter_url('http://domain.com/init/default/abc', out=True), '/abc') + self.assertEqual(filter_url('http://domain.com/init/static/abc', out=True), '/init/static/abc') + self.assertEqual(filter_url('http://domain.com/init/appadmin/index', out=True), '/appadmin') + self.assertEqual(filter_url('http://domain.com/init/appadmin/abc', out=True), '/appadmin/abc') + self.assertEqual(filter_url('http://domain.com/init/admin/index', out=True), '/init/admin') + self.assertEqual(filter_url('http://domain.com/init/admin/abc', out=True), '/init/admin/abc') + self.assertEqual(filter_url('http://domain.com/admin/default/abc', out=True), '/admin/abc') + + def test_router_specific(self): + """ + Test app-specific routes.py + + Note that make_apptree above created applications/examples/routes.py with a default_function. + """ + load(rdict=dict()) + self.assertEqual(filter_url('http://domain.com/welcome'), '/welcome/default/index') + self.assertEqual(filter_url('http://domain.com/examples'), '/examples/default/exdef') + + def test_router_defapp(self): + """ Test the default-application function """ + routers = dict(BASE=dict(default_application='welcome')) + load(rdict=routers) + # app resolution + self.assertEqual(filter_url('http://domain.com/welcome', app=True), 'welcome') + self.assertEqual(filter_url('http://domain.com/', app=True), 'welcome') + # incoming + self.assertEqual(filter_url('http://domain.com'), '/welcome/default/index') + self.assertEqual(filter_url('http://domain.com/'), '/welcome/default/index') + self.assertEqual(filter_url('http://domain.com/appadmin'), '/welcome/appadmin/index') + self.assertEqual(filter_url('http://domain.com/abc'), '/welcome/default/abc') + self.assertEqual(filter_url('http://domain.com/index/abc'), "/welcome/default/index ['abc']") + self.assertEqual(filter_url('http://domain.com/abc/def'), "/welcome/default/abc ['def']") + self.assertEqual(filter_url('http://domain.com/favicon.ico'), '%s/applications/welcome/static/favicon.ico' % root) + self.assertEqual(filter_url('http://domain.com/static/abc'), '%s/applications/welcome/static/abc' % root) + self.assertEqual(filter_url('http://domain.com/static/path/to/static'), "%s/applications/welcome/static/path/to/static" % root) + # outgoing + self.assertEqual(filter_url('http://domain.com/welcome/default/index', out=True), '/') + self.assertEqual(filter_url('http://domain.com/welcome/default/index/arg1', out=True), '/index/arg1') + self.assertEqual(filter_url('http://domain.com/welcome/default/abc', out=True), '/abc') + self.assertEqual(filter_url('http://domain.com/welcome/default/admin', out=True), '/default/admin') + self.assertEqual(filter_url('http://domain.com/welcome/static/abc', out=True), + '/welcome/static/abc') + self.assertEqual(filter_url('http://domain.com/welcome/appadmin/index', out=True), '/appadmin') + self.assertEqual(filter_url('http://domain.com/welcome/appadmin/abc', out=True), '/appadmin/abc') + self.assertEqual(filter_url('http://domain.com/welcome/admin/index', out=True), '/welcome/admin') + self.assertEqual(filter_url('http://domain.com/welcome/admin/abc', out=True), '/welcome/admin/abc') + self.assertEqual(filter_url('http://domain.com/admin/default/abc', out=True), '/admin/abc') + + def test_router_nodef(self): + """ Test no-default functions """ + routers = dict( + BASE=dict(default_application='welcome'), + welcome=dict(controllers=None), + ) + load(rdict=routers) + # outgoing + self.assertEqual(filter_url('http://domain.com/welcome/default/index', out=True), '/default') + self.assertEqual(filter_url('http://domain.com/welcome/default/index/arg1', out=True), '/default/index/arg1') + self.assertEqual(filter_url('http://domain.com/welcome/default/abc', out=True), '/default/abc') + self.assertEqual(filter_url('http://domain.com/welcome/static/abc', out=True), + '/welcome/static/abc') + self.assertEqual(filter_url('http://domain.com/welcome/appadmin/index', out=True), '/appadmin') + self.assertEqual(filter_url('http://domain.com/welcome/appadmin/abc', out=True), '/appadmin/abc') + self.assertEqual(filter_url('http://domain.com/welcome/admin/index', out=True), '/welcome/admin') + self.assertEqual(filter_url('http://domain.com/welcome/admin/abc', out=True), '/welcome/admin/abc') + self.assertEqual(filter_url('http://domain.com/admin/default/abc', out=True), '/admin/abc') + # incoming + self.assertEqual(filter_url('http://domain.com'), '/welcome/default/index') + self.assertEqual(filter_url('http://domain.com/'), '/welcome/default/index') + self.assertEqual(filter_url('http://domain.com/appadmin'), '/welcome/appadmin/index') + self.assertEqual(filter_url('http://domain.com/abc'), '/welcome/abc/index') + self.assertEqual(filter_url('http://domain.com/index/abc'), "/welcome/index/abc") + self.assertEqual(filter_url('http://domain.com/abc/def'), "/welcome/abc/def") + self.assertEqual(filter_url('http://domain.com/abc/def/ghi'), "/welcome/abc/def ['ghi']") + + routers = dict( + BASE=dict(default_application=None), + ) + load(rdict=routers) + # outgoing + self.assertEqual(filter_url('http://domain.com/welcome/default/index', out=True), '/welcome') + self.assertEqual(filter_url('http://domain.com/welcome/default/index/arg1', out=True), '/welcome/index/arg1') + self.assertEqual(filter_url('http://domain.com/welcome/default/abc', out=True), '/welcome/abc') + self.assertEqual(filter_url('http://domain.com/welcome/static/abc', out=True), '/welcome/static/abc') + self.assertEqual(filter_url('http://domain.com/welcome/appadmin/index', out=True), '/welcome/appadmin') + self.assertEqual(filter_url('http://domain.com/welcome/appadmin/abc', out=True), '/welcome/appadmin/abc') + self.assertEqual(filter_url('http://domain.com/welcome/admin/index', out=True), '/welcome/admin') + self.assertEqual(filter_url('http://domain.com/welcome/admin/abc', out=True), '/welcome/admin/abc') + self.assertEqual(filter_url('http://domain.com/admin/default/abc', out=True), '/admin/abc') + # incoming + self.assertRaises(HTTP, filter_url, 'http://domain.com') + self.assertRaises(HTTP, filter_url, 'http://domain.com/appadmin') + try: + # 2.7+ only + self.assertRaisesRegexp(HTTP, "400.*invalid application", filter_url, 'http://domain.com') + self.assertRaisesRegexp(HTTP, "400.*invalid application", filter_url, 'http://domain.com/appadmin') + except AttributeError: + pass + + routers = dict( + BASE=dict(default_application='welcome', applications=None), + ) + load(rdict=routers) + # outgoing + self.assertEqual(filter_url('http://domain.com/welcome/default/index', out=True), '/welcome') + self.assertEqual(filter_url('http://domain.com/welcome/default/index/arg1', out=True), '/welcome/index/arg1') + self.assertEqual(filter_url('http://domain.com/welcome/default/abc', out=True), '/welcome/abc') + self.assertEqual(filter_url('http://domain.com/welcome/static/abc', out=True), '/welcome/static/abc') + self.assertEqual(filter_url('http://domain.com/welcome/appadmin/index', out=True), '/welcome/appadmin') + self.assertEqual(filter_url('http://domain.com/welcome/appadmin/abc', out=True), '/welcome/appadmin/abc') + self.assertEqual(filter_url('http://domain.com/welcome/admin/index', out=True), '/welcome/admin') + self.assertEqual(filter_url('http://domain.com/welcome/admin/abc', out=True), '/welcome/admin/abc') + self.assertEqual(filter_url('http://domain.com/admin/default/abc', out=True), '/admin/abc') + # incoming + self.assertEqual(filter_url('http://domain.com'), '/welcome/default/index') + self.assertEqual(filter_url('http://domain.com/'), '/welcome/default/index') + self.assertRaises(HTTP, filter_url, 'http://domain.com/appadmin') + try: + # 2.7+ only + self.assertRaisesRegexp(HTTP, "400.*unknown application: 'appadmin'", filter_url, 'http://domain.com/appadmin') + except AttributeError: + pass + + routers = dict( + BASE=dict(default_application='welcome', applications=None), + welcome=dict(controllers=None), + ) + load(rdict=routers) + # outgoing + self.assertEqual(filter_url('http://domain.com/welcome/default/index', out=True), '/welcome/default') + self.assertEqual(filter_url('http://domain.com/welcome/default/index/arg1', out=True), '/welcome/default/index/arg1') + self.assertEqual(filter_url('http://domain.com/welcome/default/abc', out=True), '/welcome/default/abc') + self.assertEqual(filter_url('http://domain.com/welcome/static/abc', out=True), '/welcome/static/abc') + self.assertEqual(filter_url('http://domain.com/welcome/appadmin/index', out=True), '/welcome/appadmin') + self.assertEqual(filter_url('http://domain.com/welcome/appadmin/abc', out=True), '/welcome/appadmin/abc') + self.assertEqual(filter_url('http://domain.com/welcome/admin/index', out=True), '/welcome/admin') + self.assertEqual(filter_url('http://domain.com/welcome/admin/abc', out=True), '/welcome/admin/abc') + self.assertEqual(filter_url('http://domain.com/admin/default/abc', out=True), '/admin/abc') + # incoming + self.assertEqual(filter_url('http://domain.com'), '/welcome/default/index') + self.assertEqual(filter_url('http://domain.com/'), '/welcome/default/index') + self.assertRaises(HTTP, filter_url, 'http://domain.com/appadmin') + try: + # 2.7+ only + self.assertRaisesRegexp(HTTP, "400.*unknown application: 'appadmin'", filter_url, 'http://domain.com/appadmin') + except AttributeError: + pass + + routers = dict( + BASE=dict(default_application='welcome', applications=None), + welcome=dict(default_controller=None), + ) + load(rdict=routers) + # outgoing + self.assertEqual(filter_url('http://domain.com/welcome/default/index', out=True), '/welcome/default') + self.assertEqual(filter_url('http://domain.com/welcome/default/index/arg1', out=True), '/welcome/default/index/arg1') + self.assertEqual(filter_url('http://domain.com/welcome/default/abc', out=True), '/welcome/default/abc') + self.assertEqual(filter_url('http://domain.com/welcome/static/abc', out=True), '/welcome/static/abc') + self.assertEqual(filter_url('http://domain.com/welcome/appadmin/index', out=True), '/welcome/appadmin') + self.assertEqual(filter_url('http://domain.com/welcome/appadmin/abc', out=True), '/welcome/appadmin/abc') + self.assertEqual(filter_url('http://domain.com/welcome/admin/index', out=True), '/welcome/admin') + self.assertEqual(filter_url('http://domain.com/welcome/admin/abc', out=True), '/welcome/admin/abc') + self.assertEqual(filter_url('http://domain.com/admin/default/abc', out=True), '/admin/abc') + # incoming + self.assertRaises(HTTP, filter_url, 'http://domain.com') + self.assertRaises(HTTP, filter_url, 'http://domain.com/appadmin') + try: + # 2.7+ only + self.assertRaisesRegexp(HTTP, "400.*invalid controller", filter_url, 'http://domain.com') + self.assertRaisesRegexp(HTTP, "400.*unknown application: 'appadmin'", filter_url, 'http://domain.com/appadmin') + except AttributeError: + pass + + routers = dict( + BASE=dict(default_application='welcome', applications=None), + welcome=dict(controllers=None, default_function=None), + ) + load(rdict=routers) + # outgoing + self.assertEqual(filter_url('http://domain.com/welcome/default/index', out=True), '/welcome/default/index') + self.assertEqual(filter_url('http://domain.com/welcome/default/index/arg1', out=True), '/welcome/default/index/arg1') + self.assertEqual(filter_url('http://domain.com/welcome/default/abc', out=True), '/welcome/default/abc') + self.assertEqual(filter_url('http://domain.com/welcome/static/abc', out=True), '/welcome/static/abc') + self.assertEqual(filter_url('http://domain.com/welcome/appadmin/index', out=True), '/welcome/appadmin/index') + self.assertEqual(filter_url('http://domain.com/welcome/appadmin/abc', out=True), '/welcome/appadmin/abc') + self.assertEqual(filter_url('http://domain.com/welcome/admin/index', out=True), '/welcome/admin/index') + self.assertEqual(filter_url('http://domain.com/welcome/admin/abc', out=True), '/welcome/admin/abc') + self.assertEqual(filter_url('http://domain.com/admin/default/abc', out=True), '/admin/abc') + # incoming + self.assertRaises(HTTP, filter_url, 'http://domain.com') + self.assertRaises(HTTP, filter_url, 'http://domain.com/appadmin') + try: + # 2.7+ only + self.assertRaisesRegexp(HTTP, "400.*invalid function", filter_url, 'http://domain.com') + self.assertRaisesRegexp(HTTP, "400.*unknown application: 'appadmin'", filter_url, 'http://domain.com/appadmin') + except AttributeError: + pass + + def test_router_app(self): + """ Tests the doctest router app resolution""" + routers = dict( + BASE = dict( + domains = { + "domain1.com" : "app1", + "www.domain1.com" : "app1", + "domain2.com" : "app2", + }, + ), + app1 = dict(), + app2 = dict(), + goodapp = dict(), + ) + routers['bad!app'] = dict() + load(rdict=routers) + self.assertEqual(filter_url('http://domain.com/welcome', app=True), 'welcome') + self.assertEqual(filter_url('http://domain.com/welcome/', app=True), 'welcome') + self.assertEqual(filter_url('http://domain.com', app=True), 'init') + self.assertEqual(filter_url('http://domain.com/', app=True), 'init') + self.assertEqual(filter_url('http://domain.com/abc', app=True), 'init') + self.assertEqual(filter_url('http://domain1.com/abc', app=True), 'app1') + self.assertEqual(filter_url('http://www.domain1.com/abc', app=True), 'app1') + self.assertEqual(filter_url('http://domain2.com/abc', app=True), 'app2') + self.assertEqual(filter_url('http://domain2.com/admin', app=True), 'admin') + + self.assertEqual(filter_url('http://domain.com/goodapp', app=True), 'goodapp') + self.assertRaises(HTTP, filter_url, 'http://domain.com/bad!app', app=True) + try: + # 2.7+ only + self.assertRaisesRegexp(HTTP, '400.*invalid application', filter_url, 'http://domain.com/bad!app') + except AttributeError: + pass + + routers['BASE']['domains']['domain3.com'] = 'app3' + self.assertRaises(SyntaxError, load, rdict=routers) + try: + # 2.7+ only + self.assertRaisesRegexp(SyntaxError, "unknown.*app3", load, rdict=routers) + except AttributeError: + pass + + def test_router_domains(self): + ''' + Test URLs that map domains + ''' + routers = dict( + BASE = dict( + applications = ['app1', 'app2', 'app2A', 'app3', 'app4', 'app5', 'app6'], + domains = { + # two domains to the same app + "domain1.com" : "app1", + "www.domain1.com" : "app1", + # same domain, two ports, to two apps + "domain2.com" : "app2a", + "domain2.com:8080" : "app2b", + # two domains, same app, two controllers + "domain3a.com" : "app3/c3a", + "domain3b.com" : "app3/c3b", + # http vs https + "domain6.com:80" : "app6", + "domain6.com:443" : "app6s", + }, + ), + app1 = dict( default_controller = 'c1', default_function = 'f1', controllers = ['c1'], exclusive_domain=True, ), + app2a = dict( default_controller = 'c2a', default_function = 'f2a', controllers = ['c2a'], ), + app2b = dict( default_controller = 'c2b', default_function = 'f2b', controllers = ['c2b'], ), + app3 = dict( controllers = ['c3a', 'c3b'], ), + app4 = dict( default_controller = 'c4', controllers = ['c4'], domain = 'domain4.com' ), + app5 = dict( default_controller = 'c5', controllers = ['c5'], domain = 'localhost' ), + app6 = dict( default_controller = 'c6', default_function = 'f6', controllers = ['c6'], ), + app6s = dict( default_controller = 'c6s', default_function = 'f6s', controllers = ['c6s'], ), + ) + + load(rdict=routers) + self.assertEqual(filter_url('http://domain1.com/abc'), '/app1/c1/abc') + self.assertEqual(filter_url('http://domain1.com/c1/abc'), '/app1/c1/abc') + self.assertEqual(filter_url('http://domain1.com/abc.html'), '/app1/c1/abc') + self.assertEqual(filter_url('http://domain1.com/abc.css'), '/app1/c1/abc.css') + self.assertEqual(filter_url('http://domain1.com/index/abc'), "/app1/c1/index ['abc']") + + self.assertEqual(filter_url('https://domain1.com/app1/ctr/fcn', domain=('app1',None), out=True), "/ctr/fcn") + self.assertEqual(filter_url('https://www.domain1.com/app1/ctr/fcn', domain=('app1',None), out=True), "/ctr/fcn") + + self.assertEqual(filter_url('http://domain2.com/abc'), '/app2a/c2a/abc') + self.assertEqual(filter_url('http://domain2.com:8080/abc'), '/app2b/c2b/abc') + + self.assertEqual(filter_url('http://domain2.com/app2a/ctr/fcn', domain=('app2a',None), out=True), "/ctr/fcn") + self.assertEqual(filter_url('http://domain2.com/app2a/ctr/f2a', domain=('app2a',None), out=True), "/ctr") + self.assertEqual(filter_url('http://domain2.com/app2a/c2a/f2a', domain=('app2a',None), out=True), "/") + self.assertEqual(filter_url('http://domain2.com/app2a/c2a/fcn', domain=('app2a',None), out=True), "/fcn") + self.assertEqual(filter_url('http://domain2.com/app2a/ctr/fcn', domain=('app2b',None), out=True), "/app2a/ctr/fcn") + self.assertEqual(filter_url('http://domain2.com/app2a/ctr/f2a', domain=('app2b',None), out=True), "/app2a/ctr") + self.assertEqual(filter_url('http://domain2.com/app2a/c2a/f2a', domain=('app2b',None), out=True), "/app2a") + + self.assertEqual(filter_url('http://domain3a.com/'), '/app3/c3a/index') + self.assertEqual(filter_url('http://domain3a.com/abc'), '/app3/c3a/abc') + self.assertEqual(filter_url('http://domain3a.com/c3b'), '/app3/c3b/index') + self.assertEqual(filter_url('http://domain3b.com/abc'), '/app3/c3b/abc') + + self.assertEqual(filter_url('http://domain3a.com/app3/c3a/fcn', domain=('app3','c3a'), out=True), "/fcn") + self.assertEqual(filter_url('http://domain3a.com/app3/c3a/fcn', domain=('app3','c3b'), out=True), "/c3a/fcn") + self.assertEqual(filter_url('http://domain3a.com/app3/c3a/fcn', domain=('app1',None), out=True), "/app3/c3a/fcn") + + self.assertEqual(filter_url('http://domain4.com/abc'), '/app4/c4/abc') + self.assertEqual(filter_url('https://domain4.com/app4/c4/fcn', domain=('app4',None), out=True), "/fcn") + + self.assertEqual(filter_url('http://localhost/abc'), '/app5/c5/abc') + self.assertEqual(filter_url('http:///abc'), '/app5/c5/abc') # test null host => localhost + self.assertEqual(filter_url('https://localhost/app5/c5/fcn', domain=('app5',None), out=True), "/fcn") + + self.assertEqual(filter_url('http://domain6.com'), '/app6/c6/f6') + self.assertEqual(filter_url('https://domain6.com'), '/app6s/c6s/f6s') + + self.assertEqual(filter_url('http://domain2.com/app3/c3a/f3', domain=('app2b',None), out=True), "/app3/c3a/f3") + self.assertRaises(SyntaxError, filter_url, 'http://domain1.com/app1/c1/f1', domain=('app2b',None), out=True) + try: + # 2.7+ only + self.assertRaisesRegexp(SyntaxError, 'cross-domain conflict', filter_url, 'http://domain1.com/app1/c1/f1', domain=('app2b',None), out=True) + except AttributeError: + pass + self.assertEqual(filter_url('http://domain1.com/app1/c1/f1', domain=('app2b',None), host='domain2.com', out=True), "/app1") + + def test_router_raise(self): + ''' + Test URLs that raise exceptions + ''' + # test non-exception variants + router_raise = dict( + init = dict( + controllers = [], + ), + welcome = dict( + map_hyphen = False, + ), + ) + load(rdict=router_raise) + self.assertEqual(filter_url('http://domain.com/ctl'), "/init/ctl/index") + self.assertEqual(filter_url('http://domain.com/default/fcn'), "/init/default/fcn") + self.assertEqual(filter_url('http://domain.com/default/fcn.ext'), "/init/default/fcn.ext") + self.assertEqual(filter_url('http://domain.com/default/fcn/arg'), "/init/default/fcn ['arg']") + # now raise-HTTP variants + self.assertRaises(HTTP, filter_url, 'http://domain.com/bad!ctl') + self.assertRaises(HTTP, filter_url, 'http://domain.com/ctl/bad!fcn') + self.assertRaises(HTTP, filter_url, 'http://domain.com/ctl/fcn.bad!ext') + self.assertRaises(HTTP, filter_url, 'http://domain.com/ctl/fcn/bad!arg') + try: + # 2.7+ only + self.assertRaisesRegexp(HTTP, '400.*invalid controller', filter_url, 'http://domain.com/init/bad!ctl') + self.assertRaisesRegexp(HTTP, '400.*invalid function', filter_url, 'http://domain.com/init/ctlr/bad!fcn') + self.assertRaisesRegexp(HTTP, '400.*invalid extension', filter_url, 'http://domain.com/init/ctlr/fcn.bad!ext') + self.assertRaisesRegexp(HTTP, '400.*invalid arg', filter_url, 'http://domain.com/appc/init/fcn/bad!arg') + except AttributeError: + pass + + self.assertEqual(filter_url('http://domain.com/welcome/default/fcn_1'), "/welcome/default/fcn_1") + self.assertRaises(HTTP, filter_url, 'http://domain.com/welcome/default/fcn-1') + try: + # 2.7+ only + self.assertRaisesRegexp(HTTP, '400.*invalid function', filter_url, 'http://domain.com/welcome/default/fcn-1') + except AttributeError: + pass + + def test_router_out(self): + ''' + Test basic outgoing routing + ''' + router_out = dict( + BASE = dict(), + init = dict( controllers = ['default', 'ctr'], ), + app = dict(), + ) + load(rdict=router_out) + self.assertEqual(filter_url('https://domain.com/app/ctr/fcn', out=True), "/app/ctr/fcn") + self.assertEqual(filter_url('https://domain.com/init/ctr/fcn', out=True), "/ctr/fcn") + self.assertEqual(filter_url('https://domain.com/init/ctr/fcn', out=True), "/ctr/fcn") + self.assertEqual(filter_url('https://domain.com/init/static/file', out=True), "/init/static/file") + self.assertEqual(filter_url('https://domain.com/init/static/index', out=True), "/init/static/index") + self.assertEqual(filter_url('https://domain.com/init/default/index', out=True), "/") + self.assertEqual(filter_url('https://domain.com/init/ctr/index', out=True), "/ctr") + self.assertEqual(filter_url('http://domain.com/init/default/fcn?query', out=True), "/fcn?query") + self.assertEqual(filter_url('http://domain.com/init/default/fcn#anchor', out=True), "/fcn#anchor") + self.assertEqual(filter_url('http://domain.com/init/default/fcn?query#anchor', out=True), + "/fcn?query#anchor") + + router_out['BASE']['map_static'] = True + load(rdict=router_out) + self.assertEqual(filter_url('https://domain.com/init/static/file', out=True), "/static/file") + self.assertEqual(filter_url('https://domain.com/init/static/index', out=True), "/static/index") + + router_out['init']['map_static'] = False + load(rdict=router_out) + self.assertEqual(filter_url('https://domain.com/init/static/file', out=True), "/init/static/file") + self.assertEqual(filter_url('https://domain.com/init/static/index', out=True), "/init/static/index") + + def test_router_functions(self): + ''' + Test function-omission with functions=[something] + ''' + router_functions = dict( + BASE = dict( + applications = ['init', 'app', 'app2'], + default_application = 'app', + ), + init = dict( + controllers = ['default'], + ), + app = dict( + controllers = ['default', 'ctr'], + functions = ['index', 'user', 'help'], + ), + app2 = dict( + controllers = ['default', 'ctr'], + functions = ['index', 'user', 'help'], + ), + ) + load(rdict=router_functions) + self.assertEqual(str(URL(a='init', c='default', f='f', args=['arg1'])), "/init/f/arg1") + self.assertEqual(str(URL(a='init', c='default', f='index', args=['arg1'])), "/init/index/arg1") + self.assertEqual(str(URL(a='app', c='default', f='index', args=['arg1'])), "/arg1") + self.assertEqual(str(URL(a='app', c='default', f='user', args=['arg1'])), "/user/arg1") + self.assertEqual(str(URL(a='app', c='default', f='user', args=['index'])), "/user/index") + self.assertEqual(str(URL(a='app', c='default', f='index', args=['index'])), "/index/index") + self.assertEqual(str(URL(a='app', c='default', f='index', args=['init'])), "/index/init") + self.assertEqual(str(URL(a='app', c='default', f='index', args=['ctr'])), "/index/ctr") + self.assertEqual(str(URL(a='app', c='ctr', f='index', args=['arg'])), "/ctr/index/arg") + + self.assertEqual(str(URL(a='app2', c='default', f='index', args=['arg1'])), "/app2/arg1") + self.assertEqual(str(URL(a='app2', c='default', f='user', args=['arg1'])), "/app2/user/arg1") + self.assertEqual(str(URL(a='app2', c='default', f='user', args=['index'])), "/app2/user/index") + self.assertEqual(str(URL(a='app2', c='default', f='index', args=['index'])), "/app2/index/index") + self.assertEqual(str(URL(a='app2', c='default', f='index', args=['init'])), "/app2/index/init") + self.assertEqual(str(URL(a='app2', c='default', f='index', args=['ctr'])), "/app2/index/ctr") + + self.assertEqual(filter_url('http://d.com/arg'), "/app/default/index ['arg']") + self.assertEqual(filter_url('http://d.com/user'), "/app/default/user") + self.assertEqual(filter_url('http://d.com/user/arg'), "/app/default/user ['arg']") + self.assertEqual(filter_url('http://d.com/ctr'), "/app/ctr/index") + self.assertEqual(filter_url('http://d.com/ctr/index/arg'), "/app/ctr/index ['arg']") + self.assertEqual(filter_url('http://d.com/ctr/arg'), "/app/ctr/arg") + + self.assertEqual(filter_url('http://d.com/app2/arg'), "/app2/default/index ['arg']") + self.assertEqual(filter_url('http://d.com/app2/user'), "/app2/default/user") + self.assertEqual(filter_url('http://d.com/app2/user/arg'), "/app2/default/user ['arg']") + self.assertEqual(filter_url('http://d.com/app2/ctr'), "/app2/ctr/index") + self.assertEqual(filter_url('http://d.com/app2/ctr/index/arg'), "/app2/ctr/index ['arg']") + self.assertEqual(filter_url('http://d.com/app2/ctr/arg'), "/app2/ctr/arg") + + def test_router_hyphen(self): + ''' + Test hyphen conversion + ''' + router_hyphen = dict( + BASE = dict( + applications = ['init', 'app1', 'app2'], + ), + init = dict( + controllers = ['default'], + ), + app1 = dict( + controllers = ['default'], + map_hyphen = True, + ), + app2 = dict( + controllers = ['default'], + map_hyphen = False, + ), + ) + load(rdict=router_hyphen) + self.assertEqual(filter_url('http://domain.com/init/default/fcn_1', out=True), "/fcn_1") + self.assertEqual(filter_url('http://domain.com/static/filename-with_underscore'), + "%s/applications/init/static/filename-with_underscore" % root) + self.assertEqual(filter_url('http://domain.com/init/static/filename-with_underscore', out=True), + "/init/static/filename-with_underscore") + + self.assertEqual(filter_url('http://domain.com/app2/fcn_1'), + "/app2/default/fcn_1") + self.assertEqual(filter_url('http://domain.com/app2/ctr/fcn_1', domain=('app2',None), out=True), + "/ctr/fcn_1") + self.assertEqual(filter_url('http://domain.com/app2/static/filename-with_underscore', domain=('app2',None), out=True), + "/app2/static/filename-with_underscore") + self.assertEqual(filter_url('http://domain.com/app2/static/filename-with_underscore'), + "%s/applications/app2/static/filename-with_underscore" % root) + + self.assertEqual(str(URL(a='init', c='default', f='a_b')), "/a_b") + self.assertEqual(str(URL(a='app1', c='default', f='a_b')), "/app1/a-b") + self.assertEqual(str(URL(a='app2', c='default', f='a_b')), "/app2/a_b") + self.assertEqual(str(URL(a='app1', c='static', f='a/b_c')), "/app1/static/a/b_c") + self.assertEqual(str(URL(a='app1', c='static/a', f='b_c')), "/app1/static/a/b_c") + self.assertEqual(str(URL(a='app2', c='static', f='a/b_c')), "/app2/static/a/b_c") + self.assertEqual(str(URL(a='app2', c='static/a', f='b_c')), "/app2/static/a/b_c") + + + def test_router_lang(self): + ''' + Test language specifications + ''' + router_lang = dict( + BASE = dict(default_application = 'admin'), + welcome = dict(), + admin = dict( + controllers = ['default', 'ctr'], + languages = ['en', 'it', 'it-it'], default_language = 'en', + ), + examples = dict( + languages = ['en', 'it', 'it-it'], default_language = 'en', + ), + ) + load(rdict=router_lang) + self.assertEqual(filter_url('http://domain.com/index/abc'), "/admin/default/index ['abc'] (en)") + self.assertEqual(filter_url('http://domain.com/en/abc/def'), "/admin/default/abc ['def'] (en)") + self.assertEqual(filter_url('http://domain.com/it/abc/def'), "/admin/default/abc ['def'] (it)") + self.assertEqual(filter_url('http://domain.com/it-it/abc/def'), "/admin/default/abc ['def'] (it-it)") + self.assertEqual(filter_url('http://domain.com/index/a%20bc'), "/admin/default/index ['a bc'] (en)") + self.assertEqual(filter_url('http://domain.com/static/file'), "%s/applications/admin/static/file" % root) + self.assertEqual(filter_url('http://domain.com/en/static/file'), "%s/applications/admin/static/file" % root) + self.assertEqual(filter_url('http://domain.com/examples/en/static/file'), "%s/applications/examples/static/en/file" % root) + self.assertEqual(filter_url('http://domain.com/examples/static/file'), "%s/applications/examples/static/en/file" % root) + self.assertEqual(filter_url('http://domain.com/examples/it/static/file'), "%s/applications/examples/static/it/file" % root) + self.assertEqual(filter_url('http://domain.com/examples/it-it/static/file'), "%s/applications/examples/static/file" % root) + + self.assertEqual(filter_url('https://domain.com/admin/ctr/fcn', lang='en', out=True), "/ctr/fcn") + self.assertEqual(filter_url('https://domain.com/admin/ctr/fcn', lang='it', out=True), "/it/ctr/fcn") + self.assertEqual(filter_url('https://domain.com/admin/ctr/fcn', lang='it-it', out=True), "/it-it/ctr/fcn") + self.assertEqual(filter_url('https://domain.com/admin/static/file', lang='en', out=True), "/admin/en/static/file") + self.assertEqual(filter_url('https://domain.com/admin/static/file', lang='it', out=True), "/admin/it/static/file") + self.assertEqual(filter_url('https://domain.com/admin/static/file', lang='it-it', out=True), "/admin/it-it/static/file") + self.assertEqual(filter_url('https://domain.com/welcome/ctr/fcn', lang='it', out=True), "/welcome/ctr/fcn") + self.assertEqual(filter_url('https://domain.com/welcome/ctr/fcn', lang='es', out=True), "/welcome/ctr/fcn") + + router_lang['admin']['map_static'] = True + load(rdict=router_lang) + self.assertEqual(filter_url('https://domain.com/admin/ctr/fcn', lang='en', out=True), "/ctr/fcn") + self.assertEqual(filter_url('https://domain.com/admin/ctr/fcn', lang='it', out=True), "/it/ctr/fcn") + self.assertEqual(filter_url('https://domain.com/admin/ctr/fcn', lang='it-it', out=True), "/it-it/ctr/fcn") + self.assertEqual(filter_url('https://domain.com/admin/static/file', lang='en', out=True), "/static/file") + self.assertEqual(filter_url('https://domain.com/admin/static/file', lang='it', out=True), "/it/static/file") + self.assertEqual(filter_url('https://domain.com/admin/static/file', lang='it-it', out=True), "/it-it/static/file") + self.assertEqual(filter_url('https://domain.com/welcome/ctr/fcn', lang='it', out=True), "/welcome/ctr/fcn") + self.assertEqual(filter_url('https://domain.com/welcome/ctr/fcn', lang='es', out=True), "/welcome/ctr/fcn") + + def test_router_get_effective(self): + ''' + Test get_effective_router + ''' + router_get_effective = dict( + BASE = dict( + default_application = 'a1', + applications = ['a1', 'a2'], + ), + a1 = dict( + controllers = ['c1a', 'c1b', 'default'], + ), + a2 = dict( + default_controller = 'c2', + controllers = [], + ), + a3 = dict( + default_controller = 'c2', + controllers = ['c1'], + ), + a4 = dict( + default_function = 'f1', + functions = ['f2'], + ), + ) + load(rdict=router_get_effective) + self.assertEqual(get_effective_router('BASE').applications, set(['a1','a2'])) + self.assertEqual(get_effective_router('BASE').default_application, 'a1') + self.assertEqual(get_effective_router('BASE').domains, {}) + self.assertEqual(get_effective_router('a1').applications, None) + self.assertEqual(get_effective_router('a1').default_application, None) + self.assertEqual(get_effective_router('a1').domains, None) + self.assertEqual(get_effective_router('a1').default_controller, "default") + self.assertEqual(get_effective_router('a2').default_application, None) + self.assertEqual(get_effective_router('a2').default_controller, "c2") + self.assertEqual(get_effective_router('a1').controllers, set(['c1a', 'c1b', 'default', 'static'])) + self.assertEqual(get_effective_router('a2').controllers, set()) + self.assertEqual(get_effective_router('a3').controllers, set(['c1', 'c2', 'static'])) + self.assertEqual(get_effective_router('a4').functions, set(['f1', 'f2'])) + self.assertEqual(get_effective_router('xx'), None) + + def test_router_error(self): + ''' + Test rewrite of HTTP errors + ''' + router_err = dict() + load(rdict=router_err) + self.assertEqual(filter_err(200), 200) + self.assertEqual(filter_err(399), 399) + self.assertEqual(filter_err(400), 400) + + def test_router_args(self): + ''' + Test URL args parsing/generation + ''' + load(rdict=dict()) + self.assertEqual(filter_url('http://domain.com/init/default/f/arg1'), + "/init/default/f ['arg1']") + self.assertEqual(filter_url('http://domain.com/init/default/f/arg1/'), + "/init/default/f ['arg1']") + self.assertEqual(filter_url('http://domain.com/init/default/f/arg1//'), + "/init/default/f ['arg1', '']") + self.assertEqual(filter_url('http://domain.com/init/default/f//arg1'), + "/init/default/f ['', 'arg1']") + self.assertEqual(filter_url('http://domain.com/init/default/f/arg1/arg2'), + "/init/default/f ['arg1', 'arg2']") + + self.assertEqual(filter_url('http://domain.com/init/default/f', out=True), "/f") + self.assertEqual(map_url_out(None, None, 'init', 'default', 'f', None, None, None, None, None), "/f") + self.assertEqual(map_url_out(None, None, 'init', 'default', 'f', [], None, None, None, None), "/f") + self.assertEqual(map_url_out(None, None, 'init', 'default', 'f', ['arg1'], None, None, None, None), "/f") + self.assertEqual(map_url_out(None, None, 'init', 'default', 'f', ['arg1', ''], None, None, None, None), "/f") + self.assertEqual(str(URL(a='init', c='default', f='f', args=None)), "/f") + self.assertEqual(str(URL(a='init', c='default', f='f', args=['arg1'])), "/f/arg1") + self.assertEqual(str(URL(a='init', c='default', f='f', args=['arg1', ''])), "/f/arg1//") + self.assertEqual(str(URL(a='init', c='default', f='f', args=['arg1', '', 'arg3'])), "/f/arg1//arg3") + self.assertEqual(str(URL(a='init', c='default', f='f', args=['ar g'])), "/f/ar%20g") + self.assertEqual(str(URL(a='init', c='default', f='f', args=['årg'])), "/f/%C3%A5rg") + self.assertEqual(str(URL(a='init', c='default', f='fünc')), "/f\xc3\xbcnc") + + def test_routes_anchor(self): + ''' + Test URL with anchor + ''' + self.assertEqual(str(URL(a='a', c='c', f='f', anchor='anchor')), "/a/c/f#anchor") + load(rdict=dict()) + self.assertEqual(str(URL(a='a', c='c', f='f', anchor='anchor')), "/a/c/f#anchor") + args = ['a1', 'a2'] + self.assertEqual(str(URL(a='a', c='c', f='f', args=args, anchor='anchor')), + "/a/c/f/a1/a2#anchor") + vars = dict(v1=1, v2=2) + self.assertEqual(str(URL(a='a', c='c', f='f', vars=vars, anchor='anchor')), + "/a/c/f?v1=1&v2=2#anchor") + self.assertEqual(str(URL(a='a', c='c', f='f', args=args, vars=vars, anchor='anchor')), + "/a/c/f/a1/a2?v1=1&v2=2#anchor") + self.assertEqual(str(URL(a='init', c='default', f='index')), + "/") + self.assertEqual(str(URL(a='init', c='default', f='f')), + "/f") + self.assertEqual(str(URL(a='init', c='default', f='index', anchor='anchor')), + "/#anchor") + self.assertEqual(str(URL(a='init', c='default', f='f', anchor='anchor')), + "/f#anchor") + + def test_router_prefix(self): + ''' + Test path_prefix + ''' + router_path_prefix = dict( + BASE = dict( + default_application = 'a1', + applications = ['a1', 'a2'], + path_prefix = '/path/to/apps', + ), + a1 = dict( + controllers = ['c1a', 'c1b', 'default'], + ), + a2 = dict( + default_controller = 'c2', + controllers = [], + ), + ) + load(rdict=router_path_prefix) + self.assertEqual(str(URL(a='a1', c='c1a', f='f')), + "/path/to/apps/c1a/f") + self.assertEqual(str(URL(a='a2', c='c', f='f')), + "/path/to/apps/a2/c/f") + self.assertEqual(str(URL(a='a2', c='c2', f='f')), + "/path/to/apps/a2/c2/f") + self.assertEqual(filter_url('http://domain.com/a1/'), "/a1/default/index") + self.assertEqual(filter_url('http://domain.com/path/to/apps/a1/'), "/a1/default/index") + self.assertEqual(filter_url('http://domain.com/path/to/a1/'), "/a1/default/path ['to', 'a1']") + + def test_router_absolute(self): + ''' + Test absolute URL + ''' + load(rdict=dict()) + r = Storage() + r.env = Storage() + r.env.http_host = 'domain.com' + r.env.WSGI_URL_SCHEME = 'httpx' # distinguish incoming scheme + self.assertEqual(str(URL(r=r, a='a', c='c', f='f')), "/a/c/f") + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', host=True)), + "httpx://domain.com/a/c/f") + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', host='host.com')), + "httpx://host.com/a/c/f") + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=True)), + "httpx://domain.com/a/c/f") + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=False)), + "/a/c/f") + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme='https')), + "https://domain.com/a/c/f") + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme='wss')), + "wss://domain.com/a/c/f") + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=True, host=True)), + "httpx://domain.com/a/c/f") + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme='https', host=True)), + "https://domain.com/a/c/f") + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=False, host=True)), + "httpx://domain.com/a/c/f") + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=True, host='host.com')), + "httpx://host.com/a/c/f") + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=False, host='host.com')), + "httpx://host.com/a/c/f") + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', port=1234)), + "httpx://domain.com:1234/a/c/f") + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=True, port=1234)), + "httpx://domain.com:1234/a/c/f") + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', host='host.com', port=1234)), + "httpx://host.com:1234/a/c/f") + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme='wss', host='host.com', port=1234)), + "wss://host.com:1234/a/c/f") + + def test_request_uri(self): + ''' + Test REQUEST_URI in env + ''' + load(rdict=dict()) + self.assertEqual(filter_url('http://domain.com/abc', env=True).request_uri, + '/init/default/abc') + self.assertEqual(filter_url('http://domain.com/abc?def', env=True).request_uri, + '/init/default/abc?def') + self.assertEqual(filter_url('http://domain.com/index/abc', env=True).request_uri, + "/init/default/index/abc") + self.assertEqual(filter_url('http://domain.com/abc/def', env=True).request_uri, + "/init/default/abc/def") + self.assertEqual(filter_url('http://domain.com/index/a%20bc', env=True).request_uri, + "/init/default/index/a%20bc") + +if __name__ == '__main__': + setUpModule() # pre-2.7 + unittest.main() + tearDownModule() ADDED gluon/tests/test_routes.py Index: gluon/tests/test_routes.py ================================================================== --- gluon/tests/test_routes.py +++ gluon/tests/test_routes.py @@ -0,0 +1,363 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +"""Unit tests for rewrite.py regex routing option""" + +import sys +import os +import unittest +import tempfile +import logging + +if os.path.isdir('gluon'): + sys.path.append(os.path.realpath('gluon')) # running from web2py base +else: + sys.path.append(os.path.realpath('../')) # running from gluon/tests/ + +from rewrite import load, filter_url, filter_err, get_effective_router, regex_filter_out, regex_select +from html import URL +from fileutils import abspath +from settings import global_settings +from http import HTTP +from storage import Storage + +logger = None +oldcwd = None +root = None + +def setUpModule(): + def make_apptree(): + "build a temporary applications tree" + # applications/ + os.mkdir(abspath('applications')) + # applications/app/ + for app in ('admin', 'examples', 'welcome'): + os.mkdir(abspath('applications', app)) + # applications/app/(controllers, static) + for subdir in ('controllers', 'static'): + os.mkdir(abspath('applications', app, subdir)) + # applications/admin/controllers/*.py + for ctr in ('appadmin', 'default', 'gae', 'mercurial', 'shell', 'wizard'): + open(abspath('applications', 'admin', 'controllers', '%s.py' % ctr), 'w').close() + # applications/examples/controllers/*.py + for ctr in ('ajax_examples', 'appadmin', 'default', 'global', 'spreadsheet'): + open(abspath('applications', 'examples', 'controllers', '%s.py' % ctr), 'w').close() + # applications/welcome/controllers/*.py + for ctr in ('appadmin', 'default'): + open(abspath('applications', 'welcome', 'controllers', '%s.py' % ctr), 'w').close() + # create an app-specific routes.py for examples app + routes = open(abspath('applications', 'examples', 'routes.py'), 'w') + routes.write("default_function='exdef'\n") + routes.close() + + global oldcwd + if oldcwd is None: # do this only once + oldcwd = os.getcwd() + if not os.path.isdir('gluon'): + os.chdir(os.path.realpath('../../')) # run from web2py base directory + import main # for initialization after chdir + global logger + logger = logging.getLogger('web2py.rewrite') + global_settings.applications_parent = tempfile.mkdtemp() + global root + root = global_settings.applications_parent + make_apptree() + +def tearDownModule(): + global oldcwd + if oldcwd is not None: + os.chdir(oldcwd) + oldcwd = None + + +class TestRoutes(unittest.TestCase): + """ Tests the regex routing logic from gluon.rewrite """ + + def test_routes_null(self): + """ Tests a null routes table """ + load(data='') + # incoming + self.assertEqual(filter_url('http://domain.com'), '/init/default/index') + self.assertEqual(filter_url('http://domain.com/'), '/init/default/index') + self.assertEqual(filter_url('http://domain.com/abc'), '/abc/default/index') + self.assertEqual(filter_url('http://domain.com/abc/'), '/abc/default/index') + self.assertEqual(filter_url('http://domain.com/abc/def'), "/abc/def/index") + self.assertEqual(filter_url('http://domain.com/abc/def/'), "/abc/def/index") + self.assertEqual(filter_url('http://domain.com/abc/def/ghi'), "/abc/def/ghi") + self.assertEqual(filter_url('http://domain.com/abc/def/ghi/'), "/abc/def/ghi") + self.assertEqual(filter_url('http://domain.com/abc/def/ghi/jkl'), "/abc/def/ghi ['jkl']") + self.assertEqual(filter_url('http://domain.com/abc/def/ghi/j%20kl'), "/abc/def/ghi ['j_kl']") + self.assertEqual(filter_url('http://domain.com/welcome/static/path/to/static'), "%s/applications/welcome/static/path/to/static" % root) + self.assertRaises(HTTP, filter_url, 'http://domain.com/welcome/static/bad/path/to/st~tic') + try: + # 2.7+ only + self.assertRaisesRegexp(HTTP, "400.*BAD REQUEST \[invalid path\]", filter_url, 'http://domain.com/welcome/static/bad/path/to/st~tic') + except AttributeError: + pass + # outgoing + self.assertEqual(filter_url('http://domain.com/init/default/index', out=True), '/init/default/index') + self.assertEqual(filter_url('http://domain.com/init/default/index/arg1', out=True), '/init/default/index/arg1') + self.assertEqual(filter_url('http://domain.com/init/default/abc', out=True), '/init/default/abc') + + def test_routes_query(self): + """ Test query appending """ + data = r''' +routes_in = ( + ('/service/$model/create', '/app/default/call/json/create?model=$model'), +) +''' + load(data=data) + self.assertEqual(filter_url('http://localhost:8000/service/person/create'), "/app/default/call ['json', 'create'] ?model=person") + self.assertEqual(filter_url('http://localhost:8000/service/person/create?var1=val1'), "/app/default/call ['json', 'create'] ?model=person&var1=val1") + + def test_routes_specific(self): + """ + Test app-specific routes.py + + Note that make_apptree above created applications/examples/routes.py with a default_function. + """ + data = r''' +routes_app = [ + (r'/(?Pwelcome|admin|examples)\b.*', r'\g'), + (r'$anything', r'welcome'), + (r'/?$anything', r'welcome'), +] +''' + load(data=data) + self.assertEqual(filter_url('http://domain.com/welcome'), '/welcome/default/index') + self.assertEqual(filter_url('http://domain.com/examples'), '/examples/default/exdef') + + def test_routes_defapp(self): + """ Test the default-application function """ + data = r''' +default_application = 'defapp' +''' + load(data=data) + # incoming + self.assertEqual(filter_url('http://domain.com'), '/defapp/default/index') + self.assertEqual(filter_url('http://domain.com/'), '/defapp/default/index') + self.assertEqual(filter_url('http://domain.com/welcome'), '/welcome/default/index') + self.assertEqual(filter_url('http://domain.com/app'), '/app/default/index') + self.assertEqual(filter_url('http://domain.com/welcome/default/index/abc'), "/welcome/default/index ['abc']") + self.assertEqual(filter_url('http://domain.com/welcome/static/abc'), '%s/applications/welcome/static/abc' % root) + self.assertEqual(filter_url('http://domain.com/defapp/static/path/to/static'), "%s/applications/defapp/static/path/to/static" % root) + + def test_routes_raise(self): + ''' + Test URLs that raise exceptions + ''' + # test non-exception variants + load(data='') + self.assertEqual(filter_url('http://domain.com/init'), "/init/default/index") + self.assertEqual(filter_url('http://domain.com/init/default'), "/init/default/index") + self.assertEqual(filter_url('http://domain.com/init/default/fcn.ext'), "/init/default/fcn.ext") + self.assertEqual(filter_url('http://domain.com/init/default/fcn/arg'), "/init/default/fcn ['arg']") + # now raise-HTTP variants + self.assertRaises(HTTP, filter_url, 'http://domain.com/bad!ctl') + self.assertRaises(HTTP, filter_url, 'http://domain.com/ctl/bad!fcn') + self.assertRaises(HTTP, filter_url, 'http://domain.com/ctl/fcn.bad!ext') + self.assertRaises(HTTP, filter_url, 'http://domain.com/ctl/fcn/bad!arg') + try: + # 2.7+ only + self.assertRaisesRegexp(HTTP, '400 BAD REQUEST \[invalid path\]', filter_url, 'http://domain.com/init/bad!ctl') + self.assertRaisesRegexp(HTTP, '400 BAD REQUEST \[invalid path\]', filter_url, 'http://domain.com/init/ctlr/bad!fcn') + self.assertRaisesRegexp(HTTP, '400 BAD REQUEST \[invalid path\]', filter_url, 'http://domain.com/init/ctlr/fcn.bad!ext') + self.assertRaisesRegexp(HTTP, '400 BAD REQUEST \[invalid path \(args\)\]', filter_url, 'http://domain.com/appc/init/fcn/bad!arg') + except AttributeError: + pass + + self.assertEqual(filter_url('http://domain.com/welcome/default/fcn_1'), "/welcome/default/fcn_1") + self.assertRaises(HTTP, filter_url, 'http://domain.com/welcome/default/fcn-1') + try: + # 2.7+ only + self.assertRaisesRegexp(HTTP, '400 BAD REQUEST \[invalid path\]', filter_url, 'http://domain.com/welcome/default/fcn-1') + except AttributeError: + pass + + def test_routes_error(self): + ''' + Test rewrite of HTTP errors + ''' + router_err = dict() + load(rdict=router_err) + self.assertEqual(filter_err(200), 200) + self.assertEqual(filter_err(399), 399) + self.assertEqual(filter_err(400), 400) + + def test_routes_args(self): + ''' + Test URL args parsing/generation + ''' + data = r'''routes_in = [ + ('/robots.txt', '/welcome/static/robots.txt'), + ('/favicon.ico', '/welcome/static/favicon.ico'), + ('/admin$anything', '/admin$anything'), + ('.*:https?://(.*\\.)?domain1.com:$method /', '/app1/default'), + ('.*:https?://(.*\\.)?domain1.com:$method /static/$anything', '/app1/static/$anything'), + ('.*:https?://(.*\\.)?domain1.com:$method /appadmin/$anything', '/app1/appadmin/$anything'), + ('.*:https?://(.*\\.)?domain1.com:$method /$anything', '/app1/default/$anything'), + ('.*:https?://(.*\\.)?domain2.com:$method /', '/app2/default'), + ('.*:https?://(.*\\.)?domain2.com:$method /static/$anything', '/app2/static/$anything'), + ('.*:https?://(.*\\.)?domain2.com:$method /appadmin/$anything', '/app2/appadmin/$anything'), + ('.*:https?://(.*\\.)?domain2.com:$method /$anything', '/app2/default/$anything'), + ('.*:https?://(.*\\.)?domain3.com:$method /', '/app3/defcon3'), + ('.*:https?://(.*\\.)?domain3.com:$method /static/$anything', '/app3/static/$anything'), + ('.*:https?://(.*\\.)?domain3.com:$method /appadmin/$anything', '/app3/appadmin/$anything'), + ('.*:https?://(.*\\.)?domain3.com:$method /$anything', '/app3/defcon3/$anything'), + ('/', '/welcome/default'), + ('/welcome/default/$anything', '/welcome/default/$anything'), + ('/welcome/$anything', '/welcome/default/$anything'), + ('/static/$anything', '/welcome/static/$anything'), + ('/appadmin/$anything', '/welcome/appadmin/$anything'), + ('/$anything', '/welcome/default/$anything'), + ] +routes_out = [ + ('/welcome/static/$anything', '/static/$anything'), + ('/welcome/appadmin/$anything', '/appadmin/$anything'), + ('/welcome/default/$anything', '/$anything'), + ('/app1/static/$anything', '/static/$anything'), + ('/app1/appadmin/$anything', '/appadmin/$anything'), + ('/app1/default/$anything', '/$anything'), + ('/app2/static/$anything', '/static/$anything'), + ('/app2/appadmin/$anything', '/appadmin/$anything'), + ('/app2/default/$anything', '/$anything'), + ('/app3/static/$anything', '/static/$anything'), + ('/app3/appadmin/$anything', '/appadmin/$anything'), + ('/app3/defcon3/$anything', '/$anything') + ] +''' + load(data=data) + self.assertEqual(filter_url('http://domain.com/welcome/default/f/arg1'), + "/welcome/default/f ['arg1']") + self.assertEqual(filter_url('http://domain.com/welcome/default/f/arg1/'), + "/welcome/default/f ['arg1']") + self.assertEqual(filter_url('http://domain.com/welcome/default/f/arg1//'), + "/welcome/default/f ['arg1', '']") + self.assertEqual(filter_url('http://domain.com/welcome/default/f//arg1'), + "/welcome/default/f ['', 'arg1']") + self.assertEqual(filter_url('http://domain.com/welcome/default/f/arg1/arg2'), + "/welcome/default/f ['arg1', 'arg2']") + + self.assertEqual(filter_url('http://domain.com/welcome/default/f', out=True), "/f") + self.assertEqual(regex_filter_out('/welcome/default/f'), "/f") + self.assertEqual(str(URL(a='welcome', c='default', f='f', args=None)), "/f") + self.assertEqual(str(URL(a='welcome', c='default', f='f', args=['arg1'])), "/f/arg1") + self.assertEqual(str(URL(a='welcome', c='default', f='f', args=['arg1', ''])), "/f/arg1//") + self.assertEqual(str(URL(a='welcome', c='default', f='f', args=['arg1', '', 'arg3'])), "/f/arg1//arg3") + self.assertEqual(str(URL(a='welcome', c='default', f='f', args=['ar g'])), "/f/ar%20g") + self.assertEqual(str(URL(a='welcome', c='default', f='f', args=['årg'])), "/f/%C3%A5rg") + self.assertEqual(str(URL(a='welcome', c='default', f='fünc')), "/f\xc3\xbcnc") + + def test_routes_anchor(self): + ''' + Test URL with anchor + ''' + self.assertEqual(str(URL(a='a', c='c', f='f', anchor='anchor')), "/a/c/f#anchor") + load(data='') + self.assertEqual(str(URL(a='a', c='c', f='f', anchor='anchor')), "/a/c/f#anchor") + args = ['a1', 'a2'] + self.assertEqual(str(URL(a='a', c='c', f='f', args=args, anchor='anchor')), + "/a/c/f/a1/a2#anchor") + vars = dict(v1=1, v2=2) + self.assertEqual(str(URL(a='a', c='c', f='f', vars=vars, anchor='anchor')), + "/a/c/f?v1=1&v2=2#anchor") + self.assertEqual(str(URL(a='a', c='c', f='f', args=args, vars=vars, anchor='anchor')), + "/a/c/f/a1/a2?v1=1&v2=2#anchor") + + data = r'''routes_out = [ + ('/init/default/index', '/'), + ]''' + load(data=data) + self.assertEqual(str(URL(a='init', c='default', f='index')), + "/") + self.assertEqual(str(URL(a='init', c='default', f='index', anchor='anchor')), + "/init/default/index#anchor") + + data = r'''routes_out = [ + (r'/init/default/index(?P(#.*)?)', r'/\g'), + ]''' + load(data=data) + self.assertEqual(str(URL(a='init', c='default', f='index')), + "/") + self.assertEqual(str(URL(a='init', c='default', f='index', anchor='anchor')), + "/#anchor") + + data = r'''routes_out = [ + (r'/init/default/index(?P([?#].*)?)', r'/\g'), + ]''' + load(data=data) + self.assertEqual(str(URL(a='init', c='default', f='index')), + "/") + self.assertEqual(str(URL(a='init', c='default', f='index', anchor='anchor')), + "/#anchor") + query = dict(var='abc') + self.assertEqual(str(URL(a='init', c='default', f='index', vars=query)), + "/?var=abc") + self.assertEqual(str(URL(a='init', c='default', f='index', vars=query, anchor='anchor')), + "/?var=abc#anchor") + + def test_routes_absolute(self): + ''' + Test absolute URL + ''' + load(data='') + r = Storage() + r.env = Storage() + r.env.http_host = 'domain.com' + r.env.WSGI_URL_SCHEME = 'httpx' # distinguish incoming scheme + self.assertEqual(str(URL(r=r, a='a', c='c', f='f')), "/a/c/f") + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', host=True)), + "httpx://domain.com/a/c/f") + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', host='host.com')), + "httpx://host.com/a/c/f") + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=True)), + "httpx://domain.com/a/c/f") + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=False)), + "/a/c/f") + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme='https')), + "https://domain.com/a/c/f") + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme='wss')), + "wss://domain.com/a/c/f") + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=True, host=True)), + "httpx://domain.com/a/c/f") + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme='https', host=True)), + "https://domain.com/a/c/f") + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=False, host=True)), + "httpx://domain.com/a/c/f") + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=True, host='host.com')), + "httpx://host.com/a/c/f") + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=False, host='host.com')), + "httpx://host.com/a/c/f") + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', port=1234)), + "httpx://domain.com:1234/a/c/f") + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=True, port=1234)), + "httpx://domain.com:1234/a/c/f") + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', host='host.com', port=1234)), + "httpx://host.com:1234/a/c/f") + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme='wss', host='host.com', port=1234)), + "wss://host.com:1234/a/c/f") + + def test_request_uri(self): + ''' + Test REQUEST_URI in env + ''' + data = r'''routes_in = [ + ('/abc', '/init/default/abc'), + ('/index/$anything', '/init/default/index/$anything'), + ] +''' + load(data=data) + self.assertEqual(filter_url('http://domain.com/abc', env=True).request_uri, + '/init/default/abc') + self.assertEqual(filter_url('http://domain.com/abc?def', env=True).request_uri, + '/init/default/abc?def') + self.assertEqual(filter_url('http://domain.com/index/abc', env=True).request_uri, + "/init/default/index/abc") + self.assertEqual(filter_url('http://domain.com/index/a%20bc', env=True).request_uri, + "/init/default/index/a bc") + + +if __name__ == '__main__': + setUpModule() # pre-2.7 + unittest.main() + tearDownModule() ADDED gluon/tests/test_template.py Index: gluon/tests/test_template.py ================================================================== --- gluon/tests/test_template.py +++ gluon/tests/test_template.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + Unit tests for gluon.template +""" + +import sys +import os +if os.path.isdir('gluon'): + sys.path.append(os.path.realpath('gluon')) +else: + sys.path.append(os.path.realpath('../')) + +import unittest +from template import render + +class TestVirtualFields(unittest.TestCase): + + def testRun(self): + self.assertEqual(render(content='{{for i in range(n):}}{{=i}}{{pass}}', + context=dict(n=3)), '012') + self.assertEqual(render(content='{{if n>2:}}ok{{pass}}', + context=dict(n=3)), 'ok') + self.assertEqual(render(content='{{try:}}{{n/0}}{{except:}}fail{{pass}}', + context=dict(n=3)), 'fail') + self.assertEqual(render(content='{{="<&>"}}'), '<&>') + self.assertEqual(render(content='"abc"'), '"abc"') + self.assertEqual(render(content='"a\'bc"'), '"a\'bc"') + self.assertEqual(render(content='"a\"bc"'), '"a\"bc"') + self.assertEqual(render(content=r'''"a\"bc"'''), r'"a\"bc"') + self.assertEqual(render(content=r'''"""abc\""""'''), r'"""abc\""""') + + def testEqualWrite(self): + "test generation of response.write from =" + self.assertEqual(render(content='{{="abc"}}'), 'abc') + # whitespace is stripped + self.assertEqual(render(content='{{ ="abc"}}'), 'abc') + self.assertEqual(render(content='{{ ="abc" }}'), 'abc') + self.assertEqual(render(content='{{pass\n="abc" }}'), 'abc') + # = recognized only at the beginning of a physical line + self.assertEqual(render(content='{{xyz = "xyz"\n="abc"\n="def"\n=xyz }}'), 'abcdefxyz') + # = in python blocks + self.assertEqual(render(content='{{if True:\n="abc"\npass }}'), 'abc') + self.assertEqual(render(content='{{if True:\n="abc"\npass\n="def" }}'), 'abcdef') + self.assertEqual(render(content='{{if False:\n="abc"\npass\n="def" }}'), 'def') + self.assertEqual(render(content='{{if True:\n="abc"\nelse:\n="def"\npass }}'), 'abc') + self.assertEqual(render(content='{{if False:\n="abc"\nelse:\n="def"\npass }}'), 'def') + # codeblock-leading = handles internal newlines, escaped or not + self.assertEqual(render(content='{{=list((1,2,3))}}'), '[1, 2, 3]') + self.assertEqual(render(content='{{=list((1,2,\\\n3))}}'), '[1, 2, 3]') + self.assertEqual(render(content='{{=list((1,2,\n3))}}'), '[1, 2, 3]') + # ...but that means no more = operators in the codeblock + self.assertRaises(SyntaxError, render, content='{{="abc"\n="def" }}') + # = embedded in codeblock won't handle newlines in its argument + self.assertEqual(render(content='{{pass\n=list((1,2,\\\n3))}}'), '[1, 2, 3]') + self.assertRaises(SyntaxError, render, content='{{pass\n=list((1,2,\n3))}}') + + +if __name__ == '__main__': + unittest.main() ADDED gluon/tests/test_utils.py Index: gluon/tests/test_utils.py ================================================================== --- gluon/tests/test_utils.py +++ gluon/tests/test_utils.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" Unit tests for utils.py """ + +import sys +import os +import unittest +if os.path.isdir('gluon'): + sys.path.append(os.path.realpath('gluon')) +else: + sys.path.append(os.path.realpath('../')) + +from utils import md5_hash + + +class TestUtils(unittest.TestCase): + """ Tests the utils.py module """ + + def test_md5_hash(self): + """ Tests the md5_hash function """ + + data = md5_hash("web2py rocks") + self.assertEqual(data, '79509f3246a2824dee64635303e99204') + +if __name__ == '__main__': + unittest.main() ADDED gluon/tools.py Index: gluon/tools.py ================================================================== --- gluon/tools.py +++ gluon/tools.py @@ -0,0 +1,4115 @@ +#!/bin/python +# -*- coding: utf-8 -*- + +""" +This file is part of the web2py Web Framework +Copyrighted by Massimo Di Pierro +License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) +""" + +import base64 +import cPickle +import datetime +import thread +import logging +import sys +import os +import re +import time +import copy +import smtplib +import urllib +import urllib2 +import Cookie +import cStringIO +from email import MIMEBase, MIMEMultipart, MIMEText, Encoders, Header, message_from_string + +from contenttype import contenttype +from storage import Storage, StorageList, Settings, Messages +from utils import web2py_uuid +from gluon import * +from fileutils import read_file + +import serializers +import contrib.simplejson as simplejson + + +__all__ = ['Mail', 'Auth', 'Recaptcha', 'Crud', 'Service', 'PluginManager', 'fetch', 'geocode', 'prettydate'] + +logger = logging.getLogger("web2py") + +DEFAULT = lambda: None + +def callback(actions,form,tablename=None): + if actions: + if tablename and isinstance(actions,dict): + actions = actions.get(tablename, []) + if not isinstance(actions,(list, tuple)): + actions = [actions] + [action(form) for action in actions] + +def validators(*a): + b = [] + for item in a: + if isinstance(item, (list, tuple)): + b = b + list(item) + else: + b.append(item) + return b + +def call_or_redirect(f,*args): + if callable(f): + redirect(f(*args)) + else: + redirect(f) + +class Mail(object): + """ + Class for configuring and sending emails with alternative text / html + body, multiple attachments and encryption support + + Works with SMTP and Google App Engine. + """ + + class Attachment(MIMEBase.MIMEBase): + """ + Email attachment + + Arguments:: + + payload: path to file or file-like object with read() method + filename: name of the attachment stored in message; if set to + None, it will be fetched from payload path; file-like + object payload must have explicit filename specified + content_id: id of the attachment; automatically contained within + < and > + content_type: content type of the attachment; if set to None, + it will be fetched from filename using gluon.contenttype + module + encoding: encoding of all strings passed to this function (except + attachment body) + + Content ID is used to identify attachments within the html body; + in example, attached image with content ID 'photo' may be used in + html message as a source of img tag . + + Examples:: + + #Create attachment from text file: + attachment = Mail.Attachment('/path/to/file.txt') + + Content-Type: text/plain + MIME-Version: 1.0 + Content-Disposition: attachment; filename="file.txt" + Content-Transfer-Encoding: base64 + + SOMEBASE64CONTENT= + + #Create attachment from image file with custom filename and cid: + attachment = Mail.Attachment('/path/to/file.png', + filename='photo.png', + content_id='photo') + + Content-Type: image/png + MIME-Version: 1.0 + Content-Disposition: attachment; filename="photo.png" + Content-Id: + Content-Transfer-Encoding: base64 + + SOMEOTHERBASE64CONTENT= + """ + + def __init__( + self, + payload, + filename=None, + content_id=None, + content_type=None, + encoding='utf-8'): + if isinstance(payload, str): + if filename == None: + filename = os.path.basename(payload) + payload = read_file(payload, 'rb') + else: + if filename == None: + raise Exception('Missing attachment name') + payload = payload.read() + filename = filename.encode(encoding) + if content_type == None: + content_type = contenttype(filename) + self.my_filename = filename + self.my_payload = payload + MIMEBase.MIMEBase.__init__(self, *content_type.split('/', 1)) + self.set_payload(payload) + self['Content-Disposition'] = 'attachment; filename="%s"' % filename + if content_id != None: + self['Content-Id'] = '<%s>' % content_id.encode(encoding) + Encoders.encode_base64(self) + + def __init__(self, server=None, sender=None, login=None, tls=True): + """ + Main Mail object + + Arguments:: + + server: SMTP server address in address:port notation + sender: sender email address + login: sender login name and password in login:password notation + or None if no authentication is required + tls: enables/disables encryption (True by default) + + In Google App Engine use:: + + server='gae' + + For sake of backward compatibility all fields are optional and default + to None, however, to be able to send emails at least server and sender + must be specified. They are available under following fields: + + mail.settings.server + mail.settings.sender + mail.settings.login + + When server is 'logging', email is logged but not sent (debug mode) + + Optionally you can use PGP encryption or X509: + + mail.settings.cipher_type = None + mail.settings.sign = True + mail.settings.sign_passphrase = None + mail.settings.encrypt = True + mail.settings.x509_sign_keyfile = None + mail.settings.x509_sign_certfile = None + mail.settings.x509_crypt_certfiles = None + + cipher_type : None + gpg - need a python-pyme package and gpgme lib + x509 - smime + sign : sign the message (True or False) + sign_passphrase : passphrase for key signing + encrypt : encrypt the message + ... x509 only ... + x509_sign_keyfile : the signers private key filename (PEM format) + x509_sign_certfile: the signers certificate filename (PEM format) + x509_crypt_certfiles: the certificates file to encrypt the messages + with can be a file name or a list of + file names (PEM format) + + Examples:: + + #Create Mail object with authentication data for remote server: + mail = Mail('example.com:25', 'me@example.com', 'me:password') + """ + + settings = self.settings = Settings() + settings.server = server + settings.sender = sender + settings.login = login + settings.tls = tls + settings.ssl = False + settings.cipher_type = None + settings.sign = True + settings.sign_passphrase = None + settings.encrypt = True + settings.x509_sign_keyfile = None + settings.x509_sign_certfile = None + settings.x509_crypt_certfiles = None + settings.debug = False + settings.lock_keys = True + self.result = {} + self.error = None + + def send( + self, + to, + subject='None', + message='None', + attachments=None, + cc=None, + bcc=None, + reply_to=None, + encoding='utf-8', + ): + """ + Sends an email using data specified in constructor + + Arguments:: + + to: list or tuple of receiver addresses; will also accept single + object + subject: subject of the email + message: email body text; depends on type of passed object: + if 2-list or 2-tuple is passed: first element will be + source of plain text while second of html text; + otherwise: object will be the only source of plain text + and html source will be set to None; + If text or html source is: + None: content part will be ignored, + string: content part will be set to it, + file-like object: content part will be fetched from + it using it's read() method + attachments: list or tuple of Mail.Attachment objects; will also + accept single object + cc: list or tuple of carbon copy receiver addresses; will also + accept single object + bcc: list or tuple of blind carbon copy receiver addresses; will + also accept single object + reply_to: address to which reply should be composed + encoding: encoding of all strings passed to this method (including + message bodies) + + Examples:: + + #Send plain text message to single address: + mail.send('you@example.com', + 'Message subject', + 'Plain text body of the message') + + #Send html message to single address: + mail.send('you@example.com', + 'Message subject', + 'Plain text body of the message') + + #Send text and html message to three addresses (two in cc): + mail.send('you@example.com', + 'Message subject', + ('Plain text body', 'html body'), + cc=['other1@example.com', 'other2@example.com']) + + #Send html only message with image attachment available from + the message by 'photo' content id: + mail.send('you@example.com', + 'Message subject', + (None, ''), + Mail.Attachment('/path/to/photo.jpg' + content_id='photo')) + + #Send email with two attachments and no body text + mail.send('you@example.com, + 'Message subject', + None, + [Mail.Attachment('/path/to/fist.file'), + Mail.Attachment('/path/to/second.file')]) + + Returns True on success, False on failure. + + Before return, method updates two object's fields: + self.result: return value of smtplib.SMTP.sendmail() or GAE's + mail.send_mail() method + self.error: Exception message or None if above was successful + """ + + def encode_header(key): + if [c for c in key if 32>ord(c) or ord(c)>127]: + return Header.Header(key.encode('utf-8'),'utf-8') + else: + return key + + if not isinstance(self.settings.server, str): + raise Exception('Server address not specified') + if not isinstance(self.settings.sender, str): + raise Exception('Sender address not specified') + payload_in = MIMEMultipart.MIMEMultipart('mixed') + if to: + if not isinstance(to, (list,tuple)): + to = [to] + else: + raise Exception('Target receiver address not specified') + if cc: + if not isinstance(cc, (list, tuple)): + cc = [cc] + if bcc: + if not isinstance(bcc, (list, tuple)): + bcc = [bcc] + if message == None: + text = html = None + elif isinstance(message, (list, tuple)): + text, html = message + elif message.strip().startswith(''): + text = self.settings.server=='gae' and message or None + html = message + else: + text = message + html = None + if text != None or html != None: + attachment = MIMEMultipart.MIMEMultipart('alternative') + if text != None: + if isinstance(text, basestring): + text = text.decode(encoding).encode('utf-8') + else: + text = text.read().decode(encoding).encode('utf-8') + attachment.attach(MIMEText.MIMEText(text,_charset='utf-8')) + if html != None: + if isinstance(html, basestring): + html = html.decode(encoding).encode('utf-8') + else: + html = html.read().decode(encoding).encode('utf-8') + attachment.attach(MIMEText.MIMEText(html, 'html',_charset='utf-8')) + payload_in.attach(attachment) + if attachments == None: + pass + elif isinstance(attachments, (list, tuple)): + for attachment in attachments: + payload_in.attach(attachment) + else: + payload_in.attach(attachments) + + + ####################################################### + # CIPHER # + ####################################################### + cipher_type = self.settings.cipher_type + sign = self.settings.sign + sign_passphrase = self.settings.sign_passphrase + encrypt = self.settings.encrypt + ####################################################### + # GPGME # + ####################################################### + if cipher_type == 'gpg': + if not sign and not encrypt: + self.error="No sign and no encrypt is set but cipher type to gpg" + return False + + # need a python-pyme package and gpgme lib + from pyme import core, errors + from pyme.constants.sig import mode + ############################################ + # sign # + ############################################ + if sign: + import string + core.check_version(None) + pin=string.replace(payload_in.as_string(),'\n','\r\n') + plain = core.Data(pin) + sig = core.Data() + c = core.Context() + c.set_armor(1) + c.signers_clear() + # search for signing key for From: + for sigkey in c.op_keylist_all(self.settings.sender, 1): + if sigkey.can_sign: + c.signers_add(sigkey) + if not c.signers_enum(0): + self.error='No key for signing [%s]' % self.settings.sender + return False + c.set_passphrase_cb(lambda x,y,z: sign_passphrase) + try: + # make a signature + c.op_sign(plain,sig,mode.DETACH) + sig.seek(0,0) + # make it part of the email + payload=MIMEMultipart.MIMEMultipart('signed', + boundary=None, + _subparts=None, + **dict(micalg="pgp-sha1", + protocol="application/pgp-signature")) + # insert the origin payload + payload.attach(payload_in) + # insert the detached signature + p=MIMEBase.MIMEBase("application",'pgp-signature') + p.set_payload(sig.read()) + payload.attach(p) + # it's just a trick to handle the no encryption case + payload_in=payload + except errors.GPGMEError, ex: + self.error="GPG error: %s" % ex.getstring() + return False + ############################################ + # encrypt # + ############################################ + if encrypt: + core.check_version(None) + plain = core.Data(payload_in.as_string()) + cipher = core.Data() + c = core.Context() + c.set_armor(1) + # collect the public keys for encryption + recipients=[] + rec=to[:] + if cc: + rec.extend(cc) + if bcc: + rec.extend(bcc) + for addr in rec: + c.op_keylist_start(addr,0) + r = c.op_keylist_next() + if r == None: + self.error='No key for [%s]' % addr + return False + recipients.append(r) + try: + # make the encryption + c.op_encrypt(recipients, 1, plain, cipher) + cipher.seek(0,0) + # make it a part of the email + payload=MIMEMultipart.MIMEMultipart('encrypted', + boundary=None, + _subparts=None, + **dict(protocol="application/pgp-encrypted")) + p=MIMEBase.MIMEBase("application",'pgp-encrypted') + p.set_payload("Version: 1\r\n") + payload.attach(p) + p=MIMEBase.MIMEBase("application",'octet-stream') + p.set_payload(cipher.read()) + payload.attach(p) + except errors.GPGMEError, ex: + self.error="GPG error: %s" % ex.getstring() + return False + ####################################################### + # X.509 # + ####################################################### + elif cipher_type == 'x509': + if not sign and not encrypt: + self.error="No sign and no encrypt is set but cipher type to x509" + return False + x509_sign_keyfile=self.settings.x509_sign_keyfile + if self.settings.x509_sign_certfile: + x509_sign_certfile=self.settings.x509_sign_certfile + else: + # if there is no sign certfile we'll assume the + # cert is in keyfile + x509_sign_certfile=self.settings.x509_sign_keyfile + # crypt certfiles could be a string or a list + x509_crypt_certfiles=self.settings.x509_crypt_certfiles + + + # need m2crypto + from M2Crypto import BIO, SMIME, X509 + msg_bio = BIO.MemoryBuffer(payload_in.as_string()) + s = SMIME.SMIME() + + # SIGN + if sign: + #key for signing + try: + s.load_key(x509_sign_keyfile, x509_sign_certfile, callback=lambda x: sign_passphrase) + if encrypt: + p7 = s.sign(msg_bio) + else: + p7 = s.sign(msg_bio,flags=SMIME.PKCS7_DETACHED) + msg_bio = BIO.MemoryBuffer(payload_in.as_string()) # Recreate coz sign() has consumed it. + except Exception,e: + self.error="Something went wrong on signing: <%s>" %str(e) + return False + + # ENCRYPT + if encrypt: + try: + sk = X509.X509_Stack() + if not isinstance(x509_crypt_certfiles, (list, tuple)): + x509_crypt_certfiles = [x509_crypt_certfiles] + + # make an encryption cert's stack + for x in x509_crypt_certfiles: + sk.push(X509.load_cert(x)) + s.set_x509_stack(sk) + + s.set_cipher(SMIME.Cipher('des_ede3_cbc')) + tmp_bio = BIO.MemoryBuffer() + if sign: + s.write(tmp_bio, p7) + else: + tmp_bio.write(payload_in.as_string()) + p7 = s.encrypt(tmp_bio) + except Exception,e: + self.error="Something went wrong on encrypting: <%s>" %str(e) + return False + + # Final stage in sign and encryption + out = BIO.MemoryBuffer() + if encrypt: + s.write(out, p7) + else: + if sign: + s.write(out, p7, msg_bio, SMIME.PKCS7_DETACHED) + else: + out.write('\r\n') + out.write(payload_in.as_string()) + out.close() + st=str(out.read()) + payload=message_from_string(st) + else: + # no cryptography process as usual + payload=payload_in + payload['From'] = encode_header(self.settings.sender.decode(encoding)) + origTo = to[:] + if to: + payload['To'] = encode_header(', '.join(to).decode(encoding)) + if reply_to: + payload['Reply-To'] = encode_header(reply_to.decode(encoding)) + if cc: + payload['Cc'] = encode_header(', '.join(cc).decode(encoding)) + to.extend(cc) + if bcc: + to.extend(bcc) + payload['Subject'] = encode_header(subject.decode(encoding)) + payload['Date'] = time.strftime("%a, %d %b %Y %H:%M:%S +0000", + time.gmtime()) + result = {} + try: + if self.settings.server == 'logging': + logger.warn('email not sent\n%s\nFrom: %s\nTo: %s\n\n%s\n%s\n' % \ + ('-'*40,self.settings.sender, + ', '.join(to),text or html,'-'*40)) + elif self.settings.server == 'gae': + xcc = dict() + if cc: + xcc['cc'] = cc + if bcc: + xcc['bcc'] = bcc + from google.appengine.api import mail + attachments = attachments and [(a.my_filename,a.my_payload) for a in attachments] + if attachments: + result = mail.send_mail(sender=self.settings.sender, to=origTo, + subject=subject, body=text, html=html, + attachments=attachments, **xcc) + elif html: + result = mail.send_mail(sender=self.settings.sender, to=origTo, + subject=subject, body=text, html=html, **xcc) + else: + result = mail.send_mail(sender=self.settings.sender, to=origTo, + subject=subject, body=text, **xcc) + else: + smtp_args = self.settings.server.split(':') + if self.settings.ssl: + server = smtplib.SMTP_SSL(*smtp_args) + else: + server = smtplib.SMTP(*smtp_args) + if self.settings.tls and not self.settings.ssl: + server.ehlo() + server.starttls() + server.ehlo() + if self.settings.login != None: + server.login(*self.settings.login.split(':',1)) + result = server.sendmail(self.settings.sender, to, payload.as_string()) + server.quit() + except Exception, e: + logger.warn('Mail.send failure:%s' % e) + self.result = result + self.error = e + return False + self.result = result + self.error = None + return True + + +class Recaptcha(DIV): + + API_SSL_SERVER = 'https://www.google.com/recaptcha/api' + API_SERVER = 'http://www.google.com/recaptcha/api' + VERIFY_SERVER = 'http://www.google.com/recaptcha/api/verify' + + def __init__( + self, + request, + public_key='', + private_key='', + use_ssl=False, + error=None, + error_message='invalid', + label = 'Verify:', + options = '' + ): + self.remote_addr = request.env.remote_addr + self.public_key = public_key + self.private_key = private_key + self.use_ssl = use_ssl + self.error = error + self.errors = Storage() + self.error_message = error_message + self.components = [] + self.attributes = {} + self.label = label + self.options = options + self.comment = '' + + def _validate(self): + + # for local testing: + + recaptcha_challenge_field = \ + self.request_vars.recaptcha_challenge_field + recaptcha_response_field = \ + self.request_vars.recaptcha_response_field + private_key = self.private_key + remoteip = self.remote_addr + if not (recaptcha_response_field and recaptcha_challenge_field + and len(recaptcha_response_field) + and len(recaptcha_challenge_field)): + self.errors['captcha'] = self.error_message + return False + params = urllib.urlencode({ + 'privatekey': private_key, + 'remoteip': remoteip, + 'challenge': recaptcha_challenge_field, + 'response': recaptcha_response_field, + }) + request = urllib2.Request( + url=self.VERIFY_SERVER, + data=params, + headers={'Content-type': 'application/x-www-form-urlencoded', + 'User-agent': 'reCAPTCHA Python'}) + httpresp = urllib2.urlopen(request) + return_values = httpresp.read().splitlines() + httpresp.close() + return_code = return_values[0] + if return_code == 'true': + del self.request_vars.recaptcha_challenge_field + del self.request_vars.recaptcha_response_field + self.request_vars.captcha = '' + return True + self.errors['captcha'] = self.error_message + return False + + def xml(self): + public_key = self.public_key + use_ssl = self.use_ssl + error_param = '' + if self.error: + error_param = '&error=%s' % self.error + if use_ssl: + server = self.API_SSL_SERVER + else: + server = self.API_SERVER + captcha = DIV( + SCRIPT("var RecaptchaOptions = {%s};" % self.options), + SCRIPT(_type="text/javascript", + _src="%s/challenge?k=%s%s" % (server,public_key,error_param)), + TAG.noscript(IFRAME(_src="%s/noscript?k=%s%s" % (server,public_key,error_param), + _height="300",_width="500",_frameborder="0"), BR(), + INPUT(_type='hidden', _name='recaptcha_response_field', + _value='manual_challenge')), _id='recaptcha') + if not self.errors.captcha: + return XML(captcha).xml() + else: + captcha.append(DIV(self.errors['captcha'], _class='error')) + return XML(captcha).xml() + + +def addrow(form,a,b,c,style,_id,position=-1): + if style == "divs": + form[0].insert(position, DIV(DIV(LABEL(a),_class='w2p_fl'), + DIV(b, _class='w2p_fw'), + DIV(c, _class='w2p_fc'), + _id = _id)) + elif style == "table2cols": + form[0].insert(position, TR(LABEL(a),'')) + form[0].insert(position+1, TR(b, _colspan=2, _id = _id)) + elif style == "ul": + form[0].insert(position, LI(DIV(LABEL(a),_class='w2p_fl'), + DIV(b, _class='w2p_fw'), + DIV(c, _class='w2p_fc'), + _id = _id)) + else: + form[0].insert(position, TR(LABEL(a),b,c,_id = _id)) + + +class Auth(object): + """ + Class for authentication, authorization, role based access control. + + Includes: + + - registration and profile + - login and logout + - username and password retrieval + - event logging + - role creation and assignment + - user defined group/role based permission + + Authentication Example:: + + from contrib.utils import * + mail=Mail() + mail.settings.server='smtp.gmail.com:587' + mail.settings.sender='you@somewhere.com' + mail.settings.login='username:password' + auth=Auth(globals(), db) + auth.settings.mailer=mail + # auth.settings....=... + auth.define_tables() + def authentication(): + return dict(form=auth()) + + exposes: + + - http://.../{application}/{controller}/authentication/login + - http://.../{application}/{controller}/authentication/logout + - http://.../{application}/{controller}/authentication/register + - http://.../{application}/{controller}/authentication/verify_email + - http://.../{application}/{controller}/authentication/retrieve_username + - http://.../{application}/{controller}/authentication/retrieve_password + - http://.../{application}/{controller}/authentication/reset_password + - http://.../{application}/{controller}/authentication/profile + - http://.../{application}/{controller}/authentication/change_password + + On registration a group with role=new_user.id is created + and user is given membership of this group. + + You can create a group with:: + + group_id=auth.add_group('Manager', 'can access the manage action') + auth.add_permission(group_id, 'access to manage') + + Here \"access to manage\" is just a user defined string. + You can give access to a user:: + + auth.add_membership(group_id, user_id) + + If user id is omitted, the logged in user is assumed + + Then you can decorate any action:: + + @auth.requires_permission('access to manage') + def manage(): + return dict() + + You can restrict a permission to a specific table:: + + auth.add_permission(group_id, 'edit', db.sometable) + @auth.requires_permission('edit', db.sometable) + + Or to a specific record:: + + auth.add_permission(group_id, 'edit', db.sometable, 45) + @auth.requires_permission('edit', db.sometable, 45) + + If authorization is not granted calls:: + + auth.settings.on_failed_authorization + + Other options:: + + auth.settings.mailer=None + auth.settings.expiration=3600 # seconds + + ... + + ### these are messages that can be customized + ... + """ + + + def url(self, f=None, args=[], vars={}): + return URL(c=self.settings.controller,f=f,args=args,vars=vars) + + def __init__(self, environment=None, db=None, + controller='default', cas_provider = None): + """ + auth=Auth(globals(), db) + + - environment is there for legacy but unused (awful) + - db has to be the database where to create tables for authentication + + """ + ## next two lines for backward compatibility + if not db and environment and isinstance(environment,DAL): + db = environment + self.db = db + self.environment = current + request = current.request + session = current.session + auth = session.auth + if auth and auth.last_visit and auth.last_visit + \ + datetime.timedelta(days=0, seconds=auth.expiration) > request.now: + self.user = auth.user + # this is a trick to speed up sessions + if (request.now - auth.last_visit).seconds > (auth.expiration/10): + auth.last_visit = request.now + else: + self.user = None + session.auth = None + settings = self.settings = Settings() + + # ## what happens after login? + + # ## what happens after registration? + + settings.hideerror = False + settings.cas_domains = [request.env.http_host] + settings.cas_provider = cas_provider + settings.extra_fields = {} + settings.actions_disabled = [] + settings.reset_password_requires_verification = False + settings.registration_requires_verification = False + settings.registration_requires_approval = False + settings.alternate_requires_registration = False + settings.create_user_groups = True + + settings.controller = controller + settings.login_url = self.url('user', args='login') + settings.logged_url = self.url('user', args='profile') + settings.download_url = self.url('download') + settings.mailer = None + settings.login_captcha = None + settings.register_captcha = None + settings.retrieve_username_captcha = None + settings.retrieve_password_captcha = None + settings.captcha = None + settings.expiration = 3600 # one hour + settings.long_expiration = 3600*30*24 # one month + settings.remember_me_form = True + settings.allow_basic_login = False + settings.allow_basic_login_only = False + settings.on_failed_authorization = \ + self.url('user',args='not_authorized') + + settings.on_failed_authentication = lambda x: redirect(x) + + settings.formstyle = 'table3cols' + settings.label_separator = ': ' + + # ## table names to be used + + settings.password_field = 'password' + settings.table_user_name = 'auth_user' + settings.table_group_name = 'auth_group' + settings.table_membership_name = 'auth_membership' + settings.table_permission_name = 'auth_permission' + settings.table_event_name = 'auth_event' + settings.table_cas_name = 'auth_cas' + + # ## if none, they will be created + + settings.table_user = None + settings.table_group = None + settings.table_membership = None + settings.table_permission = None + settings.table_event = None + settings.table_cas = None + + # ## + + settings.showid = False + + # ## these should be functions or lambdas + + settings.login_next = self.url('index') + settings.login_onvalidation = [] + settings.login_onaccept = [] + settings.login_methods = [self] + settings.login_form = self + settings.login_email_validate = True + settings.login_userfield = None + + settings.logout_next = self.url('index') + settings.logout_onlogout = None + + settings.register_next = self.url('index') + settings.register_onvalidation = [] + settings.register_onaccept = [] + settings.register_fields = None + + settings.verify_email_next = self.url('user', args='login') + settings.verify_email_onaccept = [] + + settings.profile_next = self.url('index') + settings.profile_onvalidation = [] + settings.profile_onaccept = [] + settings.profile_fields = None + settings.retrieve_username_next = self.url('index') + settings.retrieve_password_next = self.url('index') + settings.request_reset_password_next = self.url('user', args='login') + settings.reset_password_next = self.url('user', args='login') + + settings.change_password_next = self.url('index') + settings.change_password_onvalidation = [] + settings.change_password_onaccept = [] + + settings.retrieve_password_onvalidation = [] + settings.reset_password_onvalidation = [] + + settings.hmac_key = None + settings.lock_keys = True + + + # ## these are messages that can be customized + messages = self.messages = Messages(current.T) + messages.login_button = 'Login' + messages.register_button = 'Register' + messages.password_reset_button = 'Request reset password' + messages.password_change_button = 'Change password' + messages.profile_save_button = 'Save profile' + messages.submit_button = 'Submit' + messages.verify_password = 'Verify Password' + messages.delete_label = 'Check to delete:' + messages.function_disabled = 'Function disabled' + messages.access_denied = 'Insufficient privileges' + messages.registration_verifying = 'Registration needs verification' + messages.registration_pending = 'Registration is pending approval' + messages.login_disabled = 'Login disabled by administrator' + messages.logged_in = 'Logged in' + messages.email_sent = 'Email sent' + messages.unable_to_send_email = 'Unable to send email' + messages.email_verified = 'Email verified' + messages.logged_out = 'Logged out' + messages.registration_successful = 'Registration successful' + messages.invalid_email = 'Invalid email' + messages.unable_send_email = 'Unable to send email' + messages.invalid_login = 'Invalid login' + messages.invalid_user = 'Invalid user' + messages.invalid_password = 'Invalid password' + messages.is_empty = "Cannot be empty" + messages.mismatched_password = "Password fields don't match" + messages.verify_email = \ + 'Click on the link http://...verify_email/%(key)s to verify your email' + messages.verify_email_subject = 'Email verification' + messages.username_sent = 'Your username was emailed to you' + messages.new_password_sent = 'A new password was emailed to you' + messages.password_changed = 'Password changed' + messages.retrieve_username = 'Your username is: %(username)s' + messages.retrieve_username_subject = 'Username retrieve' + messages.retrieve_password = 'Your password is: %(password)s' + messages.retrieve_password_subject = 'Password retrieve' + messages.reset_password = \ + 'Click on the link http://...reset_password/%(key)s to reset your password' + messages.reset_password_subject = 'Password reset' + messages.invalid_reset_password = 'Invalid reset password' + messages.profile_updated = 'Profile updated' + messages.new_password = 'New password' + messages.old_password = 'Old password' + messages.group_description = \ + 'Group uniquely assigned to user %(id)s' + + messages.register_log = 'User %(id)s Registered' + messages.login_log = 'User %(id)s Logged-in' + messages.login_failed_log = None + messages.logout_log = 'User %(id)s Logged-out' + messages.profile_log = 'User %(id)s Profile updated' + messages.verify_email_log = 'User %(id)s Verification email sent' + messages.retrieve_username_log = 'User %(id)s Username retrieved' + messages.retrieve_password_log = 'User %(id)s Password retrieved' + messages.reset_password_log = 'User %(id)s Password reset' + messages.change_password_log = 'User %(id)s Password changed' + messages.add_group_log = 'Group %(group_id)s created' + messages.del_group_log = 'Group %(group_id)s deleted' + messages.add_membership_log = None + messages.del_membership_log = None + messages.has_membership_log = None + messages.add_permission_log = None + messages.del_permission_log = None + messages.has_permission_log = None + messages.impersonate_log = 'User %(id)s is impersonating %(other_id)s' + + messages.label_first_name = 'First name' + messages.label_last_name = 'Last name' + messages.label_username = 'Username' + messages.label_email = 'E-mail' + messages.label_password = 'Password' + messages.label_registration_key = 'Registration key' + messages.label_reset_password_key = 'Reset Password key' + messages.label_registration_id = 'Registration identifier' + messages.label_role = 'Role' + messages.label_description = 'Description' + messages.label_user_id = 'User ID' + messages.label_group_id = 'Group ID' + messages.label_name = 'Name' + messages.label_table_name = 'Table name' + messages.label_record_id = 'Record ID' + messages.label_time_stamp = 'Timestamp' + messages.label_client_ip = 'Client IP' + messages.label_origin = 'Origin' + messages.label_remember_me = "Remember me (for 30 days)" + messages['T'] = current.T + messages.verify_password_comment = 'please input your password again' + messages.lock_keys = True + + # for "remember me" option + response = current.response + if auth and auth.remember: #when user wants to be logged in for longer + response.cookies[response.session_id_name]["expires"] = \ + auth.expiration + + def lazy_user (auth = self): return auth.user_id + reference_user = 'reference %s' % settings.table_user_name + def represent(id,record=None,s=settings): + try: + user = s.table_user(id) + return '%(first_name)s %(last_name)s' % user + except: return id + self.signature = db.Table(self.db,'auth_signature', + Field('is_active','boolean',default=True), + Field('created_on','datetime', + default=request.now, + writable=False,readable=False), + Field('created_by', + reference_user, + default=lazy_user,represent=represent, + writable=False,readable=False, + ), + Field('modified_on','datetime', + update=request.now,default=request.now, + writable=False,readable=False), + Field('modified_by', + reference_user,represent=represent, + default=lazy_user,update=lazy_user, + writable=False,readable=False)) + + + + def _get_user_id(self): + "accessor for auth.user_id" + return self.user and self.user.id or None + user_id = property(_get_user_id, doc="user.id or None") + + def _HTTP(self, *a, **b): + """ + only used in lambda: self._HTTP(404) + """ + + raise HTTP(*a, **b) + + def __call__(self): + """ + usage: + + def authentication(): return dict(form=auth()) + """ + + request = current.request + args = request.args + if not args: + redirect(self.url(args='login',vars=request.vars)) + elif args[0] in self.settings.actions_disabled: + raise HTTP(404) + if args[0] in ('login','logout','register','verify_email', + 'retrieve_username','retrieve_password', + 'reset_password','request_reset_password', + 'change_password','profile','groups', + 'impersonate','not_authorized'): + return getattr(self,args[0])() + elif args[0]=='cas' and not self.settings.cas_provider: + if args(1) == 'login': return self.cas_login(version=2) + if args(1) == 'validate': return self.cas_validate(version=2) + if args(1) == 'logout': return self.logout() + else: + raise HTTP(404) + + def navbar(self,prefix='Welcome',action=None): + request = current.request + T = current.T + if isinstance(prefix,str): + prefix = T(prefix) + if not action: + action=URL(request.application,request.controller,'user') + if prefix: + prefix = prefix.strip()+' ' + if self.user_id: + logout=A(T('logout'),_href=action+'/logout') + profile=A(T('profile'),_href=action+'/profile') + password=A(T('password'),_href=action+'/change_password') + bar = SPAN(prefix,self.user.first_name,' [ ', logout, ']',_class='auth_navbar') + if not 'profile' in self.settings.actions_disabled: + bar.insert(4, ' | ') + bar.insert(5, profile) + if not 'change_password' in self.settings.actions_disabled: + bar.insert(-1, ' | ') + bar.insert(-1, password) + else: + login=A(T('login'),_href=action+'/login') + register=A(T('register'),_href=action+'/register') + retrieve_username=A(T('forgot username?'), + _href=action+'/retrieve_username') + lost_password=A(T('lost password?'), + _href=action+'/request_reset_password') + bar = SPAN('[ ',login,' ]',_class='auth_navbar') + + if not 'register' in self.settings.actions_disabled: + bar.insert(2, ' | ') + bar.insert(3, register) + if 'username' in self.settings.table_user.fields() and \ + not 'retrieve_username' in self.settings.actions_disabled: + bar.insert(-1, ' | ') + bar.insert(-1, retrieve_username) + if not 'request_reset_password' in self.settings.actions_disabled: + bar.insert(-1, ' | ') + bar.insert(-1, lost_password) + return bar + + def __get_migrate(self, tablename, migrate=True): + + if type(migrate).__name__ == 'str': + return (migrate + tablename + '.table') + elif migrate == False: + return False + else: + return True + + def define_tables(self, username=False, migrate=True, fake_migrate=False): + """ + to be called unless tables are defined manually + + usages:: + + # defines all needed tables and table files + # 'myprefix_auth_user.table', ... + auth.define_tables(migrate='myprefix_') + + # defines all needed tables without migration/table files + auth.define_tables(migrate=False) + + """ + + db = self.db + settings = self.settings + if not settings.table_user_name in db.tables: + passfield = settings.password_field + if username or settings.cas_provider: + table = db.define_table( + settings.table_user_name, + Field('first_name', length=128, default='', + label=self.messages.label_first_name), + Field('last_name', length=128, default='', + label=self.messages.label_last_name), + Field('username', length=128, default='', + label=self.messages.label_username), + Field('email', length=512, default='', + label=self.messages.label_email), + Field(passfield, 'password', length=512, + readable=False, label=self.messages.label_password), + Field('registration_key', length=512, + writable=False, readable=False, default='', + label=self.messages.label_registration_key), + Field('reset_password_key', length=512, + writable=False, readable=False, default='', + label=self.messages.label_reset_password_key), + Field('registration_id', length=512, + writable=False, readable=False, default='', + label=self.messages.label_registration_id), + *settings.extra_fields.get(settings.table_user_name,[]), + **dict( + migrate=self.__get_migrate(settings.table_user_name, + migrate), + fake_migrate=fake_migrate, + format='%(username)s')) + table.username.requires = (IS_MATCH('[\w\.\-]+'), + IS_NOT_IN_DB(db, table.username)) + else: + table = db.define_table( + settings.table_user_name, + Field('first_name', length=128, default='', + label=self.messages.label_first_name), + Field('last_name', length=128, default='', + label=self.messages.label_last_name), + Field('email', length=512, default='', + label=self.messages.label_email), + Field(passfield, 'password', length=512, + readable=False, label=self.messages.label_password), + Field('registration_key', length=512, + writable=False, readable=False, default='', + label=self.messages.label_registration_key), + Field('reset_password_key', length=512, + writable=False, readable=False, default='', + label=self.messages.label_reset_password_key), + *settings.extra_fields.get(settings.table_user_name,[]), + **dict( + migrate=self.__get_migrate(settings.table_user_name, + migrate), + fake_migrate=fake_migrate, + format='%(first_name)s %(last_name)s (%(id)s)')) + table.first_name.requires = \ + IS_NOT_EMPTY(error_message=self.messages.is_empty) + table.last_name.requires = \ + IS_NOT_EMPTY(error_message=self.messages.is_empty) + table[passfield].requires = [CRYPT(key=settings.hmac_key)] + table.email.requires = \ + [IS_EMAIL(error_message=self.messages.invalid_email), + IS_NOT_IN_DB(db, table.email)] + table.registration_key.default = '' + settings.table_user = db[settings.table_user_name] + if not settings.table_group_name in db.tables: + table = db.define_table( + settings.table_group_name, + Field('role', length=512, default='', + label=self.messages.label_role), + Field('description', 'text', + label=self.messages.label_description), + *settings.extra_fields.get(settings.table_group_name,[]), + **dict( + migrate=self.__get_migrate( + settings.table_group_name, migrate), + fake_migrate=fake_migrate, + format = '%(role)s (%(id)s)')) + table.role.requires = IS_NOT_IN_DB(db, '%s.role' + % settings.table_group_name) + settings.table_group = db[settings.table_group_name] + if not settings.table_membership_name in db.tables: + table = db.define_table( + settings.table_membership_name, + Field('user_id', settings.table_user, + label=self.messages.label_user_id), + Field('group_id', settings.table_group, + label=self.messages.label_group_id), + *settings.extra_fields.get(settings.table_membership_name,[]), + **dict( + migrate=self.__get_migrate( + settings.table_membership_name, migrate), + fake_migrate=fake_migrate)) + table.user_id.requires = IS_IN_DB(db, '%s.id' % + settings.table_user_name, + '%(first_name)s %(last_name)s (%(id)s)') + table.group_id.requires = IS_IN_DB(db, '%s.id' % + settings.table_group_name, + '%(role)s (%(id)s)') + settings.table_membership = db[settings.table_membership_name] + if not settings.table_permission_name in db.tables: + table = db.define_table( + settings.table_permission_name, + Field('group_id', settings.table_group, + label=self.messages.label_group_id), + Field('name', default='default', length=512, + label=self.messages.label_name), + Field('table_name', length=512, + label=self.messages.label_table_name), + Field('record_id', 'integer',default=0, + label=self.messages.label_record_id), + *settings.extra_fields.get(settings.table_permission_name,[]), + **dict( + migrate=self.__get_migrate( + settings.table_permission_name, migrate), + fake_migrate=fake_migrate)) + table.group_id.requires = IS_IN_DB(db, '%s.id' % + settings.table_group_name, + '%(role)s (%(id)s)') + table.name.requires = IS_NOT_EMPTY(error_message=self.messages.is_empty) + table.table_name.requires = IS_EMPTY_OR(IS_IN_SET(self.db.tables)) + table.record_id.requires = IS_INT_IN_RANGE(0, 10 ** 9) + settings.table_permission = db[settings.table_permission_name] + if not settings.table_event_name in db.tables: + table = db.define_table( + settings.table_event_name, + Field('time_stamp', 'datetime', + default=current.request.now, + label=self.messages.label_time_stamp), + Field('client_ip', + default=current.request.client, + label=self.messages.label_client_ip), + Field('user_id', settings.table_user, default=None, + label=self.messages.label_user_id), + Field('origin', default='auth', length=512, + label=self.messages.label_origin), + Field('description', 'text', default='', + label=self.messages.label_description), + *settings.extra_fields.get(settings.table_event_name,[]), + **dict( + migrate=self.__get_migrate( + settings.table_event_name, migrate), + fake_migrate=fake_migrate)) + table.user_id.requires = IS_IN_DB(db, '%s.id' % + settings.table_user_name, + '%(first_name)s %(last_name)s (%(id)s)') + table.origin.requires = IS_NOT_EMPTY(error_message=self.messages.is_empty) + table.description.requires = IS_NOT_EMPTY(error_message=self.messages.is_empty) + settings.table_event = db[settings.table_event_name] + now = current.request.now + if settings.cas_domains: + if not settings.table_cas_name in db.tables: + table = db.define_table( + settings.table_cas_name, + Field('user_id', settings.table_user, default=None, + label=self.messages.label_user_id), + Field('created_on','datetime',default=now), + Field('url',requires=IS_URL()), + Field('uuid'), + *settings.extra_fields.get(settings.table_cas_name,[]), + **dict( + migrate=self.__get_migrate( + settings.table_event_name, migrate), + fake_migrate=fake_migrate)) + table.user_id.requires = IS_IN_DB(db, '%s.id' % \ + settings.table_user_name, + '%(first_name)s %(last_name)s (%(id)s)') + settings.table_cas = db[settings.table_cas_name] + if settings.cas_provider: + settings.actions_disabled = \ + ['profile','register','change_password','request_reset_password'] + from gluon.contrib.login_methods.cas_auth import CasAuth + maps = dict((name,lambda v,n=name:v.get(n,None)) for name in \ + settings.table_user.fields if name!='id' \ + and settings.table_user[name].readable) + maps['registration_id'] = \ + lambda v,p=settings.cas_provider:'%s/%s' % (p,v['user']) + settings.login_form = CasAuth( + casversion = 2, + urlbase = settings.cas_provider, + actions=['login','validate','logout'], + maps=maps) + + + def log_event(self, description, origin='auth'): + """ + usage:: + + auth.log_event(description='this happened', origin='auth') + """ + + if self.is_logged_in(): + user_id = self.user.id + else: + user_id = None # user unknown + self.settings.table_event.insert(description=description, + origin=origin, user_id=user_id) + + def get_or_create_user(self, keys): + """ + Used for alternate login methods: + If the user exists already then password is updated. + If the user doesn't yet exist, then they are created. + """ + table_user = self.settings.table_user + if 'registration_id' in table_user.fields() and \ + 'registration_id' in keys: + username = 'registration_id' + elif 'username' in table_user.fields(): + username = 'username' + elif 'email' in table_user.fields(): + username = 'email' + else: + raise SyntaxError, "user must have username or email" + passfield = self.settings.password_field + user = self.db(table_user[username] == keys[username]).select().first() + keys['registration_key']='' + if user: + user.update_record(**table_user._filter_fields(keys)) + else: + if not 'first_name' in keys and 'first_name' in table_user.fields: + keys['first_name'] = keys[username] + user_id = table_user.insert(**table_user._filter_fields(keys)) + user = self.user = table_user[user_id] + if self.settings.create_user_groups: + group_id = self.add_group("user_%s" % user_id) + self.add_membership(group_id, user_id) + return user + + def basic(self): + if not self.settings.allow_basic_login: + return False + basic = current.request.env.http_authorization + if not basic or not basic[:6].lower() == 'basic ': + return False + (username, password) = base64.b64decode(basic[6:]).split(':') + return self.login_bare(username, password) + + def login_bare(self, username, password): + """ + logins user + """ + + request = current.request + session = current.session + table_user = self.settings.table_user + if self.settings.login_userfield: + userfield = self.settings.login_userfield + elif 'username' in table_user.fields: + userfield = 'username' + else: + userfield = 'email' + passfield = self.settings.password_field + user = self.db(table_user[userfield] == username).select().first() + password = table_user[passfield].validate(password)[0] + if user: + if not user.registration_key and user[passfield] == password: + user = Storage(table_user._filter_fields(user, id=True)) + session.auth = Storage(user=user, last_visit=request.now, + expiration=self.settings.expiration, + hmac_key = web2py_uuid()) + self.user = user + return user + return False + + def cas_login( + self, + next=DEFAULT, + onvalidation=DEFAULT, + onaccept=DEFAULT, + log=DEFAULT, + version=2, + ): + request, session = current.request, current.session + db, table = self.db, self.settings.table_cas + session._cas_service = request.vars.service or session._cas_service + if not request.env.http_host in self.settings.cas_domains or \ + not session._cas_service: + raise HTTP(403,'not authorized') + def allow_access(): + row = table(url=session._cas_service,user_id=self.user.id) + if row: + row.update_record(created_on=request.now) + uuid = row.uuid + else: + uuid = web2py_uuid() + table.insert(url=session._cas_service, user_id=self.user.id, + uuid=uuid, created_on=request.now) + url = session._cas_service + del session._cas_service + redirect(url+"?ticket="+uuid) + if self.is_logged_in(): + allow_access() + def cas_onaccept(form, onaccept=onaccept): + if onaccept!=DEFAULT: onaccept(form) + allow_access() + return self.login(next,onvalidation,cas_onaccept,log) + + + def cas_validate(self,version=2): + request = current.request + db, table = self.db, self.settings.table_cas + current.response.headers['Content-Type']='text' + ticket = table(uuid=request.vars.ticket) + url = request.env.path_info.rsplit('/',1)[0] + if ticket: # and ticket.created_on>request.now-datetime.timedelta(60): + user = self.settings.table_user(ticket.user_id) + fullname = user.first_name+' '+user.last_name + if version==1: + raise HTTP(200,'yes\n%s:%s:%s'%(user.id,user.email,fullname)) + # assume version 2 + username = user.get('username',user.email) + raise HTTP(200,'\n'+\ + TAG['cas:serviceResponse']( + TAG['cas:authenticationSuccess']( + TAG['cas:user'](username), + *[TAG['cas:'+field.name](user[field.name]) \ + for field in self.settings.table_user \ + if field.readable]), + **{'_xmlns:cas':'http://www.yale.edu/tp/cas'}).xml()) + if version==1: + raise HTTP(200,'no\n') + # assume version 2 + raise HTTP(200,'\n'+\ + TAG['cas:serviceResponse']( + TAG['cas:authenticationFailure']( + 'Ticket %s not recognized' % ticket, + _code='INVALID TICKET'), + **{'_xmlns:cas':'http://www.yale.edu/tp/cas'}).xml()) + + + def login( + self, + next=DEFAULT, + onvalidation=DEFAULT, + onaccept=DEFAULT, + log=DEFAULT, + ): + """ + returns a login form + + .. method:: Auth.login([next=DEFAULT [, onvalidation=DEFAULT + [, onaccept=DEFAULT [, log=DEFAULT]]]]) + + """ + + table_user = self.settings.table_user + if self.settings.login_userfield: + username = self.settings.login_userfield + elif 'username' in table_user.fields: + username = 'username' + else: + username = 'email' + if 'username' in table_user.fields or not self.settings.login_email_validate: + tmpvalidator = IS_NOT_EMPTY(error_message=self.messages.is_empty) + else: + tmpvalidator = IS_EMAIL(error_message=self.messages.invalid_email) + old_requires = table_user[username].requires + table_user[username].requires = tmpvalidator + + request = current.request + response = current.response + session = current.session + + passfield = self.settings.password_field + if next == DEFAULT: + next = request.get_vars._next \ + or request.post_vars._next \ + or self.settings.login_next + if onvalidation == DEFAULT: + onvalidation = self.settings.login_onvalidation + if onaccept == DEFAULT: + onaccept = self.settings.login_onaccept + if log == DEFAULT: + log = self.messages.login_log + + user = None # default + + # do we use our own login form, or from a central source? + if self.settings.login_form == self: + form = SQLFORM( + table_user, + fields=[username, passfield], + hidden=dict(_next=next), + showid=self.settings.showid, + submit_button=self.messages.login_button, + delete_label=self.messages.delete_label, + formstyle=self.settings.formstyle, + separator=self.settings.label_separator + ) + + if self.settings.remember_me_form: + ## adds a new input checkbox "remember me for longer" + addrow(form,XML(" "), + DIV(XML(" "), + INPUT(_type='checkbox', + _class='checkbox', + _id="auth_user_remember", + _name="remember", + ), + XML("  "), + LABEL( + self.messages.label_remember_me, + _for="auth_user_remember", + )),"", + self.settings.formstyle, + 'auth_user_remember__row') + + captcha = self.settings.login_captcha or \ + (self.settings.login_captcha!=False and self.settings.captcha) + if captcha: + addrow(form, captcha.label, captcha, captcha.comment, self.settings.formstyle,'captcha__row') + accepted_form = False + + if form.accepts(request, session, + formname='login', dbio=False, + onvalidation=onvalidation, + hideerror=self.settings.hideerror): + + accepted_form = True + # check for username in db + user = self.db(table_user[username] == form.vars[username]).select().first() + if user: + # user in db, check if registration pending or disabled + temp_user = user + if temp_user.registration_key == 'pending': + response.flash = self.messages.registration_pending + return form + elif temp_user.registration_key in ('disabled','blocked'): + response.flash = self.messages.login_disabled + return form + elif temp_user.registration_key!=None and \ + temp_user.registration_key.strip(): + response.flash = \ + self.messages.registration_verifying + return form + # try alternate logins 1st as these have the + # current version of the password + user = None + for login_method in self.settings.login_methods: + if login_method != self and \ + login_method(request.vars[username], + request.vars[passfield]): + if not self in self.settings.login_methods: + # do not store password in db + form.vars[passfield] = None + user = self.get_or_create_user(form.vars) + break + if not user: + # alternates have failed, maybe because service inaccessible + if self.settings.login_methods[0] == self: + # try logging in locally using cached credentials + if temp_user[passfield] == form.vars.get(passfield, ''): + # success + user = temp_user + else: + # user not in db + if not self.settings.alternate_requires_registration: + # we're allowed to auto-register users from external systems + for login_method in self.settings.login_methods: + if login_method != self and \ + login_method(request.vars[username], + request.vars[passfield]): + if not self in self.settings.login_methods: + # do not store password in db + form.vars[passfield] = None + user = self.get_or_create_user(form.vars) + break + if not user: + if self.settings.login_failed_log: + self.log_event(self.settings.login_failed_log % request.post_vars) + # invalid login + session.flash = self.messages.invalid_login + redirect(self.url(args=request.args,vars=request.get_vars)) + + else: + # use a central authentication server + cas = self.settings.login_form + cas_user = cas.get_user() + + if cas_user: + cas_user[passfield] = None + user = self.get_or_create_user(table_user._filter_fields(cas_user)) + elif hasattr(cas,'login_form'): + return cas.login_form() + else: + # we need to pass through login again before going on + next = self.url('user',args='login',vars=dict(_next=next)) + redirect(cas.login_url(next)) + + + # process authenticated users + if user: + user = Storage(table_user._filter_fields(user, id=True)) + + if log: + self.log_event(log % user) + + # process authenticated users + # user wants to be logged in for longer + session.auth = Storage( + user = user, + last_visit = request.now, + expiration = self.settings.long_expiration, + remember = request.vars.has_key("remember"), + hmac_key = web2py_uuid() + ) + + self.user = user + session.flash = self.messages.logged_in + + # how to continue + if self.settings.login_form == self: + if accepted_form: + callback(onaccept,form) + if isinstance(next, (list, tuple)): + # fix issue with 2.6 + next = next[0] + if next and not next[0] == '/' and next[:4] != 'http': + next = self.url(next.replace('[id]', str(form.vars.id))) + redirect(next) + table_user[username].requires = old_requires + return form + elif user: + callback(onaccept,None) + redirect(next) + + def logout(self, next=DEFAULT, onlogout=DEFAULT, log=DEFAULT): + """ + logout and redirects to login + + .. method:: Auth.logout ([next=DEFAULT[, onlogout=DEFAULT[, + log=DEFAULT]]]) + + """ + + if next == DEFAULT: + next = self.settings.logout_next + if onlogout == DEFAULT: + onlogout = self.settings.logout_onlogout + if onlogout: + onlogout(self.user) + if log == DEFAULT: + log = self.messages.logout_log + if log and self.user: + self.log_event(log % self.user) + + if self.settings.login_form != self: + cas = self.settings.login_form + cas_user = cas.get_user() + if cas_user: + next = cas.logout_url(next) + + current.session.auth = None + current.session.flash = self.messages.logged_out + if next: + redirect(next) + + def register( + self, + next=DEFAULT, + onvalidation=DEFAULT, + onaccept=DEFAULT, + log=DEFAULT, + ): + """ + returns a registration form + + .. method:: Auth.register([next=DEFAULT [, onvalidation=DEFAULT + [, onaccept=DEFAULT [, log=DEFAULT]]]]) + + """ + + table_user = self.settings.table_user + request = current.request + response = current.response + session = current.session + if self.is_logged_in(): + redirect(self.settings.logged_url) + if next == DEFAULT: + next = request.get_vars._next \ + or request.post_vars._next \ + or self.settings.register_next + if onvalidation == DEFAULT: + onvalidation = self.settings.register_onvalidation + if onaccept == DEFAULT: + onaccept = self.settings.register_onaccept + if log == DEFAULT: + log = self.messages.register_log + + passfield = self.settings.password_field + formstyle = self.settings.formstyle + form = SQLFORM(table_user, + fields = self.settings.register_fields, + hidden=dict(_next=next), + showid=self.settings.showid, + submit_button=self.messages.register_button, + delete_label=self.messages.delete_label, + formstyle=formstyle, + separator=self.settings.label_separator + ) + for i, row in enumerate(form[0].components): + item = row.element('input',_name=passfield) + if item: + form.custom.widget.password_two = \ + INPUT(_name="password_two", _type="password", + requires=IS_EXPR('value==%s' % \ + repr(request.vars.get(passfield, None)), + error_message=self.messages.mismatched_password)) + + addrow(form, self.messages.verify_password + ':', + form.custom.widget.password_two, + self.messages.verify_password_comment, + formstyle, + '%s_%s__row' % (table_user, 'password_two'), + position=i+1) + break + captcha = self.settings.register_captcha or self.settings.captcha + if captcha: + addrow(form, captcha.label, captcha, captcha.comment,self.settings.formstyle, 'captcha__row') + + table_user.registration_key.default = key = web2py_uuid() + if form.accepts(request, session, formname='register', + onvalidation=onvalidation,hideerror=self.settings.hideerror): + description = self.messages.group_description % form.vars + if self.settings.create_user_groups: + group_id = self.add_group("user_%s" % form.vars.id, description) + self.add_membership(group_id, form.vars.id) + if self.settings.registration_requires_verification: + if not self.settings.mailer or \ + not self.settings.mailer.send(to=form.vars.email, + subject=self.messages.verify_email_subject, + message=self.messages.verify_email + % dict(key=key)): + self.db.rollback() + response.flash = self.messages.unable_send_email + return form + session.flash = self.messages.email_sent + elif self.settings.registration_requires_approval: + table_user[form.vars.id] = dict(registration_key='pending') + session.flash = self.messages.registration_pending + else: + table_user[form.vars.id] = dict(registration_key='') + session.flash = self.messages.registration_successful + table_user = self.settings.table_user + if 'username' in table_user.fields: + username = 'username' + else: + username = 'email' + user = self.db(table_user[username] == form.vars[username]).select().first() + user = Storage(table_user._filter_fields(user, id=True)) + session.auth = Storage(user=user, last_visit=request.now, + expiration=self.settings.expiration, + hmac_key = web2py_uuid()) + self.user = user + session.flash = self.messages.logged_in + if log: + self.log_event(log % form.vars) + callback(onaccept,form) + if not next: + next = self.url(args = request.args) + elif isinstance(next, (list, tuple)): ### fix issue with 2.6 + next = next[0] + elif next and not next[0] == '/' and next[:4] != 'http': + next = self.url(next.replace('[id]', str(form.vars.id))) + redirect(next) + return form + + def is_logged_in(self): + """ + checks if the user is logged in and returns True/False. + if so user is in auth.user as well as in session.auth.user + """ + + if self.user: + return True + return False + + def verify_email( + self, + next=DEFAULT, + onaccept=DEFAULT, + log=DEFAULT, + ): + """ + action user to verify the registration email, XXXXXXXXXXXXXXXX + + .. method:: Auth.verify_email([next=DEFAULT [, onvalidation=DEFAULT + [, onaccept=DEFAULT [, log=DEFAULT]]]]) + + """ + + key = current.request.args[-1] + table_user = self.settings.table_user + user = self.db(table_user.registration_key == key).select().first() + if not user: + redirect(self.settings.login_url) + if self.settings.registration_requires_approval: + user.update_record(registration_key = 'pending') + current.session.flash = self.messages.registration_pending + else: + user.update_record(registration_key = '') + current.session.flash = self.messages.email_verified + if log == DEFAULT: + log = self.messages.verify_email_log + if next == DEFAULT: + next = self.settings.verify_email_next + if onaccept == DEFAULT: + onaccept = self.settings.verify_email_onaccept + if log: + self.log_event(log % user) + callback(onaccept,user) + redirect(next) + + def retrieve_username( + self, + next=DEFAULT, + onvalidation=DEFAULT, + onaccept=DEFAULT, + log=DEFAULT, + ): + """ + returns a form to retrieve the user username + (only if there is a username field) + + .. method:: Auth.retrieve_username([next=DEFAULT + [, onvalidation=DEFAULT [, onaccept=DEFAULT [, log=DEFAULT]]]]) + + """ + + table_user = self.settings.table_user + if not 'username' in table_user.fields: + raise HTTP(404) + request = current.request + response = current.response + session = current.session + captcha = self.settings.retrieve_username_captcha or \ + (self.settings.retrieve_username_captcha!=False and self.settings.captcha) + if not self.settings.mailer: + response.flash = self.messages.function_disabled + return '' + if next == DEFAULT: + next = request.get_vars._next \ + or request.post_vars._next \ + or self.settings.retrieve_username_next + if onvalidation == DEFAULT: + onvalidation = self.settings.retrieve_username_onvalidation + if onaccept == DEFAULT: + onaccept = self.settings.retrieve_username_onaccept + if log == DEFAULT: + log = self.messages.retrieve_username_log + old_requires = table_user.email.requires + table_user.email.requires = [IS_IN_DB(self.db, table_user.email, + error_message=self.messages.invalid_email)] + form = SQLFORM(table_user, + fields=['email'], + hidden=dict(_next=next), + showid=self.settings.showid, + submit_button=self.messages.submit_button, + delete_label=self.messages.delete_label, + formstyle=self.settings.formstyle, + separator=self.settings.label_separator + ) + if captcha: + addrow(form, captcha.label, captcha, captcha.comment,self.settings.formstyle, 'captcha__row') + + if form.accepts(request, session, + formname='retrieve_username', dbio=False, + onvalidation=onvalidation,hideerror=self.settings.hideerror): + user = self.db(table_user.email == form.vars.email).select().first() + if not user: + current.session.flash = \ + self.messages.invalid_email + redirect(self.url(args=request.args)) + username = user.username + self.settings.mailer.send(to=form.vars.email, + subject=self.messages.retrieve_username_subject, + message=self.messages.retrieve_username + % dict(username=username)) + session.flash = self.messages.email_sent + if log: + self.log_event(log % user) + callback(onaccept,form) + if not next: + next = self.url(args = request.args) + elif isinstance(next, (list, tuple)): ### fix issue with 2.6 + next = next[0] + elif next and not next[0] == '/' and next[:4] != 'http': + next = self.url(next.replace('[id]', str(form.vars.id))) + redirect(next) + table_user.email.requires = old_requires + return form + + def random_password(self): + import string + import random + password = '' + specials=r'!#$*' + for i in range(0,3): + password += random.choice(string.lowercase) + password += random.choice(string.uppercase) + password += random.choice(string.digits) + password += random.choice(specials) + return ''.join(random.sample(password,len(password))) + + def reset_password_deprecated( + self, + next=DEFAULT, + onvalidation=DEFAULT, + onaccept=DEFAULT, + log=DEFAULT, + ): + """ + returns a form to reset the user password (deprecated) + + .. method:: Auth.reset_password_deprecated([next=DEFAULT + [, onvalidation=DEFAULT [, onaccept=DEFAULT [, log=DEFAULT]]]]) + + """ + + table_user = self.settings.table_user + request = current.request + response = current.response + session = current.session + if not self.settings.mailer: + response.flash = self.messages.function_disabled + return '' + if next == DEFAULT: + next = request.get_vars._next \ + or request.post_vars._next \ + or self.settings.retrieve_password_next + if onvalidation == DEFAULT: + onvalidation = self.settings.retrieve_password_onvalidation + if onaccept == DEFAULT: + onaccept = self.settings.retrieve_password_onaccept + if log == DEFAULT: + log = self.messages.retrieve_password_log + old_requires = table_user.email.requires + table_user.email.requires = [IS_IN_DB(self.db, table_user.email, + error_message=self.messages.invalid_email)] + form = SQLFORM(table_user, + fields=['email'], + hidden=dict(_next=next), + showid=self.settings.showid, + submit_button=self.messages.submit_button, + delete_label=self.messages.delete_label, + formstyle=self.settings.formstyle, + separator=self.settings.label_separator + ) + if form.accepts(request, session, + formname='retrieve_password', dbio=False, + onvalidation=onvalidation,hideerror=self.settings.hideerror): + user = self.db(table_user.email == form.vars.email).select().first() + if not user: + current.session.flash = \ + self.messages.invalid_email + redirect(self.url(args=request.args)) + elif user.registration_key in ('pending','disabled','blocked'): + current.session.flash = \ + self.messages.registration_pending + redirect(self.url(args=request.args)) + password = self.random_password() + passfield = self.settings.password_field + d = {passfield: table_user[passfield].validate(password)[0], + 'registration_key': ''} + user.update_record(**d) + if self.settings.mailer and \ + self.settings.mailer.send(to=form.vars.email, + subject=self.messages.retrieve_password_subject, + message=self.messages.retrieve_password \ + % dict(password=password)): + session.flash = self.messages.email_sent + else: + session.flash = self.messages.unable_to_send_email + if log: + self.log_event(log % user) + callback(onaccept,form) + if not next: + next = self.url(args = request.args) + elif isinstance(next, (list, tuple)): ### fix issue with 2.6 + next = next[0] + elif next and not next[0] == '/' and next[:4] != 'http': + next = self.url(next.replace('[id]', str(form.vars.id))) + redirect(next) + table_user.email.requires = old_requires + return form + + def reset_password( + self, + next=DEFAULT, + onvalidation=DEFAULT, + onaccept=DEFAULT, + log=DEFAULT, + ): + """ + returns a form to reset the user password + + .. method:: Auth.reset_password([next=DEFAULT + [, onvalidation=DEFAULT [, onaccept=DEFAULT [, log=DEFAULT]]]]) + + """ + + table_user = self.settings.table_user + request = current.request + # response = current.response + session = current.session + + if next == DEFAULT: + next = request.get_vars._next \ + or request.post_vars._next \ + or self.settings.reset_password_next + + try: + key = request.vars.key or request.args[-1] + t0 = int(key.split('-')[0]) + if time.time()-t0 > 60*60*24: raise Exception + user = self.db(table_user.reset_password_key == key).select().first() + if not user: raise Exception + except Exception: + session.flash = self.messages.invalid_reset_password + redirect(next) + passfield = self.settings.password_field + form = SQLFORM.factory( + Field('new_password', 'password', + label=self.messages.new_password, + requires=self.settings.table_user[passfield].requires), + Field('new_password2', 'password', + label=self.messages.verify_password, + requires=[IS_EXPR('value==%s' % repr(request.vars.new_password), + self.messages.mismatched_password)]), + submit_button=self.messages.password_reset_button, + formstyle=self.settings.formstyle, + separator=self.settings.label_separator + ) + if form.accepts(request,session,hideerror=self.settings.hideerror): + user.update_record(**{passfield:form.vars.new_password, + 'registration_key':'', + 'reset_password_key':''}) + session.flash = self.messages.password_changed + redirect(next) + return form + + def request_reset_password( + self, + next=DEFAULT, + onvalidation=DEFAULT, + onaccept=DEFAULT, + log=DEFAULT, + ): + """ + returns a form to reset the user password + + .. method:: Auth.reset_password([next=DEFAULT + [, onvalidation=DEFAULT [, onaccept=DEFAULT [, log=DEFAULT]]]]) + + """ + + table_user = self.settings.table_user + request = current.request + response = current.response + session = current.session + captcha = self.settings.retrieve_password_captcha or \ + (self.settings.retrieve_password_captcha!=False and self.settings.captcha) + + if next == DEFAULT: + next = request.get_vars._next \ + or request.post_vars._next \ + or self.settings.request_reset_password_next + + if not self.settings.mailer: + response.flash = self.messages.function_disabled + return '' + if onvalidation == DEFAULT: + onvalidation = self.settings.reset_password_onvalidation + if onaccept == DEFAULT: + onaccept = self.settings.reset_password_onaccept + if log == DEFAULT: + log = self.messages.reset_password_log + # old_requires = table_user.email.requires <<< perhaps should be restored + table_user.email.requires = [ + IS_EMAIL(error_message=self.messages.invalid_email), + IS_IN_DB(self.db, table_user.email, + error_message=self.messages.invalid_email)] + form = SQLFORM(table_user, + fields=['email'], + hidden=dict(_next=next), + showid=self.settings.showid, + submit_button=self.messages.password_reset_button, + delete_label=self.messages.delete_label, + formstyle=self.settings.formstyle, + separator=self.settings.label_separator + ) + if captcha: + addrow(form, captcha.label, captcha, captcha.comment, self.settings.formstyle,'captcha__row') + if form.accepts(request, session, + formname='reset_password', dbio=False, + onvalidation=onvalidation, + hideerror=self.settings.hideerror): + user = self.db(table_user.email == form.vars.email).select().first() + if not user: + session.flash = self.messages.invalid_email + redirect(self.url(args=request.args)) + elif user.registration_key in ('pending','disabled','blocked'): + session.flash = self.messages.registration_pending + redirect(self.url(args=request.args)) + reset_password_key = str(int(time.time()))+'-' + web2py_uuid() + + if self.settings.mailer.send(to=form.vars.email, + subject=self.messages.reset_password_subject, + message=self.messages.reset_password % \ + dict(key=reset_password_key)): + session.flash = self.messages.email_sent + user.update_record(reset_password_key=reset_password_key) + else: + session.flash = self.messages.unable_to_send_email + if log: + self.log_event(log % user) + callback(onaccept,form) + if not next: + next = self.url(args = request.args) + elif isinstance(next, (list, tuple)): ### fix issue with 2.6 + next = next[0] + elif next and not next[0] == '/' and next[:4] != 'http': + next = self.url(next.replace('[id]', str(form.vars.id))) + redirect(next) + # old_requires = table_user.email.requires + return form + + def retrieve_password( + self, + next=DEFAULT, + onvalidation=DEFAULT, + onaccept=DEFAULT, + log=DEFAULT, + ): + if self.settings.reset_password_requires_verification: + return self.request_reset_password(next,onvalidation,onaccept,log) + else: + return self.reset_password_deprecated(next,onvalidation,onaccept,log) + + def change_password( + self, + next=DEFAULT, + onvalidation=DEFAULT, + onaccept=DEFAULT, + log=DEFAULT, + ): + """ + returns a form that lets the user change password + + .. method:: Auth.change_password([next=DEFAULT[, onvalidation=DEFAULT[, + onaccept=DEFAULT[, log=DEFAULT]]]]) + """ + + if not self.is_logged_in(): + redirect(self.settings.login_url) + db = self.db + table_user = self.settings.table_user + usern = self.settings.table_user_name + s = db(table_user.id == self.user.id) + + request = current.request + session = current.session + if next == DEFAULT: + next = request.get_vars._next \ + or request.post_vars._next \ + or self.settings.change_password_next + if onvalidation == DEFAULT: + onvalidation = self.settings.change_password_onvalidation + if onaccept == DEFAULT: + onaccept = self.settings.change_password_onaccept + if log == DEFAULT: + log = self.messages.change_password_log + passfield = self.settings.password_field + form = SQLFORM.factory( + Field('old_password', 'password', + label=self.messages.old_password, + requires=validators( + table_user[passfield].requires, + IS_IN_DB(s, '%s.%s' % (usern, passfield), + error_message=self.messages.invalid_password))), + Field('new_password', 'password', + label=self.messages.new_password, + requires=table_user[passfield].requires), + Field('new_password2', 'password', + label=self.messages.verify_password, + requires=[IS_EXPR('value==%s' % repr(request.vars.new_password), + self.messages.mismatched_password)]), + submit_button=self.messages.password_change_button, + formstyle = self.settings.formstyle, + separator=self.settings.label_separator + ) + if form.accepts(request, session, + formname='change_password', + onvalidation=onvalidation, + hideerror=self.settings.hideerror): + d = {passfield: form.vars.new_password} + s.update(**d) + session.flash = self.messages.password_changed + if log: + self.log_event(log % self.user) + callback(onaccept,form) + if not next: + next = self.url(args=request.args) + elif isinstance(next, (list, tuple)): ### fix issue with 2.6 + next = next[0] + elif next and not next[0] == '/' and next[:4] != 'http': + next = self.url(next.replace('[id]', str(form.vars.id))) + redirect(next) + return form + + def profile( + self, + next=DEFAULT, + onvalidation=DEFAULT, + onaccept=DEFAULT, + log=DEFAULT, + ): + """ + returns a form that lets the user change his/her profile + + .. method:: Auth.profile([next=DEFAULT [, onvalidation=DEFAULT + [, onaccept=DEFAULT [, log=DEFAULT]]]]) + + """ + + table_user = self.settings.table_user + if not self.is_logged_in(): + redirect(self.settings.login_url) + passfield = self.settings.password_field + self.settings.table_user[passfield].writable = False + request = current.request + session = current.session + if next == DEFAULT: + next = request.get_vars._next \ + or request.post_vars._next \ + or self.settings.profile_next + if onvalidation == DEFAULT: + onvalidation = self.settings.profile_onvalidation + if onaccept == DEFAULT: + onaccept = self.settings.profile_onaccept + if log == DEFAULT: + log = self.messages.profile_log + form = SQLFORM( + table_user, + self.user.id, + fields = self.settings.profile_fields, + hidden = dict(_next=next), + showid = self.settings.showid, + submit_button = self.messages.profile_save_button, + delete_label = self.messages.delete_label, + upload = self.settings.download_url, + formstyle = self.settings.formstyle, + separator=self.settings.label_separator + ) + if form.accepts(request, session, + formname='profile', + onvalidation=onvalidation,hideerror=self.settings.hideerror): + self.user.update(table_user._filter_fields(form.vars)) + session.flash = self.messages.profile_updated + if log: + self.log_event(log % self.user) + callback(onaccept,form) + if not next: + next = self.url(args=request.args) + elif isinstance(next, (list, tuple)): ### fix issue with 2.6 + next = next[0] + elif next and not next[0] == '/' and next[:4] != 'http': + next = self.url(next.replace('[id]', str(form.vars.id))) + redirect(next) + return form + + def is_impersonating(self): + return current.session.auth.impersonator + + def impersonate(self, user_id=DEFAULT): + """ + usage: POST TO http://..../impersonate request.post_vars.user_id= + set request.post_vars.user_id to 0 to restore original user. + + requires impersonator is logged in and + has_permission('impersonate', 'auth_user', user_id) + """ + request = current.request + session = current.session + auth = session.auth + if not self.is_logged_in(): + raise HTTP(401, "Not Authorized") + current_id = auth.user.id + requested_id = user_id + if user_id == DEFAULT: + user_id = current.request.post_vars.user_id + if user_id and user_id != self.user.id and user_id != '0': + if not self.has_permission('impersonate', + self.settings.table_user_name, + user_id): + raise HTTP(403, "Forbidden") + user = self.settings.table_user(user_id) + if not user: + raise HTTP(401, "Not Authorized") + auth.impersonator = cPickle.dumps(session) + auth.user.update( + self.settings.table_user._filter_fields(user, True)) + self.user = auth.user + if self.settings.login_onaccept: + form = Storage(dict(vars=self.user)) + self.settings.login_onaccept(form) + log = self.messages.impersonate_log + if log: + self.log_event(log % dict(id=current_id,other_id=auth.user.id)) + elif user_id in (0, '0') and self.is_impersonating(): + session.clear() + session.update(cPickle.loads(auth.impersonator)) + self.user = session.auth.user + if requested_id == DEFAULT and not request.post_vars: + return SQLFORM.factory(Field('user_id','integer')) + return self.user + + def groups(self): + """ + displays the groups and their roles for the logged in user + """ + + if not self.is_logged_in(): + redirect(self.settings.login_url) + memberships = self.db(self.settings.table_membership.user_id + == self.user.id).select() + table = TABLE() + for membership in memberships: + groups = self.db(self.settings.table_group.id + == membership.group_id).select() + if groups: + group = groups[0] + table.append(TR(H3(group.role, '(%s)' % group.id))) + table.append(TR(P(group.description))) + if not memberships: + return None + return table + + def not_authorized(self): + """ + you can change the view for this page to make it look as you like + """ + + return 'ACCESS DENIED' + + def requires(self, condition): + """ + decorator that prevents access to action if not logged in + """ + + def decorator(action): + + def f(*a, **b): + + if self.settings.allow_basic_login_only and not self.basic(): + if current.request.is_restful: + raise HTTP(403,"Not authorized") + return call_or_redirect(self.settings.on_failed_authorization) + + if not condition: + if current.request.is_restful: + raise HTTP(403,"Not authorized") + if not self.basic() and not self.is_logged_in(): + request = current.request + next = URL(r=request,args=request.args, + vars=request.get_vars) + current.session.flash = current.response.flash + return call_or_redirect( + self.settings.on_failed_authentication, + self.settings.login_url + '?_next='+urllib.quote(next)) + else: + current.session.flash = self.messages.access_denied + return call_or_redirect(self.settings.on_failed_authorization) + return action(*a, **b) + f.__doc__ = action.__doc__ + f.__name__ = action.__name__ + f.__dict__.update(action.__dict__) + return f + + return decorator + + def requires_login(self): + """ + decorator that prevents access to action if not logged in + """ + + def decorator(action): + + def f(*a, **b): + + if self.settings.allow_basic_login_only and not self.basic(): + if current.request.is_restful: + raise HTTP(403,"Not authorized") + return call_or_redirect(self.settings.on_failed_authorization) + + if not self.basic() and not self.is_logged_in(): + if current.request.is_restful: + raise HTTP(403,"Not authorized") + request = current.request + next = URL(r=request,args=request.args, + vars=request.get_vars) + current.session.flash = current.response.flash + return call_or_redirect( + self.settings.on_failed_authentication, + self.settings.login_url + '?_next='+urllib.quote(next) + ) + return action(*a, **b) + f.__doc__ = action.__doc__ + f.__name__ = action.__name__ + f.__dict__.update(action.__dict__) + return f + + return decorator + + def requires_membership(self, role=None, group_id=None): + """ + decorator that prevents access to action if not logged in or + if user logged in is not a member of group_id. + If role is provided instead of group_id then the + group_id is calculated. + """ + + def decorator(action): + def f(*a, **b): + if self.settings.allow_basic_login_only and not self.basic(): + if current.request.is_restful: + raise HTTP(403,"Not authorized") + return call_or_redirect(self.settings.on_failed_authorization) + + if not self.basic() and not self.is_logged_in(): + if current.request.is_restful: + raise HTTP(403,"Not authorized") + request = current.request + next = URL(r=request,args=request.args, + vars=request.get_vars) + current.session.flash = current.response.flash + return call_or_redirect( + self.settings.on_failed_authentication, + self.settings.login_url + '?_next='+urllib.quote(next) + ) + if not self.has_membership(group_id=group_id, role=role): + current.session.flash = self.messages.access_denied + return call_or_redirect(self.settings.on_failed_authorization) + return action(*a, **b) + f.__doc__ = action.__doc__ + f.__name__ = action.__name__ + f.__dict__.update(action.__dict__) + return f + + return decorator + + + def requires_permission( + self, + name, + table_name='', + record_id=0, + ): + """ + decorator that prevents access to action if not logged in or + if user logged in is not a member of any group (role) that + has 'name' access to 'table_name', 'record_id'. + """ + + def decorator(action): + + def f(*a, **b): + if self.settings.allow_basic_login_only and not self.basic(): + if current.request.is_restful: + raise HTTP(403,"Not authorized") + return call_or_redirect(self.settings.on_failed_authorization) + + if not self.basic() and not self.is_logged_in(): + if current.request.is_restful: + raise HTTP(403,"Not authorized") + request = current.request + next = URL(r=request,args=request.args, + vars=request.get_vars) + current.session.flash = current.response.flash + return call_or_redirect( + self.settings.on_failed_authentication, + self.settings.login_url + '?_next='+urllib.quote(next) + ) + if not self.has_permission(name, table_name, record_id): + current.session.flash = self.messages.access_denied + return call_or_redirect(self.settings.on_failed_authorization) + return action(*a, **b) + f.__doc__ = action.__doc__ + f.__name__ = action.__name__ + f.__dict__.update(action.__dict__) + return f + + return decorator + + def requires_signature(self): + """ + decorator that prevents access to action if not logged in or + if user logged in is not a member of group_id. + If role is provided instead of group_id then the + group_id is calculated. + """ + + def decorator(action): + def f(*a, **b): + if self.settings.allow_basic_login_only and not self.basic(): + if current.request.is_restful: + raise HTTP(403,"Not authorized") + return call_or_redirect(self.settings.on_failed_authorization) + + if not self.basic() and not self.is_logged_in(): + if current.request.is_restful: + raise HTTP(403,"Not authorized") + request = current.request + next = URL(r=request,args=request.args, + vars=request.get_vars) + current.session.flash = current.response.flash + return call_or_redirect( + self.settings.on_failed_authentication, + self.settings.login_url + '?_next='+urllib.quote(next) + ) + if not URL.verify(current.request,user_signature=True): + current.session.flash = self.messages.access_denied + return call_or_redirect(self.settings.on_failed_authorization) + return action(*a, **b) + f.__doc__ = action.__doc__ + f.__name__ = action.__name__ + f.__dict__.update(action.__dict__) + return f + + return decorator + + def add_group(self, role, description=''): + """ + creates a group associated to a role + """ + + group_id = self.settings.table_group.insert(role=role, + description=description) + log = self.messages.add_group_log + if log: + self.log_event(log % dict(group_id=group_id, role=role)) + return group_id + + def del_group(self, group_id): + """ + deletes a group + """ + + self.db(self.settings.table_group.id == group_id).delete() + self.db(self.settings.table_membership.group_id + == group_id).delete() + self.db(self.settings.table_permission.group_id + == group_id).delete() + log = self.messages.del_group_log + if log: + self.log_event(log % dict(group_id=group_id)) + + def id_group(self, role): + """ + returns the group_id of the group specified by the role + """ + rows = self.db(self.settings.table_group.role == role).select() + if not rows: + return None + return rows[0].id + + def user_group(self, user_id = None): + """ + returns the group_id of the group uniquely associated to this user + i.e. role=user:[user_id] + """ + if not user_id and self.user: + user_id = self.user.id + role = 'user_%s' % user_id + return self.id_group(role) + + def has_membership(self, group_id=None, user_id=None, role=None): + """ + checks if user is member of group_id or role + """ + + group_id = group_id or self.id_group(role) + try: + group_id = int(group_id) + except: + group_id = self.id_group(group_id) # interpret group_id as a role + if not user_id and self.user: + user_id = self.user.id + membership = self.settings.table_membership + if self.db((membership.user_id == user_id) + & (membership.group_id == group_id)).select(): + r = True + else: + r = False + log = self.messages.has_membership_log + if log: + self.log_event(log % dict(user_id=user_id, + group_id=group_id, check=r)) + return r + + def add_membership(self, group_id=None, user_id=None, role=None): + """ + gives user_id membership of group_id or role + if user_id==None than user_id is that of current logged in user + """ + + group_id = group_id or self.id_group(role) + try: + group_id = int(group_id) + except: + group_id = self.id_group(group_id) # interpret group_id as a role + if not user_id and self.user: + user_id = self.user.id + membership = self.settings.table_membership + record = membership(user_id = user_id,group_id = group_id) + if record: + return record.id + else: + id = membership.insert(group_id=group_id, user_id=user_id) + log = self.messages.add_membership_log + if log: + self.log_event(log % dict(user_id=user_id, + group_id=group_id)) + return id + + def del_membership(self, group_id, user_id=None, role=None): + """ + revokes membership from group_id to user_id + if user_id==None than user_id is that of current logged in user + """ + + group_id = group_id or self.id_group(role) + if not user_id and self.user: + user_id = self.user.id + membership = self.settings.table_membership + log = self.messages.del_membership_log + if log: + self.log_event(log % dict(user_id=user_id, + group_id=group_id)) + return self.db(membership.user_id + == user_id)(membership.group_id + == group_id).delete() + + def has_permission( + self, + name='any', + table_name='', + record_id=0, + user_id=None, + group_id=None, + ): + """ + checks if user_id or current logged in user is member of a group + that has 'name' permission on 'table_name' and 'record_id' + if group_id is passed, it checks whether the group has the permission + """ + + if not user_id and not group_id and self.user: + user_id = self.user.id + if user_id: + membership = self.settings.table_membership + rows = self.db(membership.user_id + == user_id).select(membership.group_id) + groups = set([row.group_id for row in rows]) + if group_id and not group_id in groups: + return False + else: + groups = set([group_id]) + permission = self.settings.table_permission + rows = self.db(permission.name == name)(permission.table_name + == str(table_name))(permission.record_id + == record_id).select(permission.group_id) + groups_required = set([row.group_id for row in rows]) + if record_id: + rows = self.db(permission.name + == name)(permission.table_name + == str(table_name))(permission.record_id + == 0).select(permission.group_id) + groups_required = groups_required.union(set([row.group_id + for row in rows])) + if groups.intersection(groups_required): + r = True + else: + r = False + log = self.messages.has_permission_log + if log and user_id: + self.log_event(log % dict(user_id=user_id, name=name, + table_name=table_name, record_id=record_id)) + return r + + def add_permission( + self, + group_id, + name='any', + table_name='', + record_id=0, + ): + """ + gives group_id 'name' access to 'table_name' and 'record_id' + """ + + permission = self.settings.table_permission + if group_id == 0: + group_id = self.user_group() + id = permission.insert(group_id=group_id, name=name, + table_name=str(table_name), + record_id=long(record_id)) + log = self.messages.add_permission_log + if log: + self.log_event(log % dict(permission_id=id, group_id=group_id, + name=name, table_name=table_name, + record_id=record_id)) + return id + + def del_permission( + self, + group_id, + name='any', + table_name='', + record_id=0, + ): + """ + revokes group_id 'name' access to 'table_name' and 'record_id' + """ + + permission = self.settings.table_permission + log = self.messages.del_permission_log + if log: + self.log_event(log % dict(group_id=group_id, name=name, + table_name=table_name, record_id=record_id)) + return self.db(permission.group_id == group_id)(permission.name + == name)(permission.table_name + == str(table_name))(permission.record_id + == long(record_id)).delete() + + def accessible_query(self, name, table, user_id=None): + """ + returns a query with all accessible records for user_id or + the current logged in user + this method does not work on GAE because uses JOIN and IN + + example:: + + db(auth.accessible_query('read', db.mytable)).select(db.mytable.ALL) + + """ + if not user_id: + user_id = self.user.id + if self.has_permission(name, table, 0, user_id): + return table.id > 0 + db = self.db + membership = self.settings.table_membership + permission = self.settings.table_permission + return table.id.belongs(db(membership.user_id == user_id)\ + (membership.group_id == permission.group_id)\ + (permission.name == name)\ + (permission.table_name == table)\ + ._select(permission.record_id)) + + +class Crud(object): + + def url(self, f=None, args=[], vars={}): + """ + this should point to the controller that exposes + download and crud + """ + return URL(c=self.settings.controller,f=f,args=args,vars=vars) + + def __init__(self, environment, db=None, controller='default'): + self.db = db + if not db and environment and isinstance(environment,DAL): + self.db = environment + elif not db: + raise SyntaxError, "must pass db as first or second argument" + self.environment = current + settings = self.settings = Settings() + settings.auth = None + settings.logger = None + + settings.create_next = None + settings.update_next = None + settings.controller = controller + settings.delete_next = self.url() + settings.download_url = self.url('download') + settings.create_onvalidation = StorageList() + settings.update_onvalidation = StorageList() + settings.delete_onvalidation = StorageList() + settings.create_onaccept = StorageList() + settings.update_onaccept = StorageList() + settings.update_ondelete = StorageList() + settings.delete_onaccept = StorageList() + settings.update_deletable = True + settings.showid = False + settings.keepvalues = False + settings.create_captcha = None + settings.update_captcha = None + settings.captcha = None + settings.formstyle = 'table3cols' + settings.label_separator = ': ' + settings.hideerror = False + settings.detect_record_change = True + settings.hmac_key = None + settings.lock_keys = True + + messages = self.messages = Messages(current.T) + messages.submit_button = 'Submit' + messages.delete_label = 'Check to delete:' + messages.record_created = 'Record Created' + messages.record_updated = 'Record Updated' + messages.record_deleted = 'Record Deleted' + + messages.update_log = 'Record %(id)s updated' + messages.create_log = 'Record %(id)s created' + messages.read_log = 'Record %(id)s read' + messages.delete_log = 'Record %(id)s deleted' + + messages.lock_keys = True + + def __call__(self): + args = current.request.args + if len(args) < 1: + raise HTTP(404) + elif args[0] == 'tables': + return self.tables() + elif len(args) > 1 and not args(1) in self.db.tables: + raise HTTP(404) + table = self.db[args(1)] + if args[0] == 'create': + return self.create(table) + elif args[0] == 'select': + return self.select(table,linkto=self.url(args='read')) + elif args[0] == 'search': + form, rows = self.search(table,linkto=self.url(args='read')) + return DIV(form,SQLTABLE(rows)) + elif args[0] == 'read': + return self.read(table, args(2)) + elif args[0] == 'update': + return self.update(table, args(2)) + elif args[0] == 'delete': + return self.delete(table, args(2)) + else: + raise HTTP(404) + + def log_event(self, message): + if self.settings.logger: + self.settings.logger.log_event(message, 'crud') + + def has_permission(self, name, table, record=0): + if not self.settings.auth: + return True + try: + record_id = record.id + except: + record_id = record + return self.settings.auth.has_permission(name, str(table), record_id) + + def tables(self): + return TABLE(*[TR(A(name, + _href=self.url(args=('select',name)))) \ + for name in self.db.tables]) + + + @staticmethod + def archive(form,archive_table=None,current_record='current_record'): + """ + If you have a table (db.mytable) that needs full revision history you can just do:: + + form=crud.update(db.mytable,myrecord,onaccept=crud.archive) + + crud.archive will define a new table "mytable_archive" and store the + previous record in the newly created table including a reference + to the current record. + + If you want to access such table you need to define it yourself in a model:: + + db.define_table('mytable_archive', + Field('current_record',db.mytable), + db.mytable) + + Notice such table includes all fields of db.mytable plus one: current_record. + crud.archive does not timestamp the stored record unless your original table + has a fields like:: + + db.define_table(..., + Field('saved_on','datetime', + default=request.now,update=request.now,writable=False), + Field('saved_by',auth.user, + default=auth.user_id,update=auth.user_id,writable=False), + + there is nothing special about these fields since they are filled before + the record is archived. + + If you want to change the archive table name and the name of the reference field + you can do, for example:: + + db.define_table('myhistory', + Field('parent_record',db.mytable), + db.mytable) + + and use it as:: + + form=crud.update(db.mytable,myrecord, + onaccept=lambda form:crud.archive(form, + archive_table=db.myhistory, + current_record='parent_record')) + + """ + old_record = form.record + if not old_record: + return None + table = form.table + if not archive_table: + archive_table_name = '%s_archive' % table + if archive_table_name in table._db: + archive_table = table._db[archive_table_name] + else: + archive_table = table._db.define_table(archive_table_name, + Field(current_record,table), + table) + new_record = {current_record:old_record.id} + for fieldname in archive_table.fields: + if not fieldname in ['id',current_record] and fieldname in old_record: + new_record[fieldname]=old_record[fieldname] + id = archive_table.insert(**new_record) + return id + + def update( + self, + table, + record, + next=DEFAULT, + onvalidation=DEFAULT, + onaccept=DEFAULT, + ondelete=DEFAULT, + log=DEFAULT, + message=DEFAULT, + deletable=DEFAULT, + formname=DEFAULT, + ): + """ + .. method:: Crud.update(table, record, [next=DEFAULT + [, onvalidation=DEFAULT [, onaccept=DEFAULT [, log=DEFAULT + [, message=DEFAULT[, deletable=DEFAULT]]]]]]) + + """ + if not (isinstance(table, self.db.Table) or table in self.db.tables) \ + or (isinstance(record, str) and not str(record).isdigit()): + raise HTTP(404) + if not isinstance(table, self.db.Table): + table = self.db[table] + try: + record_id = record.id + except: + record_id = record or 0 + if record_id and not self.has_permission('update', table, record_id): + redirect(self.settings.auth.settings.on_failed_authorization) + if not record_id \ + and not self.has_permission('create', table, record_id): + redirect(self.settings.auth.settings.on_failed_authorization) + + request = current.request + response = current.response + session = current.session + if request.extension == 'json' and request.vars.json: + request.vars.update(simplejson.loads(request.vars.json)) + if next == DEFAULT: + next = request.get_vars._next \ + or request.post_vars._next \ + or self.settings.update_next + if onvalidation == DEFAULT: + onvalidation = self.settings.update_onvalidation + if onaccept == DEFAULT: + onaccept = self.settings.update_onaccept + if ondelete == DEFAULT: + ondelete = self.settings.update_ondelete + if log == DEFAULT: + log = self.messages.update_log + if deletable == DEFAULT: + deletable = self.settings.update_deletable + if message == DEFAULT: + message = self.messages.record_updated + form = SQLFORM( + table, + record, + hidden=dict(_next=next), + showid=self.settings.showid, + submit_button=self.messages.submit_button, + delete_label=self.messages.delete_label, + deletable=deletable, + upload=self.settings.download_url, + formstyle=self.settings.formstyle, + separator=self.settings.label_separator + ) + self.accepted = False + self.deleted = False + captcha = self.settings.update_captcha or \ + self.settings.captcha + if record and captcha: + addrow(form, captcha.label, captcha, captcha.comment, + self.settings.formstyle,'captcha__row') + captcha = self.settings.create_captcha or \ + self.settings.captcha + if not record and captcha: + addrow(form, captcha.label, captcha, captcha.comment, + self.settings.formstyle,'captcha__row') + if not request.extension in ('html','load'): + (_session, _formname) = (None, None) + else: + (_session, _formname) = \ + (session, '%s/%s' % (table._tablename, form.record_id)) + if formname!=DEFAULT: + _formname = formname + keepvalues = self.settings.keepvalues + if request.vars.delete_this_record: + keepvalues = False + if isinstance(onvalidation,StorageList): + onvalidation=onvalidation.get(table._tablename, []) + if form.accepts(request, _session, formname=_formname, + onvalidation=onvalidation, keepvalues=keepvalues, + hideerror=self.settings.hideerror, + detect_record_change = self.settings.detect_record_change): + self.accepted = True + response.flash = message + if log: + self.log_event(log % form.vars) + if request.vars.delete_this_record: + self.deleted = True + message = self.messages.record_deleted + callback(ondelete,form,table._tablename) + response.flash = message + callback(onaccept,form,table._tablename) + if not request.extension in ('html','load'): + raise HTTP(200, 'RECORD CREATED/UPDATED') + if isinstance(next, (list, tuple)): ### fix issue with 2.6 + next = next[0] + if next: # Only redirect when explicit + if next[0] != '/' and next[:4] != 'http': + next = URL(r=request, + f=next.replace('[id]', str(form.vars.id))) + session.flash = response.flash + redirect(next) + elif not request.extension in ('html','load'): + raise HTTP(401) + return form + + def create( + self, + table, + next=DEFAULT, + onvalidation=DEFAULT, + onaccept=DEFAULT, + log=DEFAULT, + message=DEFAULT, + formname=DEFAULT, + ): + """ + .. method:: Crud.create(table, [next=DEFAULT [, onvalidation=DEFAULT + [, onaccept=DEFAULT [, log=DEFAULT[, message=DEFAULT]]]]]) + """ + + if next == DEFAULT: + next = self.settings.create_next + if onvalidation == DEFAULT: + onvalidation = self.settings.create_onvalidation + if onaccept == DEFAULT: + onaccept = self.settings.create_onaccept + if log == DEFAULT: + log = self.messages.create_log + if message == DEFAULT: + message = self.messages.record_created + return self.update( + table, + None, + next=next, + onvalidation=onvalidation, + onaccept=onaccept, + log=log, + message=message, + deletable=False, + formname=formname, + ) + + def read(self, table, record): + if not (isinstance(table, self.db.Table) or table in self.db.tables) \ + or (isinstance(record, str) and not str(record).isdigit()): + raise HTTP(404) + if not isinstance(table, self.db.Table): + table = self.db[table] + if not self.has_permission('read', table, record): + redirect(self.settings.auth.settings.on_failed_authorization) + form = SQLFORM( + table, + record, + readonly=True, + comments=False, + upload=self.settings.download_url, + showid=self.settings.showid, + formstyle=self.settings.formstyle, + separator=self.settings.label_separator + ) + if not current.request.extension in ('html','load'): + return table._filter_fields(form.record, id=True) + return form + + def delete( + self, + table, + record_id, + next=DEFAULT, + message=DEFAULT, + ): + """ + .. method:: Crud.delete(table, record_id, [next=DEFAULT + [, message=DEFAULT]]) + """ + if not (isinstance(table, self.db.Table) or table in self.db.tables) \ + or not str(record_id).isdigit(): + raise HTTP(404) + if not isinstance(table, self.db.Table): + table = self.db[table] + if not self.has_permission('delete', table, record_id): + redirect(self.settings.auth.settings.on_failed_authorization) + request = current.request + session = current.session + if next == DEFAULT: + next = request.get_vars._next \ + or request.post_vars._next \ + or self.settings.delete_next + if message == DEFAULT: + message = self.messages.record_deleted + record = table[record_id] + if record: + callback(self.settings.delete_onvalidation,record) + del table[record_id] + callback(self.settings.delete_onaccept,record,table._tablename) + session.flash = message + if next: # Only redirect when explicit + redirect(next) + + def rows( + self, + table, + query=None, + fields=None, + orderby=None, + limitby=None, + ): + request = current.request + if not (isinstance(table, self.db.Table) or table in self.db.tables): + raise HTTP(404) + if not self.has_permission('select', table): + redirect(self.settings.auth.settings.on_failed_authorization) + #if record_id and not self.has_permission('select', table): + # redirect(self.settings.auth.settings.on_failed_authorization) + if not isinstance(table, self.db.Table): + table = self.db[table] + if not query: + query = table.id > 0 + if not fields: + fields = [field for field in table if field.readable] + rows = self.db(query).select(*fields,**dict(orderby=orderby, + limitby=limitby)) + return rows + + def select( + self, + table, + query=None, + fields=None, + orderby=None, + limitby=None, + headers={}, + **attr + ): + rows = self.rows(table,query,fields,orderby,limitby) + if not rows: + return None # Nicer than an empty table. + if not 'upload' in attr: + attr['upload'] = self.url('download') + if not current.request.extension in ('html','load'): + return rows.as_list() + if not headers: + if isinstance(table,str): + table = self.db[table] + headers = dict((str(k),k.label) for k in table) + return SQLTABLE(rows,headers=headers,**attr) + + def get_format(self, field): + rtable = field._db[field.type[10:]] + format = rtable.get('_format', None) + if format and isinstance(format, str): + return format[2:-2] + return field.name + + def get_query(self, field, op, value, refsearch=False): + try: + if refsearch: format = self.get_format(field) + if op == 'equals': + if not refsearch: + return field == value + else: + return lambda row: row[field.name][format] == value + elif op == 'not equal': + if not refsearch: + return field != value + else: + return lambda row: row[field.name][format] != value + elif op == 'greater than': + if not refsearch: + return field > value + else: + return lambda row: row[field.name][format] > value + elif op == 'less than': + if not refsearch: + return field < value + else: + return lambda row: row[field.name][format] < value + elif op == 'starts with': + if not refsearch: + return field.like(value+'%') + else: + return lambda row: str(row[field.name][format]).startswith(value) + elif op == 'ends with': + if not refsearch: + return field.like('%'+value) + else: + return lambda row: str(row[field.name][format]).endswith(value) + elif op == 'contains': + if not refsearch: + return field.like('%'+value+'%') + else: + return lambda row: value in row[field.name][format] + except: + return None + + + def search(self, *tables, **args): + """ + Creates a search form and its results for a table + Example usage: + form, results = crud.search(db.test, + queries = ['equals', 'not equal', 'contains'], + query_labels={'equals':'Equals', + 'not equal':'Not equal'}, + fields = ['id','children'], + field_labels = {'id':'ID','children':'Children'}, + zero='Please choose', + query = (db.test.id > 0)&(db.test.id != 3) ) + """ + table = tables[0] + fields = args.get('fields', table.fields) + request = current.request + db = self.db + if not (isinstance(table, db.Table) or table in db.tables): + raise HTTP(404) + attributes = {} + for key in ('orderby','groupby','left','distinct','limitby','cache'): + if key in args: attributes[key]=args[key] + tbl = TABLE() + selected = []; refsearch = []; results = [] + ops = args.get('queries', []) + zero = args.get('zero', '') + if not ops: + ops = ['equals', 'not equal', 'greater than', + 'less than', 'starts with', + 'ends with', 'contains'] + ops.insert(0,zero) + query_labels = args.get('query_labels', {}) + query = args.get('query',table.id > 0) + field_labels = args.get('field_labels',{}) + for field in fields: + field = table[field] + if not field.readable: continue + fieldname = field.name + chkval = request.vars.get('chk' + fieldname, None) + txtval = request.vars.get('txt' + fieldname, None) + opval = request.vars.get('op' + fieldname, None) + row = TR(TD(INPUT(_type = "checkbox", _name = "chk" + fieldname, + _disabled = (field.type == 'id'), + value = (field.type == 'id' or chkval == 'on'))), + TD(field_labels.get(fieldname,field.label)), + TD(SELECT([OPTION(query_labels.get(op,op), + _value=op) for op in ops], + _name = "op" + fieldname, + value = opval)), + TD(INPUT(_type = "text", _name = "txt" + fieldname, + _value = txtval, _id='txt' + fieldname, + _class = str(field.type)))) + tbl.append(row) + if request.post_vars and (chkval or field.type=='id'): + if txtval and opval != '': + if field.type[0:10] == 'reference ': + refsearch.append(self.get_query(field, + opval, txtval, refsearch=True)) + else: + value, error = field.validate(txtval) + if not error: + ### TODO deal with 'starts with', 'ends with', 'contains' on GAE + query &= self.get_query(field, opval, value) + else: + row[3].append(DIV(error,_class='error')) + selected.append(field) + form = FORM(tbl,INPUT(_type="submit")) + if selected: + try: + results = db(query).select(*selected,**attributes) + for r in refsearch: + results = results.find(r) + except: # hmmm, we should do better here + results = None + return form, results + + +urllib2.install_opener(urllib2.build_opener(urllib2.HTTPCookieProcessor())) + +def fetch(url, data=None, headers={}, + cookie=Cookie.SimpleCookie(), + user_agent='Mozilla/5.0'): + if data != None: + data = urllib.urlencode(data) + if user_agent: headers['User-agent'] = user_agent + headers['Cookie'] = ' '.join(['%s=%s;'%(c.key,c.value) for c in cookie.values()]) + try: + from google.appengine.api import urlfetch + except ImportError: + req = urllib2.Request(url, data, headers) + html = urllib2.urlopen(req).read() + else: + method = ((data==None) and urlfetch.GET) or urlfetch.POST + while url is not None: + response = urlfetch.fetch(url=url, payload=data, + method=method, headers=headers, + allow_truncated=False,follow_redirects=False, + deadline=10) + # next request will be a get, so no need to send the data again + data = None + method = urlfetch.GET + # load cookies from the response + cookie.load(response.headers.get('set-cookie', '')) + url = response.headers.get('location') + html = response.content + return html + +regex_geocode = \ + re.compile('\(?P[^,]*),(?P[^,]*).*?\') + + +def geocode(address): + try: + a = urllib.quote(address) + txt = fetch('http://maps.google.com/maps/geo?q=%s&output=xml' + % a) + item = regex_geocode.search(txt) + (la, lo) = (float(item.group('la')), float(item.group('lo'))) + return (la, lo) + except: + return (0.0, 0.0) + + +def universal_caller(f, *a, **b): + c = f.func_code.co_argcount + n = f.func_code.co_varnames[:c] + + defaults = f.func_defaults or [] + pos_args = n[0:-len(defaults)] + named_args = n[-len(defaults):] + + arg_dict = {} + + # Fill the arg_dict with name and value for the submitted, positional values + for pos_index, pos_val in enumerate(a[:c]): + arg_dict[n[pos_index]] = pos_val # n[pos_index] is the name of the argument + + # There might be pos_args left, that are sent as named_values. Gather them as well. + # If a argument already is populated with values we simply replaces them. + for arg_name in pos_args[len(arg_dict):]: + if b.has_key(arg_name): + arg_dict[arg_name] = b[arg_name] + + if len(arg_dict) >= len(pos_args): + # All the positional arguments is found. The function may now be called. + # However, we need to update the arg_dict with the values from the named arguments as well. + for arg_name in named_args: + if b.has_key(arg_name): + arg_dict[arg_name] = b[arg_name] + + return f(**arg_dict) + + # Raise an error, the function cannot be called. + raise HTTP(404, "Object does not exist") + + +class Service(object): + + def __init__(self, environment=None): + self.run_procedures = {} + self.csv_procedures = {} + self.xml_procedures = {} + self.rss_procedures = {} + self.json_procedures = {} + self.jsonrpc_procedures = {} + self.xmlrpc_procedures = {} + self.amfrpc_procedures = {} + self.amfrpc3_procedures = {} + self.soap_procedures = {} + + def run(self, f): + """ + example:: + + service = Service(globals()) + @service.run + def myfunction(a, b): + return a + b + def call(): + return service() + + Then call it with:: + + wget http://..../app/default/call/run/myfunction?a=3&b=4 + + """ + self.run_procedures[f.__name__] = f + return f + + def csv(self, f): + """ + example:: + + service = Service(globals()) + @service.csv + def myfunction(a, b): + return a + b + def call(): + return service() + + Then call it with:: + + wget http://..../app/default/call/csv/myfunction?a=3&b=4 + + """ + self.run_procedures[f.__name__] = f + return f + + def xml(self, f): + """ + example:: + + service = Service(globals()) + @service.xml + def myfunction(a, b): + return a + b + def call(): + return service() + + Then call it with:: + + wget http://..../app/default/call/xml/myfunction?a=3&b=4 + + """ + self.run_procedures[f.__name__] = f + return f + + def rss(self, f): + """ + example:: + + service = Service(globals()) + @service.rss + def myfunction(): + return dict(title=..., link=..., description=..., + created_on=..., entries=[dict(title=..., link=..., + description=..., created_on=...]) + def call(): + return service() + + Then call it with:: + + wget http://..../app/default/call/rss/myfunction + + """ + self.rss_procedures[f.__name__] = f + return f + + def json(self, f): + """ + example:: + + service = Service(globals()) + @service.json + def myfunction(a, b): + return [{a: b}] + def call(): + return service() + + Then call it with:: + + wget http://..../app/default/call/json/myfunction?a=hello&b=world + + """ + self.json_procedures[f.__name__] = f + return f + + def jsonrpc(self, f): + """ + example:: + + service = Service(globals()) + @service.jsonrpc + def myfunction(a, b): + return a + b + def call(): + return service() + + Then call it with:: + + wget http://..../app/default/call/jsonrpc/myfunction?a=hello&b=world + + """ + self.jsonrpc_procedures[f.__name__] = f + return f + + def xmlrpc(self, f): + """ + example:: + + service = Service(globals()) + @service.xmlrpc + def myfunction(a, b): + return a + b + def call(): + return service() + + The call it with:: + + wget http://..../app/default/call/xmlrpc/myfunction?a=hello&b=world + + """ + self.xmlrpc_procedures[f.__name__] = f + return f + + def amfrpc(self, f): + """ + example:: + + service = Service(globals()) + @service.amfrpc + def myfunction(a, b): + return a + b + def call(): + return service() + + The call it with:: + + wget http://..../app/default/call/amfrpc/myfunction?a=hello&b=world + + """ + self.amfrpc_procedures[f.__name__] = f + return f + + def amfrpc3(self, domain='default'): + """ + example:: + + service = Service(globals()) + @service.amfrpc3('domain') + def myfunction(a, b): + return a + b + def call(): + return service() + + The call it with:: + + wget http://..../app/default/call/amfrpc3/myfunction?a=hello&b=world + + """ + if not isinstance(domain, str): + raise SyntaxError, "AMF3 requires a domain for function" + + def _amfrpc3(f): + if domain: + self.amfrpc3_procedures[domain+'.'+f.__name__] = f + else: + self.amfrpc3_procedures[f.__name__] = f + return f + return _amfrpc3 + + def soap(self, name=None, returns=None, args=None,doc=None): + """ + example:: + + service = Service(globals()) + @service.soap('MyFunction',returns={'result':int},args={'a':int,'b':int,}) + def myfunction(a, b): + return a + b + def call(): + return service() + + The call it with:: + + from gluon.contrib.pysimplesoap.client import SoapClient + client = SoapClient(wsdl="http://..../app/default/call/soap?WSDL") + response = client.MyFunction(a=1,b=2) + return response['result'] + + Exposes online generated documentation and xml example messages at: + - http://..../app/default/call/soap + """ + + def _soap(f): + self.soap_procedures[name or f.__name__] = f, returns, args, doc + return f + return _soap + + def serve_run(self, args=None): + request = current.request + if not args: + args = request.args + if args and args[0] in self.run_procedures: + return str(universal_caller(self.run_procedures[args[0]], + *args[1:], **dict(request.vars))) + self.error() + + def serve_csv(self, args=None): + request = current.request + response = current.response + response.headers['Content-Type'] = 'text/x-csv' + if not args: + args = request.args + + def none_exception(value): + if isinstance(value, unicode): + return value.encode('utf8') + if hasattr(value, 'isoformat'): + return value.isoformat()[:19].replace('T', ' ') + if value == None: + return '' + return value + if args and args[0] in self.run_procedures: + r = universal_caller(self.run_procedures[args[0]], + *args[1:], **dict(request.vars)) + s = cStringIO.StringIO() + if hasattr(r, 'export_to_csv_file'): + r.export_to_csv_file(s) + elif r and isinstance(r[0], (dict, Storage)): + import csv + writer = csv.writer(s) + writer.writerow(r[0].keys()) + for line in r: + writer.writerow([none_exception(v) \ + for v in line.values()]) + else: + import csv + writer = csv.writer(s) + for line in r: + writer.writerow(line) + return s.getvalue() + self.error() + + def serve_xml(self, args=None): + request = current.request + response = current.response + response.headers['Content-Type'] = 'text/xml' + if not args: + args = request.args + if args and args[0] in self.run_procedures: + s = universal_caller(self.run_procedures[args[0]], + *args[1:], **dict(request.vars)) + if hasattr(s, 'as_list'): + s = s.as_list() + return serializers.xml(s) + self.error() + + def serve_rss(self, args=None): + request = current.request + response = current.response + if not args: + args = request.args + if args and args[0] in self.rss_procedures: + feed = universal_caller(self.rss_procedures[args[0]], + *args[1:], **dict(request.vars)) + else: + self.error() + response.headers['Content-Type'] = 'application/rss+xml' + return serializers.rss(feed) + + def serve_json(self, args=None): + request = current.request + response = current.response + response.headers['Content-Type'] = 'text/x-json' + if not args: + args = request.args + d = dict(request.vars) + if args and args[0] in self.json_procedures: + s = universal_caller(self.json_procedures[args[0]],*args[1:],**d) + if hasattr(s, 'as_list'): + s = s.as_list() + return response.json(s) + self.error() + + class JsonRpcException(Exception): + def __init__(self,code,info): + self.code,self.info = code,info + + def serve_jsonrpc(self): + import contrib.simplejson as simplejson + def return_response(id, result): + return serializers.json({'version': '1.1', + 'id': id, 'result': result, 'error': None}) + + def return_error(id, code, message): + return serializers.json({'id': id, + 'version': '1.1', + 'error': {'name': 'JSONRPCError', + 'code': code, 'message': message} + }) + + request = current.request + methods = self.jsonrpc_procedures + data = simplejson.loads(request.body.read()) + id, method, params = data['id'], data['method'], data.get('params','') + if not method in methods: + return return_error(id, 100, 'method "%s" does not exist' % method) + try: + s = methods[method](*params) + if hasattr(s, 'as_list'): + s = s.as_list() + return return_response(id, s) + except Service.JsonRpcException, e: + return return_error(id, e.code, e.info) + except BaseException: + etype, eval, etb = sys.exc_info() + return return_error(id, 100, '%s: %s' % (etype.__name__, eval)) + except: + etype, eval, etb = sys.exc_info() + return return_error(id, 100, 'Exception %s: %s' % (etype, eval)) + + def serve_xmlrpc(self): + request = current.request + response = current.response + services = self.xmlrpc_procedures.values() + return response.xmlrpc(request, services) + + def serve_amfrpc(self, version=0): + try: + import pyamf + import pyamf.remoting.gateway + except: + return "pyamf not installed or not in Python sys.path" + request = current.request + response = current.response + if version == 3: + services = self.amfrpc3_procedures + base_gateway = pyamf.remoting.gateway.BaseGateway(services) + pyamf_request = pyamf.remoting.decode(request.body) + else: + services = self.amfrpc_procedures + base_gateway = pyamf.remoting.gateway.BaseGateway(services) + context = pyamf.get_context(pyamf.AMF0) + pyamf_request = pyamf.remoting.decode(request.body, context) + pyamf_response = pyamf.remoting.Envelope(pyamf_request.amfVersion) + for name, message in pyamf_request: + pyamf_response[name] = base_gateway.getProcessor(message)(message) + response.headers['Content-Type'] = pyamf.remoting.CONTENT_TYPE + if version==3: + return pyamf.remoting.encode(pyamf_response).getvalue() + else: + return pyamf.remoting.encode(pyamf_response, context).getvalue() + + def serve_soap(self, version="1.1"): + try: + from contrib.pysimplesoap.server import SoapDispatcher + except: + return "pysimplesoap not installed in contrib" + request = current.request + response = current.response + procedures = self.soap_procedures + + location = "%s://%s%s" % ( + request.env.wsgi_url_scheme, + request.env.http_host, + URL(r=request,f="call/soap",vars={})) + namespace = 'namespace' in response and response.namespace or location + documentation = response.description or '' + dispatcher = SoapDispatcher( + name = response.title, + location = location, + action = location, # SOAPAction + namespace = namespace, + prefix='pys', + documentation = documentation, + ns = True) + for method, (function, returns, args, doc) in procedures.items(): + dispatcher.register_function(method, function, returns, args, doc) + if request.env.request_method == 'POST': + # Process normal Soap Operation + response.headers['Content-Type'] = 'text/xml' + return dispatcher.dispatch(request.body.read()) + elif 'WSDL' in request.vars: + # Return Web Service Description + response.headers['Content-Type'] = 'text/xml' + return dispatcher.wsdl() + elif 'op' in request.vars: + # Return method help webpage + response.headers['Content-Type'] = 'text/html' + method = request.vars['op'] + sample_req_xml, sample_res_xml, doc = dispatcher.help(method) + body = [H1("Welcome to Web2Py SOAP webservice gateway"), + A("See all webservice operations", + _href=URL(r=request,f="call/soap",vars={})), + H2(method), + P(doc), + UL(LI("Location: %s" % dispatcher.location), + LI("Namespace: %s" % dispatcher.namespace), + LI("SoapAction: %s" % dispatcher.action), + ), + H3("Sample SOAP XML Request Message:"), + CODE(sample_req_xml,language="xml"), + H3("Sample SOAP XML Response Message:"), + CODE(sample_res_xml,language="xml"), + ] + return {'body': body} + else: + # Return general help and method list webpage + response.headers['Content-Type'] = 'text/html' + body = [H1("Welcome to Web2Py SOAP webservice gateway"), + P(response.description), + P("The following operations are available"), + A("See WSDL for webservice description", + _href=URL(r=request,f="call/soap",vars={"WSDL":None})), + UL([LI(A("%s: %s" % (method, doc or ''), + _href=URL(r=request,f="call/soap",vars={'op': method}))) + for method, doc in dispatcher.list_methods()]), + ] + return {'body': body} + + def __call__(self): + """ + register services with: + service = Service(globals()) + @service.run + @service.rss + @service.json + @service.jsonrpc + @service.xmlrpc + @service.jsonrpc + @service.amfrpc + @service.amfrpc3('domain') + @service.soap('Method', returns={'Result':int}, args={'a':int,'b':int,}) + + expose services with + + def call(): return service() + + call services with + http://..../app/default/call/run?[parameters] + http://..../app/default/call/rss?[parameters] + http://..../app/default/call/json?[parameters] + http://..../app/default/call/jsonrpc + http://..../app/default/call/xmlrpc + http://..../app/default/call/amfrpc + http://..../app/default/call/amfrpc3 + http://..../app/default/call/soap + """ + + request = current.request + if len(request.args) < 1: + raise HTTP(404, "Not Found") + arg0 = request.args(0) + if arg0 == 'run': + return self.serve_run(request.args[1:]) + elif arg0 == 'rss': + return self.serve_rss(request.args[1:]) + elif arg0 == 'csv': + return self.serve_csv(request.args[1:]) + elif arg0 == 'xml': + return self.serve_xml(request.args[1:]) + elif arg0 == 'json': + return self.serve_json(request.args[1:]) + elif arg0 == 'jsonrpc': + return self.serve_jsonrpc() + elif arg0 == 'xmlrpc': + return self.serve_xmlrpc() + elif arg0 == 'amfrpc': + return self.serve_amfrpc() + elif arg0 == 'amfrpc3': + return self.serve_amfrpc(3) + elif arg0 == 'soap': + return self.serve_soap() + else: + self.error() + + def error(self): + raise HTTP(404, "Object does not exist") + + +def completion(callback): + """ + Executes a task on completion of the called action. For example: + + from gluon.tools import completion + @completion(lambda d: logging.info(repr(d))) + def index(): + return dict(message='hello') + + It logs the output of the function every time input is called. + The argument of completion is executed in a new thread. + """ + def _completion(f): + def __completion(*a,**b): + d = None + try: + d = f(*a,**b) + return d + finally: + thread.start_new_thread(callback,(d,)) + return __completion + return _completion + +def prettydate(d,T=lambda x:x): + try: + dt = datetime.datetime.now() - d + except: + return '' + if dt.days >= 2*365: + return T('%d years ago') % int(dt.days / 365) + elif dt.days >= 365: + return T('1 year ago') + elif dt.days >= 60: + return T('%d months ago') % int(dt.days / 30) + elif dt.days > 21: + return T('1 month ago') + elif dt.days >= 14: + return T('%d weeks ago') % int(dt.days / 7) + elif dt.days >= 7: + return T('1 week ago') + elif dt.days > 1: + return T('%d days ago') % dt.days + elif dt.days == 1: + return T('1 day ago') + elif dt.seconds >= 2*60*60: + return T('%d hours ago') % int(dt.seconds / 3600) + elif dt.seconds >= 60*60: + return T('1 hour ago') + elif dt.seconds >= 2*60: + return T('%d minutes ago') % int(dt.seconds / 60) + elif dt.seconds >= 60: + return T('1 minute ago') + elif dt.seconds > 1: + return T('%d seconds ago') % dt.seconds + elif dt.seconds == 1: + return T('1 second ago') + else: + return T('now') + +def test_thread_separation(): + def f(): + c=PluginManager() + lock1.acquire() + lock2.acquire() + c.x=7 + lock1.release() + lock2.release() + lock1=thread.allocate_lock() + lock2=thread.allocate_lock() + lock1.acquire() + thread.start_new_thread(f,()) + a=PluginManager() + a.x=5 + lock1.release() + lock2.acquire() + return a.x + +class PluginManager(object): + """ + + Plugin Manager is similar to a storage object but it is a single level singleton + this means that multiple instances within the same thread share the same attributes + Its constructor is also special. The first argument is the name of the plugin you are defining. + The named arguments are parameters needed by the plugin with default values. + If the parameters were previous defined, the old values are used. + + For example: + + ### in some general configuration file: + >>> plugins = PluginManager() + >>> plugins.me.param1=3 + + ### within the plugin model + >>> _ = PluginManager('me',param1=5,param2=6,param3=7) + + ### where the plugin is used + >>> print plugins.me.param1 + 3 + >>> print plugins.me.param2 + 6 + >>> plugins.me.param3 = 8 + >>> print plugins.me.param3 + 8 + + Here are some tests: + + >>> a=PluginManager() + >>> a.x=6 + >>> b=PluginManager('check') + >>> print b.x + 6 + >>> b=PluginManager() # reset settings + >>> print b.x + + >>> b.x=7 + >>> print a.x + 7 + >>> a.y.z=8 + >>> print b.y.z + 8 + >>> test_thread_separation() + 5 + >>> plugins=PluginManager('me',db='mydb') + >>> print plugins.me.db + mydb + >>> print 'me' in plugins + True + >>> print plugins.me.installed + True + """ + instances = {} + def __new__(cls,*a,**b): + id = thread.get_ident() + lock = thread.allocate_lock() + try: + lock.acquire() + try: + return cls.instances[id] + except KeyError: + instance = object.__new__(cls,*a,**b) + cls.instances[id] = instance + return instance + finally: + lock.release() + def __init__(self,plugin=None,**defaults): + if not plugin: + self.__dict__.clear() + settings = self.__getattr__(plugin) + settings.installed = True + [settings.update({key:value}) for key,value in defaults.items() if not key in settings] + def __getattr__(self, key): + if not key in self.__dict__: + self.__dict__[key] = Storage() + return self.__dict__[key] + def keys(self): + return self.__dict__.keys() + def __contains__(self,key): + return key in self.__dict__ + +if __name__ == '__main__': + import doctest + doctest.testmod() + ADDED gluon/utils.py Index: gluon/utils.py ================================================================== --- gluon/utils.py +++ gluon/utils.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +This file is part of the web2py Web Framework +Copyrighted by Massimo Di Pierro +License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) + +This file specifically includes utilities for security. +""" + +import hashlib +import hmac +import uuid +import random +import time +import os +import logging + +logger = logging.getLogger("web2py") + +def md5_hash(text): + """ Generate a md5 hash with the given text """ + return hashlib.md5(text).hexdigest() + +def simple_hash(text, digest_alg = 'md5'): + """ + Generates hash with the given text using the specified + digest hashing algorithm + """ + if not digest_alg: + raise RuntimeError, "simple_hash with digest_alg=None" + elif not isinstance(digest_alg,str): + h = digest_alg(text) + else: + h = hashlib.new(digest_alg) + h.update(text) + return h.hexdigest() + +def get_digest(value): + """ + Returns a hashlib digest algorithm from a string + """ + if not isinstance(value,str): + return value + value = value.lower() + if value == "md5": + return hashlib.md5 + elif value == "sha1": + return hashlib.sha1 + elif value == "sha224": + return hashlib.sha224 + elif value == "sha256": + return hashlib.sha256 + elif value == "sha384": + return hashlib.sha384 + elif value == "sha512": + return hashlib.sha512 + else: + raise ValueError("Invalid digest algorithm") + +def hmac_hash(value, key, digest_alg='md5', salt=None): + if ':' in key: + digest_alg, key = key.split(':') + digest_alg = get_digest(digest_alg) + d = hmac.new(key,value,digest_alg) + if salt: + d.update(str(salt)) + return d.hexdigest() + + +### compute constant ctokens +def initialize_urandom(): + """ + This function and the web2py_uuid follow from the following discussion: + http://groups.google.com/group/web2py-developers/browse_thread/thread/7fd5789a7da3f09 + + At startup web2py compute a unique ID that identifies the machine by adding + uuid.getnode() + int(time.time() * 1e3) + + This is a 48-bit number. It converts the number into 16 8-bit tokens. + It uses this value to initialize the entropy source ('/dev/urandom') and to seed random. + + If os.random() is not supported, it falls back to using random and issues a warning. + """ + node_id = uuid.getnode() + microseconds = int(time.time() * 1e6) + ctokens = [((node_id + microseconds) >> ((i%6)*8)) % 256 for i in range(16)] + random.seed(node_id + microseconds) + try: + os.urandom(1) + try: + # try to add process-specific entropy + frandom = open('/dev/urandom','wb') + try: + frandom.write(''.join(chr(t) for t in ctokens)) + finally: + frandom.close() + except IOError: + # works anyway + pass + except NotImplementedError: + logger.warning( +"""Cryptographically secure session management is not possible on your system because +your system does not provide a cryptographically secure entropy source. +This is not specific to web2py; consider deploying on a different operating system.""") + return ctokens +ctokens = initialize_urandom() + +def web2py_uuid(): + """ + This function follows from the following discussion: + http://groups.google.com/group/web2py-developers/browse_thread/thread/7fd5789a7da3f09 + + It works like uuid.uuid4 except that tries to use os.urandom() if possible + and it XORs the output with the tokens uniquely associated with this machine. + """ + bytes = [random.randrange(256) for i in range(16)] + try: + ubytes = [ord(c) for c in os.urandom(16)] # use /dev/urandom if possible + bytes = [bytes[i] ^ ubytes[i] for i in range(16)] + except NotImplementedError: + pass + ## xor bytes with constant ctokens + bytes = ''.join(chr(c ^ ctokens[i]) for i,c in enumerate(bytes)) + return str(uuid.UUID(bytes=bytes, version=4)) + ADDED gluon/validators.py Index: gluon/validators.py ================================================================== --- gluon/validators.py +++ gluon/validators.py @@ -0,0 +1,2934 @@ +#!/bin/env python +# -*- coding: utf-8 -*- + +""" +This file is part of the web2py Web Framework +Copyrighted by Massimo Di Pierro +License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) + +Thanks to ga2arch for help with IS_IN_DB and IS_NOT_IN_DB on GAE +""" + +import os +import re +import datetime +import time +import cgi +import urllib +import struct +import decimal +import unicodedata +from cStringIO import StringIO +from utils import simple_hash, hmac_hash + +__all__ = [ + 'CLEANUP', + 'CRYPT', + 'IS_ALPHANUMERIC', + 'IS_DATE_IN_RANGE', + 'IS_DATE', + 'IS_DATETIME_IN_RANGE', + 'IS_DATETIME', + 'IS_DECIMAL_IN_RANGE', + 'IS_EMAIL', + 'IS_EMPTY_OR', + 'IS_EXPR', + 'IS_FLOAT_IN_RANGE', + 'IS_IMAGE', + 'IS_IN_DB', + 'IS_IN_SET', + 'IS_INT_IN_RANGE', + 'IS_IPV4', + 'IS_LENGTH', + 'IS_LIST_OF', + 'IS_LOWER', + 'IS_MATCH', + 'IS_EQUAL_TO', + 'IS_NOT_EMPTY', + 'IS_NOT_IN_DB', + 'IS_NULL_OR', + 'IS_SLUG', + 'IS_STRONG', + 'IS_TIME', + 'IS_UPLOAD_FILENAME', + 'IS_UPPER', + 'IS_URL', + ] + +def translate(text): + if isinstance(text,(str,unicode)): + from globals import current + if hasattr(current,'T'): + return current.T(text) + return text + +def options_sorter(x,y): + return (str(x[1]).upper()>str(y[1]).upper() and 1) or -1 + +class Validator(object): + """ + Root for all validators, mainly for documentation purposes. + + Validators are classes used to validate input fields (including forms + generated from database tables). + + Here is an example of using a validator with a FORM:: + + INPUT(_name='a', requires=IS_INT_IN_RANGE(0, 10)) + + Here is an example of how to require a validator for a table field:: + + db.define_table('person', SQLField('name')) + db.person.name.requires=IS_NOT_EMPTY() + + Validators are always assigned using the requires attribute of a field. A + field can have a single validator or multiple validators. Multiple + validators are made part of a list:: + + db.person.name.requires=[IS_NOT_EMPTY(), IS_NOT_IN_DB(db, 'person.id')] + + Validators are called by the function accepts on a FORM or other HTML + helper object that contains a form. They are always called in the order in + which they are listed. + + Built-in validators have constructors that take the optional argument error + message which allows you to change the default error message. + Here is an example of a validator on a database table:: + + db.person.name.requires=IS_NOT_EMPTY(error_message=T('fill this')) + + where we have used the translation operator T to allow for + internationalization. + + Notice that default error messages are not translated. + """ + + def formatter(self, value): + """ + For some validators returns a formatted version (matching the validator) + of value. Otherwise just returns the value. + """ + return value + + +class IS_MATCH(Validator): + """ + example:: + + INPUT(_type='text', _name='name', requires=IS_MATCH('.+')) + + the argument of IS_MATCH is a regular expression:: + + >>> IS_MATCH('.+')('hello') + ('hello', None) + + >>> IS_MATCH('.+')('') + ('', 'invalid expression') + """ + + def __init__(self, expression, error_message='invalid expression', strict=True): + if strict: + if not expression.endswith('$'): + expression = '(%s)$' % expression + self.regex = re.compile(expression) + self.error_message = error_message + + def __call__(self, value): + match = self.regex.match(value) + if match: + return (match.group(), None) + return (value, translate(self.error_message)) + + +class IS_EQUAL_TO(Validator): + """ + example:: + + INPUT(_type='text', _name='password') + INPUT(_type='text', _name='password2', + requires=IS_EQUAL_TO(request.vars.password)) + + the argument of IS_EQUAL_TO is a string + + >>> IS_EQUAL_TO('aaa')('aaa') + ('aaa', None) + + >>> IS_EQUAL_TO('aaa')('aab') + ('aab', 'no match') + """ + + def __init__(self, expression, error_message='no match'): + self.expression = expression + self.error_message = error_message + + def __call__(self, value): + if value == self.expression: + return (value, None) + return (value, translate(self.error_message)) + + +class IS_EXPR(Validator): + """ + example:: + + INPUT(_type='text', _name='name', + requires=IS_EXPR('5 < int(value) < 10')) + + the argument of IS_EXPR must be python condition:: + + >>> IS_EXPR('int(value) < 2')('1') + ('1', None) + + >>> IS_EXPR('int(value) < 2')('2') + ('2', 'invalid expression') + """ + + def __init__(self, expression, error_message='invalid expression'): + self.expression = expression + self.error_message = error_message + + def __call__(self, value): + environment = {'value': value} + exec '__ret__=' + self.expression in environment + if environment['__ret__']: + return (value, None) + return (value, translate(self.error_message)) + + +class IS_LENGTH(Validator): + """ + Checks if length of field's value fits between given boundaries. Works + for both text and file inputs. + + Arguments: + + maxsize: maximum allowed length / size + minsize: minimum allowed length / size + + Examples:: + + #Check if text string is shorter than 33 characters: + INPUT(_type='text', _name='name', requires=IS_LENGTH(32)) + + #Check if password string is longer than 5 characters: + INPUT(_type='password', _name='name', requires=IS_LENGTH(minsize=6)) + + #Check if uploaded file has size between 1KB and 1MB: + INPUT(_type='file', _name='name', requires=IS_LENGTH(1048576, 1024)) + + >>> IS_LENGTH()('') + ('', None) + >>> IS_LENGTH()('1234567890') + ('1234567890', None) + >>> IS_LENGTH(maxsize=5, minsize=0)('1234567890') # too long + ('1234567890', 'enter from 0 to 5 characters') + >>> IS_LENGTH(maxsize=50, minsize=20)('1234567890') # too short + ('1234567890', 'enter from 20 to 50 characters') + """ + + def __init__(self, maxsize=255, minsize=0, + error_message='enter from %(min)g to %(max)g characters'): + self.maxsize = maxsize + self.minsize = minsize + self.error_message = error_message + + def __call__(self, value): + if isinstance(value, cgi.FieldStorage): + if value.file: + value.file.seek(0, os.SEEK_END) + length = value.file.tell() + value.file.seek(0, os.SEEK_SET) + else: + val = value.value + if val: + length = len(val) + else: + length = 0 + if self.minsize <= length <= self.maxsize: + return (value, None) + elif isinstance(value, (str, unicode, list)): + if self.minsize <= len(value) <= self.maxsize: + return (value, None) + elif self.minsize <= len(str(value)) <= self.maxsize: + try: + value.decode('utf8') + return (value, None) + except: + pass + return (value, translate(self.error_message) \ + % dict(min=self.minsize, max=self.maxsize)) + + +class IS_IN_SET(Validator): + """ + example:: + + INPUT(_type='text', _name='name', + requires=IS_IN_SET(['max', 'john'],zero='')) + + the argument of IS_IN_SET must be a list or set + + >>> IS_IN_SET(['max', 'john'])('max') + ('max', None) + >>> IS_IN_SET(['max', 'john'])('massimo') + ('massimo', 'value not allowed') + >>> IS_IN_SET(['max', 'john'], multiple=True)(('max', 'john')) + (('max', 'john'), None) + >>> IS_IN_SET(['max', 'john'], multiple=True)(('bill', 'john')) + (('bill', 'john'), 'value not allowed') + >>> IS_IN_SET(('id1','id2'), ['first label','second label'])('id1') # Traditional way + ('id1', None) + >>> IS_IN_SET({'id1':'first label', 'id2':'second label'})('id1') + ('id1', None) + >>> import itertools + >>> IS_IN_SET(itertools.chain(['1','3','5'],['2','4','6']))('1') + ('1', None) + >>> IS_IN_SET([('id1','first label'), ('id2','second label')])('id1') # Redundant way + ('id1', None) + """ + + def __init__( + self, + theset, + labels=None, + error_message='value not allowed', + multiple=False, + zero='', + sort=False, + ): + self.multiple = multiple + if isinstance(theset, dict): + self.theset = [str(item) for item in theset] + self.labels = theset.values() + elif theset and isinstance(theset, (tuple,list)) \ + and isinstance(theset[0], (tuple,list)) and len(theset[0])==2: + self.theset = [str(item) for item,label in theset] + self.labels = [str(label) for item,label in theset] + else: + self.theset = [str(item) for item in theset] + self.labels = labels + self.error_message = error_message + self.zero = zero + self.sort = sort + + def options(self,zero=True): + if not self.labels: + items = [(k, k) for (i, k) in enumerate(self.theset)] + else: + items = [(k, self.labels[i]) for (i, k) in enumerate(self.theset)] + if self.sort: + items.sort(options_sorter) + if zero and self.zero != None and not self.multiple: + items.insert(0,('',self.zero)) + return items + + def __call__(self, value): + if self.multiple: + ### if below was values = re.compile("[\w\-:]+").findall(str(value)) + if isinstance(value, (str,unicode)): + values = [value] + elif isinstance(value, (tuple, list)): + values = value + elif not value: + values = [] + else: + values = [value] + failures = [x for x in values if not x in self.theset] + if failures and self.theset: + if self.multiple and (value == None or value == ''): + return ([], None) + return (value, translate(self.error_message)) + if self.multiple: + if isinstance(self.multiple,(tuple,list)) and \ + not self.multiple[0]<=len(values)[^\)]+)\)s') + + +class IS_IN_DB(Validator): + """ + example:: + + INPUT(_type='text', _name='name', + requires=IS_IN_DB(db, db.mytable.myfield, zero='')) + + used for reference fields, rendered as a dropbox + """ + + def __init__( + self, + dbset, + field, + label=None, + error_message='value not in database', + orderby=None, + groupby=None, + cache=None, + multiple=False, + zero='', + sort=False, + _and=None, + ): + from dal import Table + if isinstance(field,Table): field = field._id + + if hasattr(dbset, 'define_table'): + self.dbset = dbset() + else: + self.dbset = dbset + self.field = field + (ktable, kfield) = str(self.field).split('.') + if not label: + label = '%%(%s)s' % kfield + if isinstance(label,str): + if regex1.match(str(label)): + label = '%%(%s)s' % str(label).split('.')[-1] + ks = regex2.findall(label) + if not kfield in ks: + ks += [kfield] + fields = ks + else: + ks = [kfield] + fields = 'all' + self.fields = fields + self.label = label + self.ktable = ktable + self.kfield = kfield + self.ks = ks + self.error_message = error_message + self.theset = None + self.orderby = orderby + self.groupby = groupby + self.cache = cache + self.multiple = multiple + self.zero = zero + self.sort = sort + self._and = _and + + def set_self_id(self, id): + if self._and: + self._and.record_id = id + + def build_set(self): + if self.fields == 'all': + fields = [f for f in self.dbset.db[self.ktable]] + else: + fields = [self.dbset.db[self.ktable][k] for k in self.fields] + if self.dbset.db._dbname != 'gae': + orderby = self.orderby or reduce(lambda a,b:a|b,fields) + groupby = self.groupby + dd = dict(orderby=orderby, groupby=groupby, cache=self.cache) + records = self.dbset.select(*fields, **dd) + else: + orderby = self.orderby or reduce(lambda a,b:a|b,(f for f in fields if not f.name=='id')) + dd = dict(orderby=orderby, cache=self.cache) + records = self.dbset.select(self.dbset.db[self.ktable].ALL, **dd) + self.theset = [str(r[self.kfield]) for r in records] + if isinstance(self.label,str): + self.labels = [self.label % dict(r) for r in records] + else: + self.labels = [self.label(r) for r in records] + + def options(self, zero=True): + self.build_set() + items = [(k, self.labels[i]) for (i, k) in enumerate(self.theset)] + if self.sort: + items.sort(options_sorter) + if zero and self.zero != None and not self.multiple: + items.insert(0,('',self.zero)) + return items + + def __call__(self, value): + if self.multiple: + if isinstance(value,list): + values=value + elif value: + values = [value] + else: + values = [] + if isinstance(self.multiple,(tuple,list)) and \ + not self.multiple[0]<=len(values) 0: + if isinstance(self.record_id, dict): + for f in self.record_id: + if str(getattr(rows[0], f)) != str(self.record_id[f]): + return (value, translate(self.error_message)) + elif str(rows[0].id) != str(self.record_id): + return (value, translate(self.error_message)) + return (value, None) + + +class IS_INT_IN_RANGE(Validator): + """ + Determine that the argument is (or can be represented as) an int, + and that it falls within the specified range. The range is interpreted + in the Pythonic way, so the test is: min <= value < max. + + The minimum and maximum limits can be None, meaning no lower or upper limit, + respectively. + + example:: + + INPUT(_type='text', _name='name', requires=IS_INT_IN_RANGE(0, 10)) + + >>> IS_INT_IN_RANGE(1,5)('4') + (4, None) + >>> IS_INT_IN_RANGE(1,5)(4) + (4, None) + >>> IS_INT_IN_RANGE(1,5)(1) + (1, None) + >>> IS_INT_IN_RANGE(1,5)(5) + (5, 'enter an integer between 1 and 4') + >>> IS_INT_IN_RANGE(1,5)(5) + (5, 'enter an integer between 1 and 4') + >>> IS_INT_IN_RANGE(1,5)(3.5) + (3, 'enter an integer between 1 and 4') + >>> IS_INT_IN_RANGE(None,5)('4') + (4, None) + >>> IS_INT_IN_RANGE(None,5)('6') + (6, 'enter an integer less than or equal to 4') + >>> IS_INT_IN_RANGE(1,None)('4') + (4, None) + >>> IS_INT_IN_RANGE(1,None)('0') + (0, 'enter an integer greater than or equal to 1') + >>> IS_INT_IN_RANGE()(6) + (6, None) + >>> IS_INT_IN_RANGE()('abc') + ('abc', 'enter an integer') + """ + + def __init__( + self, + minimum=None, + maximum=None, + error_message=None, + ): + self.minimum = self.maximum = None + if minimum is None: + if maximum is None: + self.error_message = error_message or 'enter an integer' + else: + self.maximum = int(maximum) + if error_message is None: + error_message = 'enter an integer less than or equal to %(max)g' + self.error_message = translate(error_message) % dict(max=self.maximum-1) + elif maximum is None: + self.minimum = int(minimum) + if error_message is None: + error_message = 'enter an integer greater than or equal to %(min)g' + self.error_message = translate(error_message) % dict(min=self.minimum) + else: + self.minimum = int(minimum) + self.maximum = int(maximum) + if error_message is None: + error_message = 'enter an integer between %(min)g and %(max)g' + self.error_message = translate(error_message) \ + % dict(min=self.minimum, max=self.maximum-1) + + def __call__(self, value): + try: + fvalue = float(value) + value = int(value) + if value != fvalue: + return (value, self.error_message) + if self.minimum is None: + if self.maximum is None or value < self.maximum: + return (value, None) + elif self.maximum is None: + if value >= self.minimum: + return (value, None) + elif self.minimum <= value < self.maximum: + return (value, None) + except ValueError: + pass + return (value, self.error_message) + + +class IS_FLOAT_IN_RANGE(Validator): + """ + Determine that the argument is (or can be represented as) a float, + and that it falls within the specified inclusive range. + The comparison is made with native arithmetic. + + The minimum and maximum limits can be None, meaning no lower or upper limit, + respectively. + + example:: + + INPUT(_type='text', _name='name', requires=IS_FLOAT_IN_RANGE(0, 10)) + + >>> IS_FLOAT_IN_RANGE(1,5)('4') + (4.0, None) + >>> IS_FLOAT_IN_RANGE(1,5)(4) + (4.0, None) + >>> IS_FLOAT_IN_RANGE(1,5)(1) + (1.0, None) + >>> IS_FLOAT_IN_RANGE(1,5)(5.25) + (5.25, 'enter a number between 1 and 5') + >>> IS_FLOAT_IN_RANGE(1,5)(6.0) + (6.0, 'enter a number between 1 and 5') + >>> IS_FLOAT_IN_RANGE(1,5)(3.5) + (3.5, None) + >>> IS_FLOAT_IN_RANGE(1,None)(3.5) + (3.5, None) + >>> IS_FLOAT_IN_RANGE(None,5)(3.5) + (3.5, None) + >>> IS_FLOAT_IN_RANGE(1,None)(0.5) + (0.5, 'enter a number greater than or equal to 1') + >>> IS_FLOAT_IN_RANGE(None,5)(6.5) + (6.5, 'enter a number less than or equal to 5') + >>> IS_FLOAT_IN_RANGE()(6.5) + (6.5, None) + >>> IS_FLOAT_IN_RANGE()('abc') + ('abc', 'enter a number') + """ + + def __init__( + self, + minimum=None, + maximum=None, + error_message=None, + dot='.' + ): + self.minimum = self.maximum = None + self.dot = dot + if minimum is None: + if maximum is None: + if error_message is None: + error_message = 'enter a number' + else: + self.maximum = float(maximum) + if error_message is None: + error_message = 'enter a number less than or equal to %(max)g' + elif maximum is None: + self.minimum = float(minimum) + if error_message is None: + error_message = 'enter a number greater than or equal to %(min)g' + else: + self.minimum = float(minimum) + self.maximum = float(maximum) + if error_message is None: + error_message = 'enter a number between %(min)g and %(max)g' + self.error_message = translate(error_message) \ + % dict(min=self.minimum, max=self.maximum) + + def __call__(self, value): + try: + if self.dot=='.': + fvalue = float(value) + else: + fvalue = float(str(value).replace(self.dot,'.')) + if self.minimum is None: + if self.maximum is None or fvalue <= self.maximum: + return (fvalue, None) + elif self.maximum is None: + if fvalue >= self.minimum: + return (fvalue, None) + elif self.minimum <= fvalue <= self.maximum: + return (fvalue, None) + except (ValueError, TypeError): + pass + return (value, self.error_message) + + def formatter(self,value): + if self.dot=='.': + return str(value) + else: + return str(value).replace('.',self.dot) + + +class IS_DECIMAL_IN_RANGE(Validator): + """ + Determine that the argument is (or can be represented as) a Python Decimal, + and that it falls within the specified inclusive range. + The comparison is made with Python Decimal arithmetic. + + The minimum and maximum limits can be None, meaning no lower or upper limit, + respectively. + + example:: + + INPUT(_type='text', _name='name', requires=IS_DECIMAL_IN_RANGE(0, 10)) + + >>> IS_DECIMAL_IN_RANGE(1,5)('4') + (Decimal('4'), None) + >>> IS_DECIMAL_IN_RANGE(1,5)(4) + (Decimal('4'), None) + >>> IS_DECIMAL_IN_RANGE(1,5)(1) + (Decimal('1'), None) + >>> IS_DECIMAL_IN_RANGE(1,5)(5.25) + (5.25, 'enter a number between 1 and 5') + >>> IS_DECIMAL_IN_RANGE(5.25,6)(5.25) + (Decimal('5.25'), None) + >>> IS_DECIMAL_IN_RANGE(5.25,6)('5.25') + (Decimal('5.25'), None) + >>> IS_DECIMAL_IN_RANGE(1,5)(6.0) + (6.0, 'enter a number between 1 and 5') + >>> IS_DECIMAL_IN_RANGE(1,5)(3.5) + (Decimal('3.5'), None) + >>> IS_DECIMAL_IN_RANGE(1.5,5.5)(3.5) + (Decimal('3.5'), None) + >>> IS_DECIMAL_IN_RANGE(1.5,5.5)(6.5) + (6.5, 'enter a number between 1.5 and 5.5') + >>> IS_DECIMAL_IN_RANGE(1.5,None)(6.5) + (Decimal('6.5'), None) + >>> IS_DECIMAL_IN_RANGE(1.5,None)(0.5) + (0.5, 'enter a number greater than or equal to 1.5') + >>> IS_DECIMAL_IN_RANGE(None,5.5)(4.5) + (Decimal('4.5'), None) + >>> IS_DECIMAL_IN_RANGE(None,5.5)(6.5) + (6.5, 'enter a number less than or equal to 5.5') + >>> IS_DECIMAL_IN_RANGE()(6.5) + (Decimal('6.5'), None) + >>> IS_DECIMAL_IN_RANGE(0,99)(123.123) + (123.123, 'enter a number between 0 and 99') + >>> IS_DECIMAL_IN_RANGE(0,99)('123.123') + ('123.123', 'enter a number between 0 and 99') + >>> IS_DECIMAL_IN_RANGE(0,99)('12.34') + (Decimal('12.34'), None) + >>> IS_DECIMAL_IN_RANGE()('abc') + ('abc', 'enter a decimal number') + """ + + def __init__( + self, + minimum=None, + maximum=None, + error_message=None, + dot='.' + ): + self.minimum = self.maximum = None + self.dot = dot + if minimum is None: + if maximum is None: + if error_message is None: + error_message = 'enter a decimal number' + else: + self.maximum = decimal.Decimal(str(maximum)) + if error_message is None: + error_message = 'enter a number less than or equal to %(max)g' + elif maximum is None: + self.minimum = decimal.Decimal(str(minimum)) + if error_message is None: + error_message = 'enter a number greater than or equal to %(min)g' + else: + self.minimum = decimal.Decimal(str(minimum)) + self.maximum = decimal.Decimal(str(maximum)) + if error_message is None: + error_message = 'enter a number between %(min)g and %(max)g' + self.error_message = translate(error_message) \ + % dict(min=self.minimum, max=self.maximum) + + def __call__(self, value): + try: + if isinstance(value,decimal.Decimal): + v = value + else: + v = decimal.Decimal(str(value).replace(self.dot,'.')) + if self.minimum is None: + if self.maximum is None or v <= self.maximum: + return (v, None) + elif self.maximum is None: + if v >= self.minimum: + return (v, None) + elif self.minimum <= v <= self.maximum: + return (v, None) + except (ValueError, TypeError, decimal.InvalidOperation): + pass + return (value, self.error_message) + + def formatter(self, value): + return str(value).replace('.',self.dot) + +def is_empty(value, empty_regex=None): + "test empty field" + if isinstance(value, (str, unicode)): + value = value.strip() + if empty_regex is not None and empty_regex.match(value): + value = '' + if value == None or value == '' or value == []: + return (value, True) + return (value, False) + +class IS_NOT_EMPTY(Validator): + """ + example:: + + INPUT(_type='text', _name='name', requires=IS_NOT_EMPTY()) + + >>> IS_NOT_EMPTY()(1) + (1, None) + >>> IS_NOT_EMPTY()(0) + (0, None) + >>> IS_NOT_EMPTY()('x') + ('x', None) + >>> IS_NOT_EMPTY()(' x ') + ('x', None) + >>> IS_NOT_EMPTY()(None) + (None, 'enter a value') + >>> IS_NOT_EMPTY()('') + ('', 'enter a value') + >>> IS_NOT_EMPTY()(' ') + ('', 'enter a value') + >>> IS_NOT_EMPTY()(' \\n\\t') + ('', 'enter a value') + >>> IS_NOT_EMPTY()([]) + ([], 'enter a value') + >>> IS_NOT_EMPTY(empty_regex='def')('def') + ('', 'enter a value') + >>> IS_NOT_EMPTY(empty_regex='de[fg]')('deg') + ('', 'enter a value') + >>> IS_NOT_EMPTY(empty_regex='def')('abc') + ('abc', None) + """ + + def __init__(self, error_message='enter a value', empty_regex=None): + self.error_message = error_message + if empty_regex is not None: + self.empty_regex = re.compile(empty_regex) + else: + self.empty_regex = None + + def __call__(self, value): + value, empty = is_empty(value, empty_regex=self.empty_regex) + if empty: + return (value, translate(self.error_message)) + return (value, None) + + +class IS_ALPHANUMERIC(IS_MATCH): + """ + example:: + + INPUT(_type='text', _name='name', requires=IS_ALPHANUMERIC()) + + >>> IS_ALPHANUMERIC()('1') + ('1', None) + >>> IS_ALPHANUMERIC()('') + ('', None) + >>> IS_ALPHANUMERIC()('A_a') + ('A_a', None) + >>> IS_ALPHANUMERIC()('!') + ('!', 'enter only letters, numbers, and underscore') + """ + + def __init__(self, error_message='enter only letters, numbers, and underscore'): + IS_MATCH.__init__(self, '^[\w]*$', error_message) + + +class IS_EMAIL(Validator): + """ + Checks if field's value is a valid email address. Can be set to disallow + or force addresses from certain domain(s). + + Email regex adapted from + http://haacked.com/archive/2007/08/21/i-knew-how-to-validate-an-email-address-until-i.aspx, + generally following the RFCs, except that we disallow quoted strings + and permit underscores and leading numerics in subdomain labels + + Arguments: + + - banned: regex text for disallowed address domains + - forced: regex text for required address domains + + Both arguments can also be custom objects with a match(value) method. + + Examples:: + + #Check for valid email address: + INPUT(_type='text', _name='name', + requires=IS_EMAIL()) + + #Check for valid email address that can't be from a .com domain: + INPUT(_type='text', _name='name', + requires=IS_EMAIL(banned='^.*\.com(|\..*)$')) + + #Check for valid email address that must be from a .edu domain: + INPUT(_type='text', _name='name', + requires=IS_EMAIL(forced='^.*\.edu(|\..*)$')) + + >>> IS_EMAIL()('a@b.com') + ('a@b.com', None) + >>> IS_EMAIL()('abc@def.com') + ('abc@def.com', None) + >>> IS_EMAIL()('abc@3def.com') + ('abc@3def.com', None) + >>> IS_EMAIL()('abc@def.us') + ('abc@def.us', None) + >>> IS_EMAIL()('abc@d_-f.us') + ('abc@d_-f.us', None) + >>> IS_EMAIL()('@def.com') # missing name + ('@def.com', 'enter a valid email address') + >>> IS_EMAIL()('"abc@def".com') # quoted name + ('"abc@def".com', 'enter a valid email address') + >>> IS_EMAIL()('abc+def.com') # no @ + ('abc+def.com', 'enter a valid email address') + >>> IS_EMAIL()('abc@def.x') # one-char TLD + ('abc@def.x', 'enter a valid email address') + >>> IS_EMAIL()('abc@def.12') # numeric TLD + ('abc@def.12', 'enter a valid email address') + >>> IS_EMAIL()('abc@def..com') # double-dot in domain + ('abc@def..com', 'enter a valid email address') + >>> IS_EMAIL()('abc@.def.com') # dot starts domain + ('abc@.def.com', 'enter a valid email address') + >>> IS_EMAIL()('abc@def.c_m') # underscore in TLD + ('abc@def.c_m', 'enter a valid email address') + >>> IS_EMAIL()('NotAnEmail') # missing @ + ('NotAnEmail', 'enter a valid email address') + >>> IS_EMAIL()('abc@NotAnEmail') # missing TLD + ('abc@NotAnEmail', 'enter a valid email address') + >>> IS_EMAIL()('customer/department@example.com') + ('customer/department@example.com', None) + >>> IS_EMAIL()('$A12345@example.com') + ('$A12345@example.com', None) + >>> IS_EMAIL()('!def!xyz%abc@example.com') + ('!def!xyz%abc@example.com', None) + >>> IS_EMAIL()('_Yosemite.Sam@example.com') + ('_Yosemite.Sam@example.com', None) + >>> IS_EMAIL()('~@example.com') + ('~@example.com', None) + >>> IS_EMAIL()('.wooly@example.com') # dot starts name + ('.wooly@example.com', 'enter a valid email address') + >>> IS_EMAIL()('wo..oly@example.com') # adjacent dots in name + ('wo..oly@example.com', 'enter a valid email address') + >>> IS_EMAIL()('pootietang.@example.com') # dot ends name + ('pootietang.@example.com', 'enter a valid email address') + >>> IS_EMAIL()('.@example.com') # name is bare dot + ('.@example.com', 'enter a valid email address') + >>> IS_EMAIL()('Ima.Fool@example.com') + ('Ima.Fool@example.com', None) + >>> IS_EMAIL()('Ima Fool@example.com') # space in name + ('Ima Fool@example.com', 'enter a valid email address') + >>> IS_EMAIL()('localguy@localhost') # localhost as domain + ('localguy@localhost', None) + + """ + + regex = re.compile(''' + ^(?!\.) # name may not begin with a dot + ( + [-a-z0-9!\#$%&'*+/=?^_`{|}~] # all legal characters except dot + | + (? obtained on 2008-Nov-10 + +official_url_schemes = [ + 'aaa', + 'aaas', + 'acap', + 'cap', + 'cid', + 'crid', + 'data', + 'dav', + 'dict', + 'dns', + 'fax', + 'file', + 'ftp', + 'go', + 'gopher', + 'h323', + 'http', + 'https', + 'icap', + 'im', + 'imap', + 'info', + 'ipp', + 'iris', + 'iris.beep', + 'iris.xpc', + 'iris.xpcs', + 'iris.lws', + 'ldap', + 'mailto', + 'mid', + 'modem', + 'msrp', + 'msrps', + 'mtqp', + 'mupdate', + 'news', + 'nfs', + 'nntp', + 'opaquelocktoken', + 'pop', + 'pres', + 'prospero', + 'rtsp', + 'service', + 'shttp', + 'sip', + 'sips', + 'snmp', + 'soap.beep', + 'soap.beeps', + 'tag', + 'tel', + 'telnet', + 'tftp', + 'thismessage', + 'tip', + 'tv', + 'urn', + 'vemmi', + 'wais', + 'xmlrpc.beep', + 'xmlrpc.beep', + 'xmpp', + 'z39.50r', + 'z39.50s', + ] +unofficial_url_schemes = [ + 'about', + 'adiumxtra', + 'aim', + 'afp', + 'aw', + 'callto', + 'chrome', + 'cvs', + 'ed2k', + 'feed', + 'fish', + 'gg', + 'gizmoproject', + 'iax2', + 'irc', + 'ircs', + 'itms', + 'jar', + 'javascript', + 'keyparc', + 'lastfm', + 'ldaps', + 'magnet', + 'mms', + 'msnim', + 'mvn', + 'notes', + 'nsfw', + 'psyc', + 'paparazzi:http', + 'rmi', + 'rsync', + 'secondlife', + 'sgn', + 'skype', + 'ssh', + 'sftp', + 'smb', + 'sms', + 'soldat', + 'steam', + 'svn', + 'teamspeak', + 'unreal', + 'ut2004', + 'ventrilo', + 'view-source', + 'webcal', + 'wyciwyg', + 'xfire', + 'xri', + 'ymsgr', + ] +all_url_schemes = [None] + official_url_schemes + unofficial_url_schemes +http_schemes = [None, 'http', 'https'] + + +# This regex comes from RFC 2396, Appendix B. It's used to split a URL into +# its component parts +# Here are the regex groups that it extracts: +# scheme = group(2) +# authority = group(4) +# path = group(5) +# query = group(7) +# fragment = group(9) + +url_split_regex = \ + re.compile('^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?') + +# Defined in RFC 3490, Section 3.1, Requirement #1 +# Use this regex to split the authority component of a unicode URL into +# its component labels +label_split_regex = re.compile(u'[\u002e\u3002\uff0e\uff61]') + + +def escape_unicode(string): + ''' + Converts a unicode string into US-ASCII, using a simple conversion scheme. + Each unicode character that does not have a US-ASCII equivalent is + converted into a URL escaped form based on its hexadecimal value. + For example, the unicode character '\u4e86' will become the string '%4e%86' + + :param string: unicode string, the unicode string to convert into an + escaped US-ASCII form + :returns: the US-ASCII escaped form of the inputted string + :rtype: string + + @author: Jonathan Benn + ''' + returnValue = StringIO() + + for character in string: + code = ord(character) + if code > 0x7F: + hexCode = hex(code) + returnValue.write('%' + hexCode[2:4] + '%' + hexCode[4:6]) + else: + returnValue.write(character) + + return returnValue.getvalue() + + +def unicode_to_ascii_authority(authority): + ''' + Follows the steps in RFC 3490, Section 4 to convert a unicode authority + string into its ASCII equivalent. + For example, u'www.Alliancefran\xe7aise.nu' will be converted into + 'www.xn--alliancefranaise-npb.nu' + + :param authority: unicode string, the URL authority component to convert, + e.g. u'www.Alliancefran\xe7aise.nu' + :returns: the US-ASCII character equivalent to the inputed authority, + e.g. 'www.xn--alliancefranaise-npb.nu' + :rtype: string + :raises Exception: if the function is not able to convert the inputed + authority + + @author: Jonathan Benn + ''' + #RFC 3490, Section 4, Step 1 + #The encodings.idna Python module assumes that AllowUnassigned == True + + #RFC 3490, Section 4, Step 2 + labels = label_split_regex.split(authority) + + #RFC 3490, Section 4, Step 3 + #The encodings.idna Python module assumes that UseSTD3ASCIIRules == False + + #RFC 3490, Section 4, Step 4 + #We use the ToASCII operation because we are about to put the authority + #into an IDN-unaware slot + asciiLabels = [] + try: + import encodings.idna + for label in labels: + if label: + asciiLabels.append(encodings.idna.ToASCII(label)) + else: + #encodings.idna.ToASCII does not accept an empty string, but + #it is necessary for us to allow for empty labels so that we + #don't modify the URL + asciiLabels.append('') + except: + asciiLabels=[str(label) for label in labels] + #RFC 3490, Section 4, Step 5 + return str(reduce(lambda x, y: x + unichr(0x002E) + y, asciiLabels)) + + +def unicode_to_ascii_url(url, prepend_scheme): + ''' + Converts the inputed unicode url into a US-ASCII equivalent. This function + goes a little beyond RFC 3490, which is limited in scope to the domain name + (authority) only. Here, the functionality is expanded to what was observed + on Wikipedia on 2009-Jan-22: + + Component Can Use Unicode? + --------- ---------------- + scheme No + authority Yes + path Yes + query Yes + fragment No + + The authority component gets converted to punycode, but occurrences of + unicode in other components get converted into a pair of URI escapes (we + assume 4-byte unicode). E.g. the unicode character U+4E2D will be + converted into '%4E%2D'. Testing with Firefox v3.0.5 has shown that it can + understand this kind of URI encoding. + + :param url: unicode string, the URL to convert from unicode into US-ASCII + :param prepend_scheme: string, a protocol scheme to prepend to the URL if + we're having trouble parsing it. + e.g. "http". Input None to disable this functionality + :returns: a US-ASCII equivalent of the inputed url + :rtype: string + + @author: Jonathan Benn + ''' + #convert the authority component of the URL into an ASCII punycode string, + #but encode the rest using the regular URI character encoding + + groups = url_split_regex.match(url).groups() + #If no authority was found + if not groups[3]: + #Try appending a scheme to see if that fixes the problem + scheme_to_prepend = prepend_scheme or 'http' + groups = url_split_regex.match( + unicode(scheme_to_prepend) + u'://' + url).groups() + #if we still can't find the authority + if not groups[3]: + raise Exception('No authority component found, '+ \ + 'could not decode unicode to US-ASCII') + + #We're here if we found an authority, let's rebuild the URL + scheme = groups[1] + authority = groups[3] + path = groups[4] or '' + query = groups[5] or '' + fragment = groups[7] or '' + + if prepend_scheme: + scheme = str(scheme) + '://' + else: + scheme = '' + return scheme + unicode_to_ascii_authority(authority) +\ + escape_unicode(path) + escape_unicode(query) + str(fragment) + + +class IS_GENERIC_URL(Validator): + """ + Rejects a URL string if any of the following is true: + * The string is empty or None + * The string uses characters that are not allowed in a URL + * The URL scheme specified (if one is specified) is not valid + + Based on RFC 2396: http://www.faqs.org/rfcs/rfc2396.html + + This function only checks the URL's syntax. It does not check that the URL + points to a real document, for example, or that it otherwise makes sense + semantically. This function does automatically prepend 'http://' in front + of a URL if and only if that's necessary to successfully parse the URL. + Please note that a scheme will be prepended only for rare cases + (e.g. 'google.ca:80') + + The list of allowed schemes is customizable with the allowed_schemes + parameter. If you exclude None from the list, then abbreviated URLs + (lacking a scheme such as 'http') will be rejected. + + The default prepended scheme is customizable with the prepend_scheme + parameter. If you set prepend_scheme to None then prepending will be + disabled. URLs that require prepending to parse will still be accepted, + but the return value will not be modified. + + @author: Jonathan Benn + + >>> IS_GENERIC_URL()('http://user@abc.com') + ('http://user@abc.com', None) + + """ + + def __init__( + self, + error_message='enter a valid URL', + allowed_schemes=None, + prepend_scheme=None, + ): + """ + :param error_message: a string, the error message to give the end user + if the URL does not validate + :param allowed_schemes: a list containing strings or None. Each element + is a scheme the inputed URL is allowed to use + :param prepend_scheme: a string, this scheme is prepended if it's + necessary to make the URL valid + """ + + self.error_message = error_message + if allowed_schemes == None: + self.allowed_schemes = all_url_schemes + else: + self.allowed_schemes = allowed_schemes + self.prepend_scheme = prepend_scheme + if self.prepend_scheme not in self.allowed_schemes: + raise SyntaxError, \ + "prepend_scheme='%s' is not in allowed_schemes=%s" \ + % (self.prepend_scheme, self.allowed_schemes) + + def __call__(self, value): + """ + :param value: a string, the URL to validate + :returns: a tuple, where tuple[0] is the inputed value (possible + prepended with prepend_scheme), and tuple[1] is either + None (success!) or the string error_message + """ + try: + # if the URL does not misuse the '%' character + if not re.compile( + r"%[^0-9A-Fa-f]{2}|%[^0-9A-Fa-f][0-9A-Fa-f]|%[0-9A-Fa-f][^0-9A-Fa-f]|%$|%[0-9A-Fa-f]$|%[^0-9A-Fa-f]$" + ).search(value): + # if the URL is only composed of valid characters + if re.compile( + r"[A-Za-z0-9;/?:@&=+$,\-_\.!~*'\(\)%#]+$").match(value): + # Then split up the URL into its components and check on + # the scheme + scheme = url_split_regex.match(value).group(2) + # Clean up the scheme before we check it + if scheme != None: + scheme = urllib.unquote(scheme).lower() + # If the scheme really exists + if scheme in self.allowed_schemes: + # Then the URL is valid + return (value, None) + else: + # else, for the possible case of abbreviated URLs with + # ports, check to see if adding a valid scheme fixes + # the problem (but only do this if it doesn't have + # one already!) + if not re.compile('://').search(value) and None\ + in self.allowed_schemes: + schemeToUse = self.prepend_scheme or 'http' + prependTest = self.__call__(schemeToUse + + '://' + value) + # if the prepend test succeeded + if prependTest[1] == None: + # if prepending in the output is enabled + if self.prepend_scheme: + return prependTest + else: + # else return the original, + # non-prepended value + return (value, None) + except: + pass + # else the URL is not valid + return (value, translate(self.error_message)) + +# Sources (obtained 2008-Nov-11): +# http://en.wikipedia.org/wiki/Top-level_domain +# http://www.iana.org/domains/root/db/ + +official_top_level_domains = [ + 'ac', + 'ad', + 'ae', + 'aero', + 'af', + 'ag', + 'ai', + 'al', + 'am', + 'an', + 'ao', + 'aq', + 'ar', + 'arpa', + 'as', + 'asia', + 'at', + 'au', + 'aw', + 'ax', + 'az', + 'ba', + 'bb', + 'bd', + 'be', + 'bf', + 'bg', + 'bh', + 'bi', + 'biz', + 'bj', + 'bl', + 'bm', + 'bn', + 'bo', + 'br', + 'bs', + 'bt', + 'bv', + 'bw', + 'by', + 'bz', + 'ca', + 'cat', + 'cc', + 'cd', + 'cf', + 'cg', + 'ch', + 'ci', + 'ck', + 'cl', + 'cm', + 'cn', + 'co', + 'com', + 'coop', + 'cr', + 'cu', + 'cv', + 'cx', + 'cy', + 'cz', + 'de', + 'dj', + 'dk', + 'dm', + 'do', + 'dz', + 'ec', + 'edu', + 'ee', + 'eg', + 'eh', + 'er', + 'es', + 'et', + 'eu', + 'example', + 'fi', + 'fj', + 'fk', + 'fm', + 'fo', + 'fr', + 'ga', + 'gb', + 'gd', + 'ge', + 'gf', + 'gg', + 'gh', + 'gi', + 'gl', + 'gm', + 'gn', + 'gov', + 'gp', + 'gq', + 'gr', + 'gs', + 'gt', + 'gu', + 'gw', + 'gy', + 'hk', + 'hm', + 'hn', + 'hr', + 'ht', + 'hu', + 'id', + 'ie', + 'il', + 'im', + 'in', + 'info', + 'int', + 'invalid', + 'io', + 'iq', + 'ir', + 'is', + 'it', + 'je', + 'jm', + 'jo', + 'jobs', + 'jp', + 'ke', + 'kg', + 'kh', + 'ki', + 'km', + 'kn', + 'kp', + 'kr', + 'kw', + 'ky', + 'kz', + 'la', + 'lb', + 'lc', + 'li', + 'lk', + 'localhost', + 'lr', + 'ls', + 'lt', + 'lu', + 'lv', + 'ly', + 'ma', + 'mc', + 'md', + 'me', + 'mf', + 'mg', + 'mh', + 'mil', + 'mk', + 'ml', + 'mm', + 'mn', + 'mo', + 'mobi', + 'mp', + 'mq', + 'mr', + 'ms', + 'mt', + 'mu', + 'museum', + 'mv', + 'mw', + 'mx', + 'my', + 'mz', + 'na', + 'name', + 'nc', + 'ne', + 'net', + 'nf', + 'ng', + 'ni', + 'nl', + 'no', + 'np', + 'nr', + 'nu', + 'nz', + 'om', + 'org', + 'pa', + 'pe', + 'pf', + 'pg', + 'ph', + 'pk', + 'pl', + 'pm', + 'pn', + 'pr', + 'pro', + 'ps', + 'pt', + 'pw', + 'py', + 'qa', + 're', + 'ro', + 'rs', + 'ru', + 'rw', + 'sa', + 'sb', + 'sc', + 'sd', + 'se', + 'sg', + 'sh', + 'si', + 'sj', + 'sk', + 'sl', + 'sm', + 'sn', + 'so', + 'sr', + 'st', + 'su', + 'sv', + 'sy', + 'sz', + 'tc', + 'td', + 'tel', + 'test', + 'tf', + 'tg', + 'th', + 'tj', + 'tk', + 'tl', + 'tm', + 'tn', + 'to', + 'tp', + 'tr', + 'travel', + 'tt', + 'tv', + 'tw', + 'tz', + 'ua', + 'ug', + 'uk', + 'um', + 'us', + 'uy', + 'uz', + 'va', + 'vc', + 've', + 'vg', + 'vi', + 'vn', + 'vu', + 'wf', + 'ws', + 'xn--0zwm56d', + 'xn--11b5bs3a9aj6g', + 'xn--80akhbyknj4f', + 'xn--9t4b11yi5a', + 'xn--deba0ad', + 'xn--g6w251d', + 'xn--hgbk6aj7f53bba', + 'xn--hlcj6aya9esc7a', + 'xn--jxalpdlp', + 'xn--kgbechtv', + 'xn--zckzah', + 'ye', + 'yt', + 'yu', + 'za', + 'zm', + 'zw', + ] + + +class IS_HTTP_URL(Validator): + """ + Rejects a URL string if any of the following is true: + * The string is empty or None + * The string uses characters that are not allowed in a URL + * The string breaks any of the HTTP syntactic rules + * The URL scheme specified (if one is specified) is not 'http' or 'https' + * The top-level domain (if a host name is specified) does not exist + + Based on RFC 2616: http://www.faqs.org/rfcs/rfc2616.html + + This function only checks the URL's syntax. It does not check that the URL + points to a real document, for example, or that it otherwise makes sense + semantically. This function does automatically prepend 'http://' in front + of a URL in the case of an abbreviated URL (e.g. 'google.ca'). + + The list of allowed schemes is customizable with the allowed_schemes + parameter. If you exclude None from the list, then abbreviated URLs + (lacking a scheme such as 'http') will be rejected. + + The default prepended scheme is customizable with the prepend_scheme + parameter. If you set prepend_scheme to None then prepending will be + disabled. URLs that require prepending to parse will still be accepted, + but the return value will not be modified. + + @author: Jonathan Benn + + >>> IS_HTTP_URL()('http://1.2.3.4') + ('http://1.2.3.4', None) + >>> IS_HTTP_URL()('http://abc.com') + ('http://abc.com', None) + >>> IS_HTTP_URL()('https://abc.com') + ('https://abc.com', None) + >>> IS_HTTP_URL()('httpx://abc.com') + ('httpx://abc.com', 'enter a valid URL') + >>> IS_HTTP_URL()('http://abc.com:80') + ('http://abc.com:80', None) + >>> IS_HTTP_URL()('http://user@abc.com') + ('http://user@abc.com', None) + >>> IS_HTTP_URL()('http://user@1.2.3.4') + ('http://user@1.2.3.4', None) + + """ + + def __init__( + self, + error_message='enter a valid URL', + allowed_schemes=None, + prepend_scheme='http', + ): + """ + :param error_message: a string, the error message to give the end user + if the URL does not validate + :param allowed_schemes: a list containing strings or None. Each element + is a scheme the inputed URL is allowed to use + :param prepend_scheme: a string, this scheme is prepended if it's + necessary to make the URL valid + """ + + self.error_message = error_message + if allowed_schemes == None: + self.allowed_schemes = http_schemes + else: + self.allowed_schemes = allowed_schemes + self.prepend_scheme = prepend_scheme + + for i in self.allowed_schemes: + if i not in http_schemes: + raise SyntaxError, \ + "allowed_scheme value '%s' is not in %s" % \ + (i, http_schemes) + + if self.prepend_scheme not in self.allowed_schemes: + raise SyntaxError, \ + "prepend_scheme='%s' is not in allowed_schemes=%s" % \ + (self.prepend_scheme, self.allowed_schemes) + + def __call__(self, value): + """ + :param value: a string, the URL to validate + :returns: a tuple, where tuple[0] is the inputed value + (possible prepended with prepend_scheme), and tuple[1] is either + None (success!) or the string error_message + """ + + try: + # if the URL passes generic validation + x = IS_GENERIC_URL(error_message=self.error_message, + allowed_schemes=self.allowed_schemes, + prepend_scheme=self.prepend_scheme) + if x(value)[1] == None: + componentsMatch = url_split_regex.match(value) + authority = componentsMatch.group(4) + # if there is an authority component + if authority: + # if authority is a valid IP address + if re.compile( + "([\w.!~*'|;:&=+$,-]+@)?\d+\.\d+\.\d+\.\d+(:\d*)*$").match(authority): + # Then this HTTP URL is valid + return (value, None) + else: + # else if authority is a valid domain name + domainMatch = \ + re.compile( + "([\w.!~*'|;:&=+$,-]+@)?(([A-Za-z0-9]+[A-Za-z0-9\-]*[A-Za-z0-9]+\.)*([A-Za-z0-9]+\.)*)*([A-Za-z]+[A-Za-z0-9\-]*[A-Za-z0-9]+)\.?(:\d*)*$" + ).match(authority) + if domainMatch: + # if the top-level domain really exists + if domainMatch.group(5).lower()\ + in official_top_level_domains: + # Then this HTTP URL is valid + return (value, None) + else: + # else this is a relative/abbreviated URL, which will parse + # into the URL's path component + path = componentsMatch.group(5) + # relative case: if this is a valid path (if it starts with + # a slash) + if re.compile('/').match(path): + # Then this HTTP URL is valid + return (value, None) + else: + # abbreviated case: if we haven't already, prepend a + # scheme and see if it fixes the problem + if not re.compile('://').search(value): + schemeToUse = self.prepend_scheme or 'http' + prependTest = self.__call__(schemeToUse + + '://' + value) + # if the prepend test succeeded + if prependTest[1] == None: + # if prepending in the output is enabled + if self.prepend_scheme: + return prependTest + else: + # else return the original, non-prepended + # value + return (value, None) + except: + pass + # else the HTTP URL is not valid + return (value, translate(self.error_message)) + + +class IS_URL(Validator): + """ + Rejects a URL string if any of the following is true: + * The string is empty or None + * The string uses characters that are not allowed in a URL + * The string breaks any of the HTTP syntactic rules + * The URL scheme specified (if one is specified) is not 'http' or 'https' + * The top-level domain (if a host name is specified) does not exist + + (These rules are based on RFC 2616: http://www.faqs.org/rfcs/rfc2616.html) + + This function only checks the URL's syntax. It does not check that the URL + points to a real document, for example, or that it otherwise makes sense + semantically. This function does automatically prepend 'http://' in front + of a URL in the case of an abbreviated URL (e.g. 'google.ca'). + + If the parameter mode='generic' is used, then this function's behavior + changes. It then rejects a URL string if any of the following is true: + * The string is empty or None + * The string uses characters that are not allowed in a URL + * The URL scheme specified (if one is specified) is not valid + + (These rules are based on RFC 2396: http://www.faqs.org/rfcs/rfc2396.html) + + The list of allowed schemes is customizable with the allowed_schemes + parameter. If you exclude None from the list, then abbreviated URLs + (lacking a scheme such as 'http') will be rejected. + + The default prepended scheme is customizable with the prepend_scheme + parameter. If you set prepend_scheme to None then prepending will be + disabled. URLs that require prepending to parse will still be accepted, + but the return value will not be modified. + + IS_URL is compatible with the Internationalized Domain Name (IDN) standard + specified in RFC 3490 (http://tools.ietf.org/html/rfc3490). As a result, + URLs can be regular strings or unicode strings. + If the URL's domain component (e.g. google.ca) contains non-US-ASCII + letters, then the domain will be converted into Punycode (defined in + RFC 3492, http://tools.ietf.org/html/rfc3492). IS_URL goes a bit beyond + the standards, and allows non-US-ASCII characters to be present in the path + and query components of the URL as well. These non-US-ASCII characters will + be escaped using the standard '%20' type syntax. e.g. the unicode + character with hex code 0x4e86 will become '%4e%86' + + Code Examples:: + + INPUT(_type='text', _name='name', requires=IS_URL()) + >>> IS_URL()('abc.com') + ('http://abc.com', None) + + INPUT(_type='text', _name='name', requires=IS_URL(mode='generic')) + >>> IS_URL(mode='generic')('abc.com') + ('abc.com', None) + + INPUT(_type='text', _name='name', + requires=IS_URL(allowed_schemes=['https'], prepend_scheme='https')) + >>> IS_URL(allowed_schemes=['https'], prepend_scheme='https')('https://abc.com') + ('https://abc.com', None) + + INPUT(_type='text', _name='name', + requires=IS_URL(prepend_scheme='https')) + >>> IS_URL(prepend_scheme='https')('abc.com') + ('https://abc.com', None) + + INPUT(_type='text', _name='name', + requires=IS_URL(mode='generic', allowed_schemes=['ftps', 'https'], + prepend_scheme='https')) + >>> IS_URL(mode='generic', allowed_schemes=['ftps', 'https'], prepend_scheme='https')('https://abc.com') + ('https://abc.com', None) + >>> IS_URL(mode='generic', allowed_schemes=['ftps', 'https', None], prepend_scheme='https')('abc.com') + ('abc.com', None) + + @author: Jonathan Benn + """ + + def __init__( + self, + error_message='enter a valid URL', + mode='http', + allowed_schemes=None, + prepend_scheme='http', + ): + """ + :param error_message: a string, the error message to give the end user + if the URL does not validate + :param allowed_schemes: a list containing strings or None. Each element + is a scheme the inputed URL is allowed to use + :param prepend_scheme: a string, this scheme is prepended if it's + necessary to make the URL valid + """ + + self.error_message = error_message + self.mode = mode.lower() + if not self.mode in ['generic', 'http']: + raise SyntaxError, "invalid mode '%s' in IS_URL" % self.mode + self.allowed_schemes = allowed_schemes + + if self.allowed_schemes: + if prepend_scheme not in self.allowed_schemes: + raise SyntaxError, \ + "prepend_scheme='%s' is not in allowed_schemes=%s" \ + % (prepend_scheme, self.allowed_schemes) + + # if allowed_schemes is None, then we will defer testing + # prepend_scheme's validity to a sub-method + + self.prepend_scheme = prepend_scheme + + def __call__(self, value): + """ + :param value: a unicode or regular string, the URL to validate + :returns: a (string, string) tuple, where tuple[0] is the modified + input value and tuple[1] is either None (success!) or the + string error_message. The input value will never be modified in the + case of an error. However, if there is success then the input URL + may be modified to (1) prepend a scheme, and/or (2) convert a + non-compliant unicode URL into a compliant US-ASCII version. + """ + + if self.mode == 'generic': + subMethod = IS_GENERIC_URL(error_message=self.error_message, + allowed_schemes=self.allowed_schemes, + prepend_scheme=self.prepend_scheme) + elif self.mode == 'http': + subMethod = IS_HTTP_URL(error_message=self.error_message, + allowed_schemes=self.allowed_schemes, + prepend_scheme=self.prepend_scheme) + else: + raise SyntaxError, "invalid mode '%s' in IS_URL" % self.mode + + if type(value) != unicode: + return subMethod(value) + else: + try: + asciiValue = unicode_to_ascii_url(value, self.prepend_scheme) + except Exception: + #If we are not able to convert the unicode url into a + # US-ASCII URL, then the URL is not valid + return (value, translate(self.error_message)) + + methodResult = subMethod(asciiValue) + #if the validation of the US-ASCII version of the value failed + if methodResult[1] != None: + # then return the original input value, not the US-ASCII version + return (value, methodResult[1]) + else: + return methodResult + + +regex_time = re.compile( + '((?P[0-9]+))([^0-9 ]+(?P[0-9 ]+))?([^0-9ap ]+(?P[0-9]*))?((?P[ap]m))?') + + +class IS_TIME(Validator): + """ + example:: + + INPUT(_type='text', _name='name', requires=IS_TIME()) + + understands the following formats + hh:mm:ss [am/pm] + hh:mm [am/pm] + hh [am/pm] + + [am/pm] is optional, ':' can be replaced by any other non-space non-digit + + >>> IS_TIME()('21:30') + (datetime.time(21, 30), None) + >>> IS_TIME()('21-30') + (datetime.time(21, 30), None) + >>> IS_TIME()('21.30') + (datetime.time(21, 30), None) + >>> IS_TIME()('21:30:59') + (datetime.time(21, 30, 59), None) + >>> IS_TIME()('5:30') + (datetime.time(5, 30), None) + >>> IS_TIME()('5:30 am') + (datetime.time(5, 30), None) + >>> IS_TIME()('5:30 pm') + (datetime.time(17, 30), None) + >>> IS_TIME()('5:30 whatever') + ('5:30 whatever', 'enter time as hh:mm:ss (seconds, am, pm optional)') + >>> IS_TIME()('5:30 20') + ('5:30 20', 'enter time as hh:mm:ss (seconds, am, pm optional)') + >>> IS_TIME()('24:30') + ('24:30', 'enter time as hh:mm:ss (seconds, am, pm optional)') + >>> IS_TIME()('21:60') + ('21:60', 'enter time as hh:mm:ss (seconds, am, pm optional)') + >>> IS_TIME()('21:30::') + ('21:30::', 'enter time as hh:mm:ss (seconds, am, pm optional)') + >>> IS_TIME()('') + ('', 'enter time as hh:mm:ss (seconds, am, pm optional)') + """ + + def __init__(self, error_message='enter time as hh:mm:ss (seconds, am, pm optional)'): + self.error_message = error_message + + def __call__(self, value): + try: + ivalue = value + value = regex_time.match(value.lower()) + (h, m, s) = (int(value.group('h')), 0, 0) + if value.group('m') != None: + m = int(value.group('m')) + if value.group('s') != None: + s = int(value.group('s')) + if value.group('d') == 'pm' and 0 < h < 12: + h = h + 12 + if not (h in range(24) and m in range(60) and s + in range(60)): + raise ValueError\ + ('Hours or minutes or seconds are outside of allowed range') + value = datetime.time(h, m, s) + return (value, None) + except AttributeError: + pass + except ValueError: + pass + return (ivalue, translate(self.error_message)) + + +class IS_DATE(Validator): + """ + example:: + + INPUT(_type='text', _name='name', requires=IS_DATE()) + + date has to be in the ISO8960 format YYYY-MM-DD + """ + + def __init__(self, format='%Y-%m-%d', + error_message='enter date as %(format)s'): + self.format = str(format) + self.error_message = str(error_message) + + def __call__(self, value): + if isinstance(value,datetime.date): + return (value,None) + try: + (y, m, d, hh, mm, ss, t0, t1, t2) = \ + time.strptime(value, str(self.format)) + value = datetime.date(y, m, d) + return (value, None) + except: + return (value, translate(self.error_message) % IS_DATETIME.nice(self.format)) + + def formatter(self, value): + format = self.format + year = value.year + y = '%.4i' % year + format = format.replace('%y',y[-2:]) + format = format.replace('%Y',y) + if year<1900: + year = 2000 + d = datetime.date(year,value.month,value.day) + return d.strftime(format) + + +class IS_DATETIME(Validator): + """ + example:: + + INPUT(_type='text', _name='name', requires=IS_DATETIME()) + + datetime has to be in the ISO8960 format YYYY-MM-DD hh:mm:ss + """ + + isodatetime = '%Y-%m-%d %H:%M:%S' + + @staticmethod + def nice(format): + code=(('%Y','1963'), + ('%y','63'), + ('%d','28'), + ('%m','08'), + ('%b','Aug'), + ('%b','August'), + ('%H','14'), + ('%I','02'), + ('%p','PM'), + ('%M','30'), + ('%S','59')) + for (a,b) in code: + format=format.replace(a,b) + return dict(format=format) + + def __init__(self, format='%Y-%m-%d %H:%M:%S', + error_message='enter date and time as %(format)s'): + self.format = str(format) + self.error_message = str(error_message) + + def __call__(self, value): + if isinstance(value,datetime.datetime): + return (value,None) + try: + (y, m, d, hh, mm, ss, t0, t1, t2) = \ + time.strptime(value, str(self.format)) + value = datetime.datetime(y, m, d, hh, mm, ss) + return (value, None) + except: + return (value, translate(self.error_message) % IS_DATETIME.nice(self.format)) + + def formatter(self, value): + format = self.format + year = value.year + y = '%.4i' % year + format = format.replace('%y',y[-2:]) + format = format.replace('%Y',y) + if year<1900: + year = 2000 + d = datetime.datetime(year,value.month,value.day,value.hour,value.minute,value.second) + return d.strftime(format) + +class IS_DATE_IN_RANGE(IS_DATE): + """ + example:: + + >>> v = IS_DATE_IN_RANGE(minimum=datetime.date(2008,1,1), \ + maximum=datetime.date(2009,12,31), \ + format="%m/%d/%Y",error_message="oops") + + >>> v('03/03/2008') + (datetime.date(2008, 3, 3), None) + + >>> v('03/03/2010') + (datetime.date(2010, 3, 3), 'oops') + + >>> v(datetime.date(2008,3,3)) + (datetime.date(2008, 3, 3), None) + + >>> v(datetime.date(2010,3,3)) + (datetime.date(2010, 3, 3), 'oops') + + """ + def __init__(self, + minimum = None, + maximum = None, + format='%Y-%m-%d', + error_message = None): + self.minimum = minimum + self.maximum = maximum + if error_message is None: + if minimum is None: + error_message = "enter date on or before %(max)s" + elif maximum is None: + error_message = "enter date on or after %(min)s" + else: + error_message = "enter date in range %(min)s %(max)s" + d = dict(min=minimum, max=maximum) + IS_DATE.__init__(self, + format = format, + error_message = error_message % d) + + def __call__(self, value): + (value, msg) = IS_DATE.__call__(self,value) + if msg is not None: + return (value, msg) + if self.minimum and self.minimum > value: + return (value, translate(self.error_message)) + if self.maximum and value > self.maximum: + return (value, translate(self.error_message)) + return (value, None) + + +class IS_DATETIME_IN_RANGE(IS_DATETIME): + """ + example:: + + >>> v = IS_DATETIME_IN_RANGE(\ + minimum=datetime.datetime(2008,1,1,12,20), \ + maximum=datetime.datetime(2009,12,31,12,20), \ + format="%m/%d/%Y %H:%M",error_message="oops") + >>> v('03/03/2008 12:40') + (datetime.datetime(2008, 3, 3, 12, 40), None) + + >>> v('03/03/2010 10:34') + (datetime.datetime(2010, 3, 3, 10, 34), 'oops') + + >>> v(datetime.datetime(2008,3,3,0,0)) + (datetime.datetime(2008, 3, 3, 0, 0), None) + + >>> v(datetime.datetime(2010,3,3,0,0)) + (datetime.datetime(2010, 3, 3, 0, 0), 'oops') + """ + def __init__(self, + minimum = None, + maximum = None, + format = '%Y-%m-%d %H:%M:%S', + error_message = None): + self.minimum = minimum + self.maximum = maximum + if error_message is None: + if minimum is None: + error_message = "enter date and time on or before %(max)s" + elif maximum is None: + error_message = "enter date and time on or after %(min)s" + else: + error_message = "enter date and time in range %(min)s %(max)s" + d = dict(min = minimum, max = maximum) + IS_DATETIME.__init__(self, + format = format, + error_message = error_message % d) + + def __call__(self, value): + (value, msg) = IS_DATETIME.__call__(self, value) + if msg is not None: + return (value, msg) + if self.minimum and self.minimum > value: + return (value, translate(self.error_message)) + if self.maximum and value > self.maximum: + return (value, translate(self.error_message)) + return (value, None) + + +class IS_LIST_OF(Validator): + + def __init__(self, other): + self.other = other + + def __call__(self, value): + ivalue = value + if not isinstance(value, list): + ivalue = [ivalue] + new_value = [] + for item in ivalue: + (v, e) = self.other(item) + if e: + return (value, e) + else: + new_value.append(v) + return (new_value, None) + + +class IS_LOWER(Validator): + """ + convert to lower case + + >>> IS_LOWER()('ABC') + ('abc', None) + >>> IS_LOWER()('Ñ') + ('\\xc3\\xb1', None) + """ + + def __call__(self, value): + return (value.decode('utf8').lower().encode('utf8'), None) + + +class IS_UPPER(Validator): + """ + convert to upper case + + >>> IS_UPPER()('abc') + ('ABC', None) + >>> IS_UPPER()('ñ') + ('\\xc3\\x91', None) + """ + + def __call__(self, value): + return (value.decode('utf8').upper().encode('utf8'), None) + + +def urlify(value, maxlen=80, keep_underscores=False): + """ + Convert incoming string to a simplified ASCII subset. + if (keep_underscores): underscores are retained in the string + else: underscores are translated to hyphens (default) + """ + s = value.lower() # to lowercase + s = s.decode('utf-8') # to utf-8 + s = unicodedata.normalize('NFKD', s) # normalize eg è => e, ñ => n + s = s.encode('ASCII', 'ignore') # encode as ASCII + s = re.sub('&\w+;', '', s) # strip html entities + if keep_underscores: + s = re.sub('\s+', '-', s) # whitespace to hyphens + s = re.sub('[^\w\-]', '', s) # strip all but alphanumeric/underscore/hyphen + else: + s = re.sub('[\s_]+', '-', s) # whitespace & underscores to hyphens + s = re.sub('[^a-z0-9\-]', '', s) # strip all but alphanumeric/hyphen + s = re.sub('[-_][-_]+', '-', s) # collapse strings of hyphens + s = s.strip('-') # remove leading and trailing hyphens + return s[:maxlen] # enforce maximum length + + +class IS_SLUG(Validator): + """ + convert arbitrary text string to a slug + + >>> IS_SLUG()('abc123') + ('abc123', None) + >>> IS_SLUG()('ABC123') + ('abc123', None) + >>> IS_SLUG()('abc-123') + ('abc-123', None) + >>> IS_SLUG()('abc--123') + ('abc-123', None) + >>> IS_SLUG()('abc 123') + ('abc-123', None) + >>> IS_SLUG()('abc\t_123') + ('abc-123', None) + >>> IS_SLUG()('-abc-') + ('abc', None) + >>> IS_SLUG()('--a--b--_ -c--') + ('a-b-c', None) + >>> IS_SLUG()('abc&123') + ('abc123', None) + >>> IS_SLUG()('abc&123&def') + ('abc123def', None) + >>> IS_SLUG()('ñ') + ('n', None) + >>> IS_SLUG(maxlen=4)('abc123') + ('abc1', None) + >>> IS_SLUG()('abc_123') + ('abc-123', None) + >>> IS_SLUG(keep_underscores=False)('abc_123') + ('abc-123', None) + >>> IS_SLUG(keep_underscores=True)('abc_123') + ('abc_123', None) + >>> IS_SLUG(check=False)('abc') + ('abc', None) + >>> IS_SLUG(check=True)('abc') + ('abc', None) + >>> IS_SLUG(check=False)('a bc') + ('a-bc', None) + >>> IS_SLUG(check=True)('a bc') + ('a bc', 'must be slug') + """ + + @staticmethod + def urlify(value, maxlen=80, keep_underscores=False): + return urlify(value, maxlen, keep_underscores) + + def __init__(self, maxlen=80, check=False, error_message='must be slug', keep_underscores=False): + self.maxlen = maxlen + self.check = check + self.error_message = error_message + self.keep_underscores = keep_underscores + + def __call__(self, value): + if self.check and value != urlify(value, self.maxlen, self.keep_underscores): + return (value, translate(self.error_message)) + return (urlify(value,self.maxlen, self.keep_underscores), None) + +class IS_EMPTY_OR(Validator): + """ + dummy class for testing IS_EMPTY_OR + + >>> IS_EMPTY_OR(IS_EMAIL())('abc@def.com') + ('abc@def.com', None) + >>> IS_EMPTY_OR(IS_EMAIL())(' ') + (None, None) + >>> IS_EMPTY_OR(IS_EMAIL(), null='abc')(' ') + ('abc', None) + >>> IS_EMPTY_OR(IS_EMAIL(), null='abc', empty_regex='def')('def') + ('abc', None) + >>> IS_EMPTY_OR(IS_EMAIL())('abc') + ('abc', 'enter a valid email address') + >>> IS_EMPTY_OR(IS_EMAIL())(' abc ') + ('abc', 'enter a valid email address') + """ + + def __init__(self, other, null=None, empty_regex=None): + (self.other, self.null) = (other, null) + if empty_regex is not None: + self.empty_regex = re.compile(empty_regex) + else: + self.empty_regex = None + if hasattr(other, 'multiple'): + self.multiple = other.multiple + if hasattr(other, 'options'): + self.options=self._options + + def _options(self): + options = self.other.options() + if (not options or options[0][0]!='') and not self.multiple: + options.insert(0,('','')) + return options + + def set_self_id(self, id): + if isinstance(self.other, (list, tuple)): + for item in self.other: + if hasattr(item, 'set_self_id'): + item.set_self_id(id) + else: + if hasattr(self.other, 'set_self_id'): + self.other.set_self_id(id) + + def __call__(self, value): + value, empty = is_empty(value, empty_regex=self.empty_regex) + if empty: + return (self.null, None) + if isinstance(self.other, (list, tuple)): + for item in self.other: + value, error = item(value) + if error: break + return value, error + else: + return self.other(value) + + def formatter(self, value): + if hasattr(self.other, 'formatter'): + return self.other.formatter(value) + return value + +IS_NULL_OR = IS_EMPTY_OR # for backward compatibility + + +class CLEANUP(Validator): + """ + example:: + + INPUT(_type='text', _name='name', requires=CLEANUP()) + + removes special characters on validation + """ + + def __init__(self, regex='[^\x09\x0a\x0d\x20-\x7e]'): + self.regex = re.compile(regex) + + def __call__(self, value): + v = self.regex.sub('',str(value).strip()) + return (v, None) + + +class CRYPT(object): + """ + example:: + + INPUT(_type='text', _name='name', requires=CRYPT()) + + encodes the value on validation with a digest. + + If no arguments are provided CRYPT uses the MD5 algorithm. + If the key argument is provided the HMAC+MD5 algorithm is used. + If the digest_alg is specified this is used to replace the + MD5 with, for example, SHA512. The digest_alg can be + the name of a hashlib algorithm as a string or the algorithm itself. + """ + + def __init__(self, key=None, digest_alg='md5'): + self.key = key + self.digest_alg = digest_alg + + def __call__(self, value): + if self.key: + return (hmac_hash(value, self.key, self.digest_alg), None) + else: + return (simple_hash(value, self.digest_alg), None) + + +class IS_STRONG(object): + """ + example:: + + INPUT(_type='password', _name='passwd', + requires=IS_STRONG(min=10, special=2, upper=2)) + + enforces complexity requirements on a field + """ + + def __init__(self, min=8, max=20, upper=1, lower=1, number=1, + special=1, specials=r'~!@#$%^&*()_+-=?<>,.:;{}[]|', + invalid=' "', error_message=None): + self.min = min + self.max = max + self.upper = upper + self.lower = lower + self.number = number + self.special = special + self.specials = specials + self.invalid = invalid + self.error_message = error_message + + def __call__(self, value): + failures = [] + if type(self.min) == int and self.min > 0: + if not len(value) >= self.min: + failures.append("Minimum length is %s" % self.min) + if type(self.max) == int and self.max > 0: + if not len(value) <= self.max: + failures.append("Maximum length is %s" % self.max) + if type(self.special) == int: + all_special = [ch in value for ch in self.specials] + if self.special > 0: + if not all_special.count(True) >= self.special: + failures.append("Must include at least %s of the following : %s" % (self.special, self.specials)) + if self.invalid: + all_invalid = [ch in value for ch in self.invalid] + if all_invalid.count(True) > 0: + failures.append("May not contain any of the following: %s" \ + % self.invalid) + if type(self.upper) == int: + all_upper = re.findall("[A-Z]", value) + if self.upper > 0: + if not len(all_upper) >= self.upper: + failures.append("Must include at least %s upper case" \ + % str(self.upper)) + else: + if len(all_upper) > 0: + failures.append("May not include any upper case letters") + if type(self.lower) == int: + all_lower = re.findall("[a-z]", value) + if self.lower > 0: + if not len(all_lower) >= self.lower: + failures.append("Must include at least %s lower case" \ + % str(self.lower)) + else: + if len(all_lower) > 0: + failures.append("May not include any lower case letters") + if type(self.number) == int: + all_number = re.findall("[0-9]", value) + if self.number > 0: + numbers = "number" + if self.number > 1: + numbers = "numbers" + if not len(all_number) >= self.number: + failures.append("Must include at least %s %s" \ + % (str(self.number), numbers)) + else: + if len(all_number) > 0: + failures.append("May not include any numbers") + if len(failures) == 0: + return (value, None) + if not translate(self.error_message): + from html import XML + return (value, XML('
    '.join(failures))) + else: + return (value, translate(self.error_message)) + + +class IS_IN_SUBSET(IS_IN_SET): + + def __init__(self, *a, **b): + IS_IN_SET.__init__(self, *a, **b) + + def __call__(self, value): + values = re.compile("\w+").findall(str(value)) + failures = [x for x in values if IS_IN_SET.__call__(self, x)[1]] + if failures: + return (value, translate(self.error_message)) + return (value, None) + + +class IS_IMAGE(Validator): + """ + Checks if file uploaded through file input was saved in one of selected + image formats and has dimensions (width and height) within given boundaries. + + Does *not* check for maximum file size (use IS_LENGTH for that). Returns + validation failure if no data was uploaded. + + Supported file formats: BMP, GIF, JPEG, PNG. + + Code parts taken from + http://mail.python.org/pipermail/python-list/2007-June/617126.html + + Arguments: + + extensions: iterable containing allowed *lowercase* image file extensions + ('jpg' extension of uploaded file counts as 'jpeg') + maxsize: iterable containing maximum width and height of the image + minsize: iterable containing minimum width and height of the image + + Use (-1, -1) as minsize to pass image size check. + + Examples:: + + #Check if uploaded file is in any of supported image formats: + INPUT(_type='file', _name='name', requires=IS_IMAGE()) + + #Check if uploaded file is either JPEG or PNG: + INPUT(_type='file', _name='name', + requires=IS_IMAGE(extensions=('jpeg', 'png'))) + + #Check if uploaded file is PNG with maximum size of 200x200 pixels: + INPUT(_type='file', _name='name', + requires=IS_IMAGE(extensions=('png'), maxsize=(200, 200))) + """ + + def __init__(self, + extensions=('bmp', 'gif', 'jpeg', 'png'), + maxsize=(10000, 10000), + minsize=(0, 0), + error_message='invalid image'): + + self.extensions = extensions + self.maxsize = maxsize + self.minsize = minsize + self.error_message = error_message + + def __call__(self, value): + try: + extension = value.filename.rfind('.') + assert extension >= 0 + extension = value.filename[extension + 1:].lower() + if extension == 'jpg': + extension = 'jpeg' + assert extension in self.extensions + if extension == 'bmp': + width, height = self.__bmp(value.file) + elif extension == 'gif': + width, height = self.__gif(value.file) + elif extension == 'jpeg': + width, height = self.__jpeg(value.file) + elif extension == 'png': + width, height = self.__png(value.file) + else: + width = -1 + height = -1 + assert self.minsize[0] <= width <= self.maxsize[0] \ + and self.minsize[1] <= height <= self.maxsize[1] + value.file.seek(0) + return (value, None) + except: + return (value, translate(self.error_message)) + + def __bmp(self, stream): + if stream.read(2) == 'BM': + stream.read(16) + return struct.unpack("= 0xC0 and code <= 0xC3: + return tuple(reversed( + struct.unpack("!xHH", stream.read(5)))) + else: + stream.read(length - 2) + return (-1, -1) + + def __png(self, stream): + if stream.read(8) == '\211PNG\r\n\032\n': + stream.read(4) + if stream.read(4) == "IHDR": + return struct.unpack("!LL", stream.read(8)) + return (-1, -1) + + +class IS_UPLOAD_FILENAME(Validator): + """ + Checks if name and extension of file uploaded through file input matches + given criteria. + + Does *not* ensure the file type in any way. Returns validation failure + if no data was uploaded. + + Arguments:: + + filename: filename (before dot) regex + extension: extension (after dot) regex + lastdot: which dot should be used as a filename / extension separator: + True means last dot, eg. file.png -> file / png + False means first dot, eg. file.tar.gz -> file / tar.gz + case: 0 - keep the case, 1 - transform the string into lowercase (default), + 2 - transform the string into uppercase + + If there is no dot present, extension checks will be done against empty + string and filename checks against whole value. + + Examples:: + + #Check if file has a pdf extension (case insensitive): + INPUT(_type='file', _name='name', + requires=IS_UPLOAD_FILENAME(extension='pdf')) + + #Check if file has a tar.gz extension and name starting with backup: + INPUT(_type='file', _name='name', + requires=IS_UPLOAD_FILENAME(filename='backup.*', + extension='tar.gz', lastdot=False)) + + #Check if file has no extension and name matching README + #(case sensitive): + INPUT(_type='file', _name='name', + requires=IS_UPLOAD_FILENAME(filename='^README$', + extension='^$', case=0)) + """ + + def __init__(self, filename=None, extension=None, lastdot=True, case=1, + error_message='enter valid filename'): + if isinstance(filename, str): + filename = re.compile(filename) + if isinstance(extension, str): + extension = re.compile(extension) + self.filename = filename + self.extension = extension + self.lastdot = lastdot + self.case = case + self.error_message = error_message + + def __call__(self, value): + try: + string = value.filename + except: + return (value, translate(self.error_message)) + if self.case == 1: + string = string.lower() + elif self.case == 2: + string = string.upper() + if self.lastdot: + dot = string.rfind('.') + else: + dot = string.find('.') + if dot == -1: + dot = len(string) + if self.filename and not self.filename.match(string[:dot]): + return (value, translate(self.error_message)) + elif self.extension and not self.extension.match(string[dot + 1:]): + return (value, translate(self.error_message)) + else: + return (value, None) + + +class IS_IPV4(Validator): + """ + Checks if field's value is an IP version 4 address in decimal form. Can + be set to force addresses from certain range. + + IPv4 regex taken from: http://regexlib.com/REDetails.aspx?regexp_id=1411 + + Arguments: + + minip: lowest allowed address; accepts: + str, eg. 192.168.0.1 + list or tuple of octets, eg. [192, 168, 0, 1] + maxip: highest allowed address; same as above + invert: True to allow addresses only from outside of given range; note + that range boundaries are not matched this way + is_localhost: localhost address treatment: + None (default): indifferent + True (enforce): query address must match localhost address + (127.0.0.1) + False (forbid): query address must not match localhost + address + is_private: same as above, except that query address is checked against + two address ranges: 172.16.0.0 - 172.31.255.255 and + 192.168.0.0 - 192.168.255.255 + is_automatic: same as above, except that query address is checked against + one address range: 169.254.0.0 - 169.254.255.255 + + Minip and maxip may also be lists or tuples of addresses in all above + forms (str, int, list / tuple), allowing setup of multiple address ranges: + + minip = (minip1, minip2, ... minipN) + | | | + | | | + maxip = (maxip1, maxip2, ... maxipN) + + Longer iterable will be truncated to match length of shorter one. + + Examples:: + + #Check for valid IPv4 address: + INPUT(_type='text', _name='name', requires=IS_IPV4()) + + #Check for valid IPv4 address belonging to specific range: + INPUT(_type='text', _name='name', + requires=IS_IPV4(minip='100.200.0.0', maxip='100.200.255.255')) + + #Check for valid IPv4 address belonging to either 100.110.0.0 - + #100.110.255.255 or 200.50.0.0 - 200.50.0.255 address range: + INPUT(_type='text', _name='name', + requires=IS_IPV4(minip=('100.110.0.0', '200.50.0.0'), + maxip=('100.110.255.255', '200.50.0.255'))) + + #Check for valid IPv4 address belonging to private address space: + INPUT(_type='text', _name='name', requires=IS_IPV4(is_private=True)) + + #Check for valid IPv4 address that is not a localhost address: + INPUT(_type='text', _name='name', requires=IS_IPV4(is_localhost=False)) + + >>> IS_IPV4()('1.2.3.4') + ('1.2.3.4', None) + >>> IS_IPV4()('255.255.255.255') + ('255.255.255.255', None) + >>> IS_IPV4()('1.2.3.4 ') + ('1.2.3.4 ', 'enter valid IPv4 address') + >>> IS_IPV4()('1.2.3.4.5') + ('1.2.3.4.5', 'enter valid IPv4 address') + >>> IS_IPV4()('123.123') + ('123.123', 'enter valid IPv4 address') + >>> IS_IPV4()('1111.2.3.4') + ('1111.2.3.4', 'enter valid IPv4 address') + >>> IS_IPV4()('0111.2.3.4') + ('0111.2.3.4', 'enter valid IPv4 address') + >>> IS_IPV4()('256.2.3.4') + ('256.2.3.4', 'enter valid IPv4 address') + >>> IS_IPV4()('300.2.3.4') + ('300.2.3.4', 'enter valid IPv4 address') + >>> IS_IPV4(minip='1.2.3.4', maxip='1.2.3.4')('1.2.3.4') + ('1.2.3.4', None) + >>> IS_IPV4(minip='1.2.3.5', maxip='1.2.3.9', error_message='bad ip')('1.2.3.4') + ('1.2.3.4', 'bad ip') + >>> IS_IPV4(maxip='1.2.3.4', invert=True)('127.0.0.1') + ('127.0.0.1', None) + >>> IS_IPV4(maxip='1.2.3.4', invert=True)('1.2.3.4') + ('1.2.3.4', 'enter valid IPv4 address') + >>> IS_IPV4(is_localhost=True)('127.0.0.1') + ('127.0.0.1', None) + >>> IS_IPV4(is_localhost=True)('1.2.3.4') + ('1.2.3.4', 'enter valid IPv4 address') + >>> IS_IPV4(is_localhost=False)('127.0.0.1') + ('127.0.0.1', 'enter valid IPv4 address') + >>> IS_IPV4(maxip='100.0.0.0', is_localhost=True)('127.0.0.1') + ('127.0.0.1', 'enter valid IPv4 address') + """ + + regex = re.compile( + '^(([1-9]?\d|1\d\d|2[0-4]\d|25[0-5])\.){3}([1-9]?\d|1\d\d|2[0-4]\d|25[0-5])$') + numbers = (16777216, 65536, 256, 1) + localhost = 2130706433 + private = ((2886729728L, 2886795263L), (3232235520L, 3232301055L)) + automatic = (2851995648L, 2852061183L) + + def __init__( + self, + minip='0.0.0.0', + maxip='255.255.255.255', + invert=False, + is_localhost=None, + is_private=None, + is_automatic=None, + error_message='enter valid IPv4 address'): + for n, value in enumerate((minip, maxip)): + temp = [] + if isinstance(value, str): + temp.append(value.split('.')) + elif isinstance(value, (list, tuple)): + if len(value) == len(filter(lambda item: isinstance(item, int), value)) == 4: + temp.append(value) + else: + for item in value: + if isinstance(item, str): + temp.append(item.split('.')) + elif isinstance(item, (list, tuple)): + temp.append(item) + numbers = [] + for item in temp: + number = 0 + for i, j in zip(self.numbers, item): + number += i * int(j) + numbers.append(number) + if n == 0: + self.minip = numbers + else: + self.maxip = numbers + self.invert = invert + self.is_localhost = is_localhost + self.is_private = is_private + self.is_automatic = is_automatic + self.error_message = error_message + + def __call__(self, value): + if self.regex.match(value): + number = 0 + for i, j in zip(self.numbers, value.split('.')): + number += i * int(j) + ok = False + for bottom, top in zip(self.minip, self.maxip): + if self.invert != (bottom <= number <= top): + ok = True + if not (self.is_localhost == None or self.is_localhost == \ + (number == self.localhost)): + ok = False + if not (self.is_private == None or self.is_private == \ + (sum([number[0] <= number <= number[1] for number in self.private]) > 0)): + ok = False + if not (self.is_automatic == None or self.is_automatic == \ + (self.automatic[0] <= number <= self.automatic[1])): + ok = False + if ok: + return (value, None) + return (value, translate(self.error_message)) + +if __name__ == '__main__': + import doctest + doctest.testmod() + ADDED gluon/widget.py Index: gluon/widget.py ================================================================== --- gluon/widget.py +++ gluon/widget.py @@ -0,0 +1,922 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +This file is part of the web2py Web Framework +Copyrighted by Massimo Di Pierro +License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) + +The widget is called from web2py. +""" + +import sys +import cStringIO +import time +import thread +import re +import os +import socket +import signal +import math +import logging + +import newcron +import main + +from fileutils import w2p_pack, read_file, write_file +from shell import run, test +from settings import global_settings + +try: + import Tkinter, tkMessageBox + import contrib.taskbar_widget + from winservice import web2py_windows_service_handler +except: + pass + + +try: + BaseException +except NameError: + BaseException = Exception + +ProgramName = 'web2py Web Framework' +ProgramAuthor = 'Created by Massimo Di Pierro, Copyright 2007-2011' +ProgramVersion = read_file('VERSION').strip() + +ProgramInfo = '''%s + %s + %s''' % (ProgramName, ProgramAuthor, ProgramVersion) + +if not sys.version[:3] in ['2.4', '2.5', '2.6', '2.7']: + msg = 'Warning: web2py requires Python 2.4, 2.5 (recommended), 2.6 or 2.7 but you are running:\n%s' + msg = msg % sys.version + sys.stderr.write(msg) + +logger = logging.getLogger("web2py") + +class IO(object): + """ """ + + def __init__(self): + """ """ + + self.buffer = cStringIO.StringIO() + + def write(self, data): + """ """ + + sys.__stdout__.write(data) + if hasattr(self, 'callback'): + self.callback(data) + else: + self.buffer.write(data) + + +def try_start_browser(url): + """ Try to start the default browser """ + + try: + import webbrowser + webbrowser.open(url) + except: + print 'warning: unable to detect your browser' + + +def start_browser(ip, port): + """ Starts the default browser """ + print 'please visit:' + print '\thttp://%s:%s' % (ip, port) + print 'starting browser...' + try_start_browser('http://%s:%s' % (ip, port)) + + +def presentation(root): + """ Draw the splash screen """ + + root.withdraw() + + dx = root.winfo_screenwidth() + dy = root.winfo_screenheight() + + dialog = Tkinter.Toplevel(root, bg='white') + dialog.geometry('%ix%i+%i+%i' % (500, 300, dx / 2 - 200, dy / 2 - 150)) + + dialog.overrideredirect(1) + dialog.focus_force() + + canvas = Tkinter.Canvas(dialog, + background='white', + width=500, + height=300) + canvas.pack() + root.update() + + img = Tkinter.PhotoImage(file='splashlogo.gif') + pnl = Tkinter.Label(canvas, image=img, background='white', bd=0) + pnl.pack(side='top', fill='both', expand='yes') + # Prevent garbage collection of img + pnl.image=img + + def add_label(text='Change Me', font_size=12, foreground='#195866', height=1): + return Tkinter.Label( + master=canvas, + width=250, + height=height, + text=text, + font=('Helvetica', font_size), + anchor=Tkinter.CENTER, + foreground=foreground, + background='white' + ) + + add_label('Welcome to...').pack(side='top') + add_label(ProgramName, 18, '#FF5C1F', 2).pack() + add_label(ProgramAuthor).pack() + add_label(ProgramVersion).pack() + + root.update() + time.sleep(5) + dialog.destroy() + return + + +class web2pyDialog(object): + """ Main window dialog """ + + def __init__(self, root, options): + """ web2pyDialog constructor """ + + root.title('web2py server') + self.root = Tkinter.Toplevel(root) + self.options = options + self.menu = Tkinter.Menu(self.root) + servermenu = Tkinter.Menu(self.menu, tearoff=0) + httplog = os.path.join(self.options.folder, 'httpserver.log') + + # Building the Menu + item = lambda: try_start_browser(httplog) + servermenu.add_command(label='View httpserver.log', + command=item) + + servermenu.add_command(label='Quit (pid:%i)' % os.getpid(), + command=self.quit) + + self.menu.add_cascade(label='Server', menu=servermenu) + + self.pagesmenu = Tkinter.Menu(self.menu, tearoff=0) + self.menu.add_cascade(label='Pages', menu=self.pagesmenu) + + helpmenu = Tkinter.Menu(self.menu, tearoff=0) + + # Home Page + item = lambda: try_start_browser('http://www.web2py.com') + helpmenu.add_command(label='Home Page', + command=item) + + # About + item = lambda: tkMessageBox.showinfo('About web2py', ProgramInfo) + helpmenu.add_command(label='About', + command=item) + + self.menu.add_cascade(label='Info', menu=helpmenu) + + self.root.config(menu=self.menu) + + if options.taskbar: + self.root.protocol('WM_DELETE_WINDOW', + lambda: self.quit(True)) + else: + self.root.protocol('WM_DELETE_WINDOW', self.quit) + + sticky = Tkinter.NW + + # IP + Tkinter.Label(self.root, + text='Server IP:', + justify=Tkinter.LEFT).grid(row=0, + column=0, + sticky=sticky) + self.ip = Tkinter.Entry(self.root) + self.ip.insert(Tkinter.END, self.options.ip) + self.ip.grid(row=0, column=1, sticky=sticky) + + # Port + Tkinter.Label(self.root, + text='Server Port:', + justify=Tkinter.LEFT).grid(row=1, + column=0, + sticky=sticky) + + self.port_number = Tkinter.Entry(self.root) + self.port_number.insert(Tkinter.END, self.options.port) + self.port_number.grid(row=1, column=1, sticky=sticky) + + # Password + Tkinter.Label(self.root, + text='Choose Password:', + justify=Tkinter.LEFT).grid(row=2, + column=0, + sticky=sticky) + + self.password = Tkinter.Entry(self.root, show='*') + self.password.bind('', lambda e: self.start()) + self.password.focus_force() + self.password.grid(row=2, column=1, sticky=sticky) + + # Prepare the canvas + self.canvas = Tkinter.Canvas(self.root, + width=300, + height=100, + bg='black') + self.canvas.grid(row=3, column=0, columnspan=2) + self.canvas.after(1000, self.update_canvas) + + # Prepare the frame + frame = Tkinter.Frame(self.root) + frame.grid(row=4, column=0, columnspan=2) + + # Start button + self.button_start = Tkinter.Button(frame, + text='start server', + command=self.start) + + self.button_start.grid(row=0, column=0) + + # Stop button + self.button_stop = Tkinter.Button(frame, + text='stop server', + command=self.stop) + + self.button_stop.grid(row=0, column=1) + self.button_stop.configure(state='disabled') + + if options.taskbar: + self.tb = contrib.taskbar_widget.TaskBarIcon() + self.checkTaskBar() + + if options.password != '': + self.password.insert(0, options.password) + self.start() + self.root.withdraw() + else: + self.tb = None + + def checkTaskBar(self): + """ Check taskbar status """ + + if self.tb.status: + if self.tb.status[0] == self.tb.EnumStatus.QUIT: + self.quit() + elif self.tb.status[0] == self.tb.EnumStatus.TOGGLE: + if self.root.state() == 'withdrawn': + self.root.deiconify() + else: + self.root.withdraw() + elif self.tb.status[0] == self.tb.EnumStatus.STOP: + self.stop() + elif self.tb.status[0] == self.tb.EnumStatus.START: + self.start() + elif self.tb.status[0] == self.tb.EnumStatus.RESTART: + self.stop() + self.start() + del self.tb.status[0] + + self.root.after(1000, self.checkTaskBar) + + def update(self, text): + """ Update app text """ + + try: + self.text.configure(state='normal') + self.text.insert('end', text) + self.text.configure(state='disabled') + except: + pass # ## this should only happen in case app is destroyed + + def connect_pages(self): + """ Connect pages """ + + for arq in os.listdir('applications/'): + if os.path.exists('applications/%s/__init__.py' % arq): + url = self.url + '/' + arq + start_browser = lambda u = url: try_start_browser(u) + self.pagesmenu.add_command(label=url, + command=start_browser) + + def quit(self, justHide=False): + """ Finish the program execution """ + + if justHide: + self.root.withdraw() + else: + try: + self.server.stop() + except: + pass + + try: + self.tb.Destroy() + except: + pass + + self.root.destroy() + sys.exit() + + def error(self, message): + """ Show error message """ + + tkMessageBox.showerror('web2py start server', message) + + def start(self): + """ Start web2py server """ + + password = self.password.get() + + if not password: + self.error('no password, no web admin interface') + + ip = self.ip.get() + + regexp = '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}' + if ip and not re.compile(regexp).match(ip): + return self.error('invalid host ip address') + + try: + port = int(self.port_number.get()) + except: + return self.error('invalid port number') + + self.url = 'http://%s:%s' % (ip, port) + self.connect_pages() + self.button_start.configure(state='disabled') + + try: + options = self.options + req_queue_size = options.request_queue_size + self.server = main.HttpServer( + ip, + port, + password, + pid_filename=options.pid_filename, + log_filename=options.log_filename, + profiler_filename=options.profiler_filename, + ssl_certificate=options.ssl_certificate, + ssl_private_key=options.ssl_private_key, + min_threads=options.minthreads, + max_threads=options.maxthreads, + server_name=options.server_name, + request_queue_size=req_queue_size, + timeout=options.timeout, + shutdown_timeout=options.shutdown_timeout, + path=options.folder, + interfaces=options.interfaces) + + thread.start_new_thread(self.server.start, ()) + except Exception, e: + self.button_start.configure(state='normal') + return self.error(str(e)) + + self.button_stop.configure(state='normal') + + if not options.taskbar: + thread.start_new_thread(start_browser, (ip, port)) + + self.password.configure(state='readonly') + self.ip.configure(state='readonly') + self.port_number.configure(state='readonly') + + if self.tb: + self.tb.SetServerRunning() + + def stop(self): + """ Stop web2py server """ + + self.button_start.configure(state='normal') + self.button_stop.configure(state='disabled') + self.password.configure(state='normal') + self.ip.configure(state='normal') + self.port_number.configure(state='normal') + self.server.stop() + + if self.tb: + self.tb.SetServerStopped() + + def update_canvas(self): + """ Update canvas """ + + try: + t1 = os.path.getsize('httpserver.log') + except: + self.canvas.after(1000, self.update_canvas) + return + + try: + fp = open('httpserver.log', 'r') + fp.seek(self.t0) + data = fp.read(t1 - self.t0) + fp.close() + value = self.p0[1:] + [10 + 90.0 / math.sqrt(1 + data.count('\n'))] + self.p0 = value + + for i in xrange(len(self.p0) - 1): + c = self.canvas.coords(self.q0[i]) + self.canvas.coords(self.q0[i], + (c[0], + self.p0[i], + c[2], + self.p0[i + 1])) + self.t0 = t1 + except BaseException: + self.t0 = time.time() + self.t0 = t1 + self.p0 = [100] * 300 + self.q0 = [self.canvas.create_line(i, 100, i + 1, 100, + fill='green') for i in xrange(len(self.p0) - 1)] + + self.canvas.after(1000, self.update_canvas) + + +def console(): + """ Defines the behavior of the console web2py execution """ + import optparse + import textwrap + + usage = "python web2py.py" + + description = """\ + web2py Web Framework startup script. + ATTENTION: unless a password is specified (-a 'passwd') web2py will + attempt to run a GUI. In this case command line options are ignored.""" + + description = textwrap.dedent(description) + + parser = optparse.OptionParser(usage, None, optparse.Option, ProgramVersion) + + parser.description = description + + parser.add_option('-i', + '--ip', + default='127.0.0.1', + dest='ip', + help='ip address of the server (127.0.0.1)') + + parser.add_option('-p', + '--port', + default='8000', + dest='port', + type='int', + help='port of server (8000)') + + msg = 'password to be used for administration' + msg += ' (use -a "" to reuse the last password))' + parser.add_option('-a', + '--password', + default='', + dest='password', + help=msg) + + parser.add_option('-c', + '--ssl_certificate', + default='', + dest='ssl_certificate', + help='file that contains ssl certificate') + + parser.add_option('-k', + '--ssl_private_key', + default='', + dest='ssl_private_key', + help='file that contains ssl private key') + + parser.add_option('-d', + '--pid_filename', + default='httpserver.pid', + dest='pid_filename', + help='file to store the pid of the server') + + parser.add_option('-l', + '--log_filename', + default='httpserver.log', + dest='log_filename', + help='file to log connections') + + parser.add_option('-n', + '--numthreads', + default=None, + type='int', + dest='numthreads', + help='number of threads (deprecated)') + + parser.add_option('--minthreads', + default=None, + type='int', + dest='minthreads', + help='minimum number of server threads') + + parser.add_option('--maxthreads', + default=None, + type='int', + dest='maxthreads', + help='maximum number of server threads') + + parser.add_option('-s', + '--server_name', + default=socket.gethostname(), + dest='server_name', + help='server name for the web server') + + msg = 'max number of queued requests when server unavailable' + parser.add_option('-q', + '--request_queue_size', + default='5', + type='int', + dest='request_queue_size', + help=msg) + + parser.add_option('-o', + '--timeout', + default='10', + type='int', + dest='timeout', + help='timeout for individual request (10 seconds)') + + parser.add_option('-z', + '--shutdown_timeout', + default='5', + type='int', + dest='shutdown_timeout', + help='timeout on shutdown of server (5 seconds)') + parser.add_option('-f', + '--folder', + default=os.getcwd(), + dest='folder', + help='folder from which to run web2py') + + parser.add_option('-v', + '--verbose', + action='store_true', + dest='verbose', + default=False, + help='increase --test verbosity') + + parser.add_option('-Q', + '--quiet', + action='store_true', + dest='quiet', + default=False, + help='disable all output') + + msg = 'set debug output level (0-100, 0 means all, 100 means none;' + msg += ' default is 30)' + parser.add_option('-D', + '--debug', + dest='debuglevel', + default=30, + type='int', + help=msg) + + msg = 'run web2py in interactive shell or IPython (if installed) with' + msg += ' specified appname (if app does not exist it will be created).' + msg += ' APPNAME like a/c/f (c,f optional)' + parser.add_option('-S', + '--shell', + dest='shell', + metavar='APPNAME', + help=msg) + + msg = 'run web2py in interactive shell or bpython (if installed) with' + msg += ' specified appname (if app does not exist it will be created).' + msg += '\n Use combined with --shell' + parser.add_option('-B', + '--bpython', + action='store_true', + default=False, + dest='bpython', + help=msg) + + msg = 'only use plain python shell; should be used with --shell option' + parser.add_option('-P', + '--plain', + action='store_true', + default=False, + dest='plain', + help=msg) + + msg = 'auto import model files; default is False; should be used' + msg += ' with --shell option' + parser.add_option('-M', + '--import_models', + action='store_true', + default=False, + dest='import_models', + help=msg) + + msg = 'run PYTHON_FILE in web2py environment;' + msg += ' should be used with --shell option' + parser.add_option('-R', + '--run', + dest='run', + metavar='PYTHON_FILE', + default='', + help=msg) + + msg = 'run doctests in web2py environment; ' +\ + 'TEST_PATH like a/c/f (c,f optional)' + parser.add_option('-T', + '--test', + dest='test', + metavar='TEST_PATH', + default=None, + help=msg) + + parser.add_option('-W', + '--winservice', + dest='winservice', + default='', + help='-W install|start|stop as Windows service') + + msg = 'trigger a cron run manually; usually invoked from a system crontab' + parser.add_option('-C', + '--cron', + action='store_true', + dest='extcron', + default=False, + help=msg) + + msg = 'triggers the use of softcron' + parser.add_option('--softcron', + action='store_true', + dest='softcron', + default=False, + help=msg) + + parser.add_option('-N', + '--no-cron', + action='store_true', + dest='nocron', + default=False, + help='do not start cron automatically') + + parser.add_option('-J', + '--cronjob', + action='store_true', + dest='cronjob', + default=False, + help='identify cron-initiated command') + + parser.add_option('-L', + '--config', + dest='config', + default='', + help='config file') + + parser.add_option('-F', + '--profiler', + dest='profiler_filename', + default=None, + help='profiler filename') + + parser.add_option('-t', + '--taskbar', + action='store_true', + dest='taskbar', + default=False, + help='use web2py gui and run in taskbar (system tray)') + + parser.add_option('', + '--nogui', + action='store_true', + default=False, + dest='nogui', + help='text-only, no GUI') + + parser.add_option('-A', + '--args', + action='store', + dest='args', + default=None, + help='should be followed by a list of arguments to be passed to script, to be used with -S, -A must be the last option') + + parser.add_option('--no-banner', + action='store_true', + default=False, + dest='nobanner', + help='Do not print header banner') + + msg = 'listen on multiple addresses: "ip:port:cert:key;ip2:port2:cert2:key2;..." (:cert:key optional; no spaces)' + parser.add_option('--interfaces', + action='store', + dest='interfaces', + default=None, + help=msg) + + if '-A' in sys.argv: k = sys.argv.index('-A') + elif '--args' in sys.argv: k = sys.argv.index('--args') + else: k=len(sys.argv) + sys.argv, other_args = sys.argv[:k], sys.argv[k+1:] + (options, args) = parser.parse_args() + options.args = [options.run] + other_args + global_settings.cmd_options = options + global_settings.cmd_args = args + + if options.quiet: + capture = cStringIO.StringIO() + sys.stdout = capture + logger.setLevel(logging.CRITICAL + 1) + else: + logger.setLevel(options.debuglevel) + + if options.config[-3:] == '.py': + options.config = options.config[:-3] + + if options.cronjob: + global_settings.cronjob = True # tell the world + options.nocron = True # don't start cron jobs + options.plain = True # cronjobs use a plain shell + + options.folder = os.path.abspath(options.folder) + + # accept --interfaces in the form "ip:port:cert:key;ip2:port2;ip3:port3:cert3:key3" + # (no spaces; optional cert:key indicate SSL) + # + if isinstance(options.interfaces, str): + options.interfaces = [interface.split(':') for interface in options.interfaces.split(';')] + for interface in options.interfaces: + interface[1] = int(interface[1]) # numeric port + options.interfaces = [tuple(interface) for interface in options.interfaces] + + if options.numthreads is not None and options.minthreads is None: + options.minthreads = options.numthreads # legacy + + if not options.cronjob: + # If we have the applications package or if we should upgrade + if not os.path.exists('applications/__init__.py'): + write_file('applications/__init__.py', '') + + if not os.path.exists('welcome.w2p') or os.path.exists('NEWINSTALL'): + try: + w2p_pack('welcome.w2p','applications/welcome') + os.unlink('NEWINSTALL') + except: + msg = "New installation: unable to create welcome.w2p file" + sys.stderr.write(msg) + + return (options, args) + + +def start(cron=True): + """ Start server """ + + # ## get command line arguments + + (options, args) = console() + + if not options.nobanner: + print ProgramName + print ProgramAuthor + print ProgramVersion + + from dal import drivers + if not options.nobanner: + print 'Database drivers available: %s' % ', '.join(drivers) + + + # ## if -L load options from options.config file + if options.config: + try: + options2 = __import__(options.config, {}, {}, '') + except Exception: + try: + # Jython doesn't like the extra stuff + options2 = __import__(options.config) + except Exception: + print 'Cannot import config file [%s]' % options.config + sys.exit(1) + for key in dir(options2): + if hasattr(options,key): + setattr(options,key,getattr(options2,key)) + + # ## if -T run doctests (no cron) + if hasattr(options,'test') and options.test: + test(options.test, verbose=options.verbose) + return + + # ## if -S start interactive shell (also no cron) + if options.shell: + if options.args!=None: + sys.argv[:] = options.args + run(options.shell, plain=options.plain, bpython=options.bpython, + import_models=options.import_models, startfile=options.run) + return + + # ## if -C start cron run (extcron) and exit + # ## if -N or not cron disable cron in this *process* + # ## if --softcron use softcron + # ## use hardcron in all other cases + if options.extcron: + print 'Starting extcron...' + global_settings.web2py_crontype = 'external' + extcron = newcron.extcron(options.folder) + extcron.start() + extcron.join() + return + elif cron and not options.nocron and options.softcron: + print 'Using softcron (but this is not very efficient)' + global_settings.web2py_crontype = 'soft' + elif cron and not options.nocron: + print 'Starting hardcron...' + global_settings.web2py_crontype = 'hard' + newcron.hardcron(options.folder).start() + + # ## if -W install/start/stop web2py as service + if options.winservice: + if os.name == 'nt': + web2py_windows_service_handler(['', options.winservice], + options.config) + else: + print 'Error: Windows services not supported on this platform' + sys.exit(1) + return + + # ## if no password provided and havetk start Tk interface + # ## or start interface if we want to put in taskbar (system tray) + + try: + options.taskbar + except: + options.taskbar = False + + if options.taskbar and os.name != 'nt': + print 'Error: taskbar not supported on this platform' + sys.exit(1) + + root = None + + if not options.nogui: + try: + import Tkinter + havetk = True + except ImportError: + logger.warn('GUI not available because Tk library is not installed') + havetk = False + + if options.password == '' and havetk or options.taskbar and havetk: + try: + root = Tkinter.Tk() + except: + pass + + if root: + root.focus_force() + if not options.quiet: + presentation(root) + master = web2pyDialog(root, options) + signal.signal(signal.SIGTERM, lambda a, b: master.quit()) + + try: + root.mainloop() + except: + master.quit() + + sys.exit() + + # ## if no tk and no password, ask for a password + + if not root and options.password == '': + options.password = raw_input('choose a password:') + + if not options.password and not options.nobanner: + print 'no password, no admin interface' + + # ## start server + + (ip, port) = (options.ip, int(options.port)) + + if not options.nobanner: + print 'please visit:' + print '\thttp://%s:%s' % (ip, port) + print 'use "kill -SIGTERM %i" to shutdown the web2py server' % os.getpid() + + server = main.HttpServer(ip=ip, + port=port, + password=options.password, + pid_filename=options.pid_filename, + log_filename=options.log_filename, + profiler_filename=options.profiler_filename, + ssl_certificate=options.ssl_certificate, + ssl_private_key=options.ssl_private_key, + min_threads=options.minthreads, + max_threads=options.maxthreads, + server_name=options.server_name, + request_queue_size=options.request_queue_size, + timeout=options.timeout, + shutdown_timeout=options.shutdown_timeout, + path=options.folder, + interfaces=options.interfaces) + + try: + server.start() + except KeyboardInterrupt: + server.stop() + logging.shutdown() + ADDED gluon/winservice.py Index: gluon/winservice.py ================================================================== --- gluon/winservice.py +++ gluon/winservice.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +This file is part of the web2py Web Framework +Developed by Massimo Di Pierro and +Limodou . +License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) + +This makes uses of the pywin32 package +(http://sourceforge.net/projects/pywin32/). +You do not need to install this package to use web2py. + + +""" + +import time +import os +import sys +import traceback +try: + import win32serviceutil + import win32service + import win32event +except: + if os.name == 'nt': + print "Warning, winservice is unable to install the Mark Hammond Win32 extensions" +import servicemanager +import _winreg +from fileutils import up + +__all__ = ['web2py_windows_service_handler'] + +class Service(win32serviceutil.ServiceFramework): + + _svc_name_ = '_unNamed' + _svc_display_name_ = '_Service Template' + + def __init__(self, *args): + win32serviceutil.ServiceFramework.__init__(self, *args) + self.stop_event = win32event.CreateEvent(None, 0, 0, None) + + def log(self, msg): + servicemanager.LogInfoMsg(str(msg)) + + def SvcDoRun(self): + self.ReportServiceStatus(win32service.SERVICE_START_PENDING) + try: + self.ReportServiceStatus(win32service.SERVICE_RUNNING) + self.start() + win32event.WaitForSingleObject(self.stop_event, + win32event.INFINITE) + except: + self.log(traceback.format_exc(sys.exc_info)) + self.SvcStop() + self.ReportServiceStatus(win32service.SERVICE_STOPPED) + + def SvcStop(self): + self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) + try: + self.stop() + except: + self.log(traceback.format_exc(sys.exc_info)) + win32event.SetEvent(self.stop_event) + self.ReportServiceStatus(win32service.SERVICE_STOPPED) + + # to be overridden + + def start(self): + pass + + # to be overridden + + def stop(self): + pass + + +class Web2pyService(Service): + + _svc_name_ = 'web2py' + _svc_display_name_ = 'web2py Service' + _exe_args_ = 'options' + server = None + + def chdir(self): + try: + h = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, + r'SYSTEM\CurrentControlSet\Services\%s' + % self._svc_name_) + try: + cls = _winreg.QueryValue(h, 'PythonClass') + finally: + _winreg.CloseKey(h) + dir = os.path.dirname(cls) + os.chdir(dir) + return True + except: + self.log("Can't change to web2py working path; server is stopped") + return False + + def start(self): + self.log('web2py server starting') + if not self.chdir(): + return + if len(sys.argv) == 2: + opt_mod = sys.argv[1] + else: + opt_mod = self._exe_args_ + options = __import__(opt_mod, [], [], '') + if True: # legacy support for old options files, which have only (deprecated) numthreads + if hasattr(options, 'numthreads') and not hasattr(options, 'minthreads'): + options.minthreads = options.numthreads + if not hasattr(options, 'minthreads'): options.minthreads = None + if not hasattr(options, 'maxthreads'): options.maxthreads = None + import main + self.server = main.HttpServer( + ip=options.ip, + port=options.port, + password=options.password, + pid_filename=options.pid_filename, + log_filename=options.log_filename, + profiler_filename=options.profiler_filename, + ssl_certificate=options.ssl_certificate, + ssl_private_key=options.ssl_private_key, + min_threads=options.minthreads, + max_threads=options.maxthreads, + server_name=options.server_name, + request_queue_size=options.request_queue_size, + timeout=options.timeout, + shutdown_timeout=options.shutdown_timeout, + path=options.folder + ) + try: + self.server.start() + except: + + # self.server.stop() + + self.server = None + raise + + def stop(self): + self.log('web2py server stopping') + if not self.chdir(): + return + if self.server: + self.server.stop() + time.sleep(1) + + +def web2py_windows_service_handler(argv=None, opt_file='options'): + path = os.path.dirname(__file__) + classstring = os.path.normpath(os.path.join(up(path), + 'gluon.winservice.Web2pyService')) + if opt_file: + Web2pyService._exe_args_ = opt_file + win32serviceutil.HandleCommandLine(Web2pyService, + serviceClassString=classstring, argv=['', 'install']) + win32serviceutil.HandleCommandLine(Web2pyService, + serviceClassString=classstring, argv=argv) + + +if __name__ == '__main__': + web2py_windows_service_handler() + ADDED gluon/xmlrpc.py Index: gluon/xmlrpc.py ================================================================== --- gluon/xmlrpc.py +++ gluon/xmlrpc.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +This file is part of the web2py Web Framework +Copyrighted by Massimo Di Pierro +License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) +""" + +from SimpleXMLRPCServer import SimpleXMLRPCDispatcher + + +def handler(request, response, methods): + response.session_id = None # no sessions for xmlrpc + dispatcher = SimpleXMLRPCDispatcher(allow_none=True, encoding=None) + for method in methods: + dispatcher.register_function(method) + dispatcher.register_introspection_functions() + response.headers['Content-Type'] = 'text/xml' + dispatch = getattr(dispatcher, '_dispatch', None) + return dispatcher._marshaled_dispatch(request.body.read(), dispatch) + ADDED logging.example.conf Index: logging.example.conf ================================================================== --- logging.example.conf +++ logging.example.conf @@ -0,0 +1,96 @@ +[loggers] +keys=root,rocket,markdown,web2py,rewrite,app,welcome + +# the default configuration is console-based (stdout) for backward compatibility +# +# note that file-based handlers are thread-safe but not mp-safe; +# for mp-safe logging, configure the appropriate syslog handler + +[handlers] +keys=consoleHandler +#keys=consoleHandler,rotatingFileHandler +#keys=osxSysLogHandler + +[formatters] +keys=simpleFormatter + +[logger_root] +level=WARNING +handlers=consoleHandler + +[logger_web2py] +level=WARNING +handlers=consoleHandler +qualname=web2py +propagate=0 + +[logger_rewrite] +level=WARNING +qualname=web2py.rewrite +handlers=consoleHandler +propagate=0 + +# generic app handler +[logger_app] +level=WARNING +qualname=web2py.app +handlers=consoleHandler +propagate=0 + +# welcome app handler +[logger_welcome] +level=WARNING +qualname=web2py.app.welcome +handlers=consoleHandler +propagate=0 + +# loggers for legacy getLogger calls: Rocket and markdown +[logger_rocket] +level=WARNING +handlers=consoleHandler +qualname=Rocket +propagate=0 + +[logger_markdown] +level=WARNING +handlers=consoleHandler +qualname=markdown +propagate=0 + +[handler_consoleHandler] +class=StreamHandler +level=WARNING +formatter=simpleFormatter +args=(sys.stdout,) + +# Rotating file handler +# mkdir logs in the web2py base directory if not already present +# args: (filename[, mode[, maxBytes[, backupCount[, encoding[, delay]]]]]) +# +[handler_rotatingFileHandler] +class=handlers.RotatingFileHandler +level=INFO +formatter=simpleFormatter +args=("logs/web2py.log", "a", 1000000, 5) + +[handler_osxSysLogHandler] +class=handlers.SysLogHandler +level=WARNING +formatter=simpleFormatter +args=("/var/run/syslog", handlers.SysLogHandler.LOG_DAEMON) + +[handler_linuxSysLogHandler] +class=handlers.SysLogHandler +level=WARNING +formatter=simpleFormatter +args=("/dev/log", handlers.SysLogHandler.LOG_DAEMON) + +[handler_remoteSysLogHandler] +class=handlers.SysLogHandler +level=WARNING +formatter=simpleFormatter +args=(('sysloghost.domain.com', handlers.SYSLOG_UDP_PORT), handlers.SysLogHandler.LOG_DAEMON) + +[formatter_simpleFormatter] +format=%(asctime)s - %(name)s - %(levelname)s - %(message)s +datefmt= ADDED modpythonhandler.py Index: modpythonhandler.py ================================================================== --- modpythonhandler.py +++ modpythonhandler.py @@ -0,0 +1,224 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +This file is part of the web2py Web Framework +Copyrighted by Massimo Di Pierro +License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) + +WSGI wrapper for mod_python. Requires Python 2.2 or greater. +Part of CherryPy mut modified by Massimo Di Pierro (2008) for web2py + + + SetHandler python-program + PythonHandler modpythonhandler + PythonPath \"['/path/to/web2py/'] + sys.path\" + PythonOption SCRIPT_NAME /myapp + + +Some WSGI implementations assume that the SCRIPT_NAME environ variable will +always be equal to 'the root URL of the app'; Apache probably won't act as +you expect in that case. You can add another PythonOption directive to tell +modpython_gateway to force that behavior: + + PythonOption SCRIPT_NAME /mcontrol + +The module.function will be called with no arguments on server shutdown, +once for each child process or thread. +""" + +import traceback +import sys +import os +from mod_python import apache + +path = os.path.dirname(os.path.abspath(__file__)) +os.chdir(path) +sys.path = [path]+[p for p in sys.path if not p==path] + +import gluon.main + + +class InputWrapper(object): + """ Input wrapper for the wsgi handler """ + + def __init__(self, req): + """ InputWrapper constructor """ + + self.req = req + + def close(self): + """ """ + + pass + + def read(self, size=-1): + """ Wrapper for req.read """ + + return self.req.read(size) + + def readline(self, size=-1): + """ Wrapper for req.readline """ + + return self.req.readline(size) + + def readlines(self, hint=-1): + """ Wrapper for req.readlines """ + + return self.req.readlines(hint) + + def __iter__(self): + """ Defines a generator with the req data """ + + line = self.readline() + while line: + yield line + + # Notice this won't prefetch the next line; it only + # gets called if the generator is resumed. + line = self.readline() + + +class ErrorWrapper(object): + """ Error wrapper for the wsgi handler """ + + def __init__(self, req): + """ ErrorWrapper constructor """ + + self.req = req + + def flush(self): + """ """ + + pass + + def write(self, msg): + """ Logs the given msg in the log file """ + + self.req.log_error(msg) + + def writelines(self, seq): + """ Writes various lines in the log file """ + + self.write(''.join(seq)) + + +bad_value = "You must provide a PythonOption '%s', either 'on' or 'off', when running a version of mod_python < 3.1" + + +class Handler: + """ Defines the handler """ + + def __init__(self, req): + """ Handler constructor """ + + self.started = False + options = req.get_options() + + # Threading and forking + try: + q = apache.mpm_query + threaded = q(apache.AP_MPMQ_IS_THREADED) + forked = q(apache.AP_MPMQ_IS_FORKED) + except AttributeError: + threaded = options.get('multithread', '').lower() + + if threaded == 'on': + threaded = True + elif threaded == 'off': + threaded = False + else: + raise ValueError(bad_value % 'multithread') + + forked = options.get('multiprocess', '').lower() + + if forked == 'on': + forked = True + elif forked == 'off': + forked = False + else: + raise ValueError(bad_value % 'multiprocess') + + env = self.environ = dict(apache.build_cgi_env(req)) + + if 'SCRIPT_NAME' in options: + # Override SCRIPT_NAME and PATH_INFO if requested. + env['SCRIPT_NAME'] = options['SCRIPT_NAME'] + env['PATH_INFO'] = req.uri[len(options['SCRIPT_NAME']):] + + env['wsgi.input'] = InputWrapper(req) + env['wsgi.errors'] = ErrorWrapper(req) + env['wsgi.version'] = (1, 0) + env['wsgi.run_once'] = False + + if env.get('HTTPS') in ('yes', 'on', '1'): + env['wsgi.url_scheme'] = 'https' + else: + env['wsgi.url_scheme'] = 'http' + + env['wsgi.multithread'] = threaded + env['wsgi.multiprocess'] = forked + + self.request = req + + def run(self, application): + """ Run the application """ + + try: + result = application(self.environ, self.start_response) + + for data in result: + self.write(data) + + if not self.started: + self.request.set_content_length(0) + + if hasattr(result, 'close'): + result.close() + except: + traceback.print_exc(None, self.environ['wsgi.errors']) + + if not self.started: + self.request.status = 500 + self.request.content_type = 'text/plain' + data = 'A server error occurred. Please contact the ' + \ + 'administrator.' + self.request.set_content_length(len(data)) + self.request.write(data) + + def start_response(self, status, headers, exc_info=None): + """ Defines the request data """ + + if exc_info: + try: + if self.started: + raise exc_info[0], exc_info[1], exc_info[2] + finally: + exc_info = None + + self.request.status = int(status[:3]) + + for (key, val) in headers: + if key.lower() == 'content-length': + self.request.set_content_length(int(val)) + elif key.lower() == 'content-type': + self.request.content_type = val + else: + self.request.headers_out.add(key, val) + + return self.write + + def write(self, data): + """ Write the request data """ + + if not self.started: + self.started = True + + self.request.write(data) + + +def handler(req): + """ Execute the gluon app """ + + Handler(req).run(gluon.main.wsgibase) + return apache.OK ADDED options_std.py Index: options_std.py ================================================================== --- options_std.py +++ options_std.py @@ -0,0 +1,33 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# when web2py is run as a windows service (web2py.exe -W) +# it does not load the command line options but it +# expects to find conifguration settings in a file called +# +# web2py/options.py +# +# this file is an example for options.py + +import socket +import os + +ip = '0.0.0.0' +port = 80 +interfaces=[('0.0.0.0',80),('0.0.0.0',443,'ssl_private_key.pem','ssl_certificate.pem')] +password = '' # ## means use the previous password +pid_filename = 'httpserver.pid' +log_filename = 'httpserver.log' +profiler_filename = None +#ssl_certificate = 'ssl_certificate.pem' # ## path to certificate file +#ssl_private_key = 'ssl_private_key.pem' # ## path to private key file +#numthreads = 50 # ## deprecated; remove +minthreads = None +maxthreads = None +server_name = socket.gethostname() +request_queue_size = 5 +timeout = 30 +shutdown_timeout = 5 +folder = os.getcwd() +extcron = None +nocron = None ADDED queue.example.yaml Index: queue.example.yaml ================================================================== --- queue.example.yaml +++ queue.example.yaml @@ -0,0 +1,8 @@ +# To configure Google App Engine task queues, copy this file to queue.yaml +# and edit as required +# See http://code.google.com/appengine/docs/python/config/queue.html + +queue: +- name: default + rate: 20/m + bucket_size: 1 ADDED router.example.py Index: router.example.py ================================================================== --- router.example.py +++ router.example.py @@ -0,0 +1,198 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# routers are dictionaries of URL routing parameters. +# +# For each request, the effective router is: +# the built-in default base router (shown below), +# updated by the BASE router in routes.py routers, +# updated by the app-specific router in routes.py routers (if any), +# updated by the app-specific router from applications/app/routes.py routers (if any) +# +# +# Router members: +# +# default_application: default application name +# applications: list of all recognized applications, or 'ALL' to use all currently installed applications +# Names in applications are always treated as an application names when they appear first in an incoming URL. +# Set applications=None to disable the removal of application names from outgoing URLs. +# domains: optional dict mapping domain names to application names +# The domain name can include a port number: domain.com:8080 +# The application name can include a controller: appx/ctlrx +# Example: +# domains = { "domain.com" : "app", +# "x.domain.com" : "appx", +# }, +# path_prefix: a path fragment that is prefixed to all outgoing URLs and stripped from all incoming URLs +# +# Note: default_application, applications, domains & path_prefix are permitted only in the BASE router, +# and domain makes sense only in an application-specific router. +# The remaining members can appear in the BASE router (as defaults for all applications) +# or in application-specific routers. +# +# default_controller: name of default controller +# default_function: name of default function (in all controllers) +# controllers: list of valid controllers in selected app +# or "DEFAULT" to use all controllers in the selected app plus 'static' +# or None to disable controller-name removal. +# Names in controllers are always treated as controller names when they appear in an incoming URL after +# the (optional) application and language names. +# functions: list of valid functions in the default controller (default None) +# If present, the default function name will be omitted when the controller is the default controller +# and the first arg does not create an ambiguity. +# languages: list of all supported languages +# Names in languages are always treated as language names when they appear in an incoming URL after +# the (optional) application name. +# default_language +# The language code (for example: en, it-it) optionally appears in the URL following +# the application (which may be omitted). For incoming URLs, the code is copied to +# request.language; for outgoing URLs it is taken from request.language. +# If languages=None, language support is disabled. +# The default_language, if any, is omitted from the URL. +# root_static: list of static files accessed from root (by default, favicon.ico & robots.txt) +# (mapped to the default application's static/ directory) +# Each default (including domain-mapped) application has its own root-static files. +# domain: the domain that maps to this application (alternative to using domains in the BASE router) +# exclusive_domain: If True (default is False), an exception is raised if an attempt is made to generate +# an outgoing URL with a different application without providing an explicit host. +# map_hyphen: If True (default is False), hyphens in incoming /a/c/f fields are converted +# to underscores, and back to hyphens in outgoing URLs. +# Language, args and the query string are not affected. +# map_static: By default, the default application is not stripped from static URLs. +# Set map_static=True to override this policy. +# acfe_match: regex for valid application, controller, function, extension /a/c/f.e +# file_match: regex for valid file (used for static file names) +# args_match: regex for valid args +# This validation provides a measure of security. +# If it is changed, the application perform its own validation. +# +# +# The built-in default router supplies default values (undefined members are None): +# +# default_router = dict( +# default_application = 'init', +# applications = 'ALL', +# default_controller = 'default', +# controllers = 'DEFAULT', +# default_function = 'index', +# functions = None, +# default_language = None, +# languages = None, +# root_static = ['favicon.ico', 'robots.txt'], +# domains = None, +# map_hyphen = False, +# acfe_match = r'\w+$', # legal app/ctlr/fcn/ext +# file_match = r'(\w+[-=./]?)+$', # legal file (path) name +# args_match = r'([\w@ -]+[=.]?)+$', # legal arg in args +# ) +# +# See rewrite.map_url_in() and rewrite.map_url_out() for implementation details. + + +# This simple router set overrides only the default application name, +# but provides full rewrite functionality. + +routers = dict( + + # base router + BASE = dict( + default_application = 'welcome', + ), +) + + +# Error-handling redirects all HTTP errors (status codes >= 400) to a specified +# path. If you wish to use error-handling redirects, uncomment the tuple +# below. You can customize responses by adding a tuple entry with the first +# value in 'appName/HTTPstatusCode' format. ( Only HTTP codes >= 400 are +# routed. ) and the value as a path to redirect the user to. You may also use +# '*' as a wildcard. +# +# The error handling page is also passed the error code and ticket as +# variables. Traceback information will be stored in the ticket. +# +# routes_onerror = [ +# (r'init/400', r'/init/default/login') +# ,(r'init/*', r'/init/static/fail.html') +# ,(r'*/404', r'/init/static/cantfind.html') +# ,(r'*/*', r'/init/error/index') +# ] + +# specify action in charge of error handling +# +# error_handler = dict(application='error', +# controller='default', +# function='index') + +# In the event that the error-handling page itself returns an error, web2py will +# fall back to its old static responses. You can customize them here. +# ErrorMessageTicket takes a string format dictionary containing (only) the +# "ticket" key. + +# error_message = '

    %s

    ' +# error_message_ticket = '

    Internal error

    Ticket issued:
    %(ticket)s' + +def __routes_doctest(): + ''' + Dummy function for doctesting routes.py. + + Use filter_url() to test incoming or outgoing routes; + filter_err() for error redirection. + + filter_url() accepts overrides for method and remote host: + filter_url(url, method='get', remote='0.0.0.0', out=False) + + filter_err() accepts overrides for application and ticket: + filter_err(status, application='app', ticket='tkt') + + >>> import os + >>> import gluon.main + >>> from gluon.rewrite import load, filter_url, filter_err, get_effective_router + >>> load(routes=os.path.basename(__file__)) + + >>> filter_url('http://domain.com/abc', app=True) + 'welcome' + >>> filter_url('http://domain.com/welcome', app=True) + 'welcome' + >>> os.path.relpath(filter_url('http://domain.com/favicon.ico')) + 'applications/welcome/static/favicon.ico' + >>> filter_url('http://domain.com/abc') + '/welcome/default/abc' + >>> filter_url('http://domain.com/index/abc') + "/welcome/default/index ['abc']" + >>> filter_url('http://domain.com/default/abc.css') + '/welcome/default/abc.css' + >>> filter_url('http://domain.com/default/index/abc') + "/welcome/default/index ['abc']" + >>> filter_url('http://domain.com/default/index/a bc') + "/welcome/default/index ['a bc']" + + >>> filter_url('https://domain.com/app/ctr/fcn', out=True) + '/app/ctr/fcn' + >>> filter_url('https://domain.com/welcome/ctr/fcn', out=True) + '/ctr/fcn' + >>> filter_url('https://domain.com/welcome/default/fcn', out=True) + '/fcn' + >>> filter_url('https://domain.com/welcome/default/index', out=True) + '/' + >>> filter_url('https://domain.com/welcome/appadmin/index', out=True) + '/appadmin' + >>> filter_url('http://domain.com/welcome/default/fcn?query', out=True) + '/fcn?query' + >>> filter_url('http://domain.com/welcome/default/fcn#anchor', out=True) + '/fcn#anchor' + >>> filter_url('http://domain.com/welcome/default/fcn?query#anchor', out=True) + '/fcn?query#anchor' + + >>> filter_err(200) + 200 + >>> filter_err(399) + 399 + >>> filter_err(400) + 400 + ''' + pass + +if __name__ == '__main__': + import doctest + doctest.testmod() ADDED routes.example.py Index: routes.example.py ================================================================== --- routes.example.py +++ routes.example.py @@ -0,0 +1,165 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# default_application, default_controller, default_function +# are used when the respective element is missing from the +# (possibly rewritten) incoming URL +# +default_application = 'init' # ordinarily set in base routes.py +default_controller = 'default' # ordinarily set in app-specific routes.py +default_function = 'index' # ordinarily set in app-specific routes.py + +# routes_app is a tuple of tuples. The first item in each is a regexp that will +# be used to match the incoming request URL. The second item in the tuple is +# an applicationname. This mechanism allows you to specify the use of an +# app-specific routes.py. This entry is meaningful only in the base routes.py. +# +# Example: support welcome, admin, app and myapp, with myapp the default: + + +routes_app = ((r'/(?Pwelcome|admin|app)\b.*', r'\g'), + (r'(.*)', r'myapp'), + (r'/?(.*)', r'myapp')) + +# routes_in is a tuple of tuples. The first item in each is a regexp that will +# be used to match the incoming request URL. The second item in the tuple is +# what it will be replaced with. This mechanism allows you to redirect incoming +# routes to different web2py locations +# +# Example: If you wish for your entire website to use init's static directory: +# +# routes_in=( (r'/static/(?P[\w./-]+)', r'/init/static/\g') ) +# + +routes_in = ((r'.*:/favicon.ico', r'/examples/static/favicon.ico'), + (r'.*:/robots.txt', r'/examples/static/robots.txt'), + ((r'.*http://otherdomain.com.* (?P.*)', r'/app/ctr\g'))) + +# routes_out, like routes_in translates URL paths created with the web2py URL() +# function in the same manner that route_in translates inbound URL paths. +# + +routes_out = ((r'.*http://otherdomain.com.* /app/ctr(?P.*)', r'\g'), + (r'/app(?P.*)', r'\g')) + +# Error-handling redirects all HTTP errors (status codes >= 400) to a specified +# path. If you wish to use error-handling redirects, uncomment the tuple +# below. You can customize responses by adding a tuple entry with the first +# value in 'appName/HTTPstatusCode' format. ( Only HTTP codes >= 400 are +# routed. ) and the value as a path to redirect the user to. You may also use +# '*' as a wildcard. +# +# The error handling page is also passed the error code and ticket as +# variables. Traceback information will be stored in the ticket. +# +# routes_onerror = [ +# (r'init/400', r'/init/default/login') +# ,(r'init/*', r'/init/static/fail.html') +# ,(r'*/404', r'/init/static/cantfind.html') +# ,(r'*/*', r'/init/error/index') +# ] + +# specify action in charge of error handling +# +# error_handler = dict(application='error', +# controller='default', +# function='index') + +# In the event that the error-handling page itself returns an error, web2py will +# fall back to its old static responses. You can customize them here. +# ErrorMessageTicket takes a string format dictionary containing (only) the +# "ticket" key. + +# error_message = '

    %s

    ' +# error_message_ticket = '

    Internal error

    Ticket issued: %(ticket)s' + +# specify a list of apps that bypass args-checking and use request.raw_args +# +#routes_apps_raw=['myapp'] +#routes_apps_raw=['myapp', 'myotherapp'] + +def __routes_doctest(): + ''' + Dummy function for doctesting routes.py. + + Use filter_url() to test incoming or outgoing routes; + filter_err() for error redirection. + + filter_url() accepts overrides for method and remote host: + filter_url(url, method='get', remote='0.0.0.0', out=False) + + filter_err() accepts overrides for application and ticket: + filter_err(status, application='app', ticket='tkt') + + >>> import os + >>> import gluon.main + >>> from gluon.rewrite import regex_select, load, filter_url, regex_filter_out, filter_err, compile_regex + >>> regex_select() + >>> load(routes=os.path.basename(__file__)) + + >>> os.path.relpath(filter_url('http://domain.com/favicon.ico')) + 'applications/examples/static/favicon.ico' + >>> os.path.relpath(filter_url('http://domain.com/robots.txt')) + 'applications/examples/static/robots.txt' + >>> filter_url('http://domain.com') + '/init/default/index' + >>> filter_url('http://domain.com/') + '/init/default/index' + >>> filter_url('http://domain.com/init/default/fcn') + '/init/default/fcn' + >>> filter_url('http://domain.com/init/default/fcn/') + '/init/default/fcn' + >>> filter_url('http://domain.com/app/ctr/fcn') + '/app/ctr/fcn' + >>> filter_url('http://domain.com/app/ctr/fcn/arg1') + "/app/ctr/fcn ['arg1']" + >>> filter_url('http://domain.com/app/ctr/fcn/arg1/') + "/app/ctr/fcn ['arg1']" + >>> filter_url('http://domain.com/app/ctr/fcn/arg1//') + "/app/ctr/fcn ['arg1', '']" + >>> filter_url('http://domain.com/app/ctr/fcn//arg1') + "/app/ctr/fcn ['', 'arg1']" + >>> filter_url('HTTP://DOMAIN.COM/app/ctr/fcn') + '/app/ctr/fcn' + >>> filter_url('http://domain.com/app/ctr/fcn?query') + '/app/ctr/fcn ?query' + >>> filter_url('http://otherdomain.com/fcn') + '/app/ctr/fcn' + >>> regex_filter_out('/app/ctr/fcn') + '/ctr/fcn' + >>> filter_url('https://otherdomain.com/app/ctr/fcn', out=True) + '/ctr/fcn' + >>> filter_url('https://otherdomain.com/app/ctr/fcn/arg1//', out=True) + '/ctr/fcn/arg1//' + >>> filter_url('http://otherdomain.com/app/ctr/fcn', out=True) + '/fcn' + >>> filter_url('http://otherdomain.com/app/ctr/fcn?query', out=True) + '/fcn?query' + >>> filter_url('http://otherdomain.com/app/ctr/fcn#anchor', out=True) + '/fcn#anchor' + >>> filter_err(200) + 200 + >>> filter_err(399) + 399 + >>> filter_err(400) + 400 + >>> filter_url('http://domain.com/welcome', app=True) + 'welcome' + >>> filter_url('http://domain.com/', app=True) + 'myapp' + >>> filter_url('http://domain.com', app=True) + 'myapp' + >>> compile_regex('.*http://otherdomain.com.* (?P.*)', '/app/ctr\g')[0].pattern + '^.*http://otherdomain.com.* (?P.*)$' + >>> compile_regex('.*http://otherdomain.com.* (?P.*)', '/app/ctr\g')[1] + '/app/ctr\\\\g' + >>> compile_regex('/$c/$f', '/init/$c/$f')[0].pattern + '^.*?:https?://[^:/]+:[a-z]+ /(?P\\\\w+)/(?P\\\\w+)$' + >>> compile_regex('/$c/$f', '/init/$c/$f')[1] + '/init/\\\\g/\\\\g' + ''' + pass + +if __name__ == '__main__': + import doctest + doctest.testmod() ADDED scgihandler.py Index: scgihandler.py ================================================================== --- scgihandler.py +++ scgihandler.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +scgihandler.py - handler for SCGI protocol + +Modified by Michele Comitini +from fcgihandler.py to support SCGI + +fcgihandler has the following copyright: +" This file is part of the web2py Web Framework + Copyrighted by Massimo Di Pierro + License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) +" + +This is a handler for lighttpd+scgi +This file has to be in the PYTHONPATH +Put something like this in the lighttpd.conf file: + +server.document-root="/var/www/web2py/" +# for >= linux-2.6 +server.event-handler = "linux-sysepoll" + +url.rewrite-once = ( + "^(/.+?/static/.+)$" => "/applications$1", + "(^|/.*)$" => "/handler_web2py.scgi$1", +) +scgi.server = ( "/handler_web2py.scgi" => + ("handler_web2py" => + ( "host" => "127.0.0.1", + "port" => "4000", + "check-local" => "disable", # don't forget to set "disable"! + ) + ) +) + + + + +""" + +LOGGING = False +SOFTCRON = False + +import sys +import os + +path = os.path.dirname(os.path.abspath(__file__)) +os.chdir(path) +sys.path = [path]+[p for p in sys.path if not p==path] + +import gluon.main + +# uncomment one of the two imports below depending on the SCGIWSGI server installed +#import paste.util.scgiserver as scgi +from wsgitools.scgi.forkpool import SCGIServer + +if LOGGING: + application = gluon.main.appfactory(wsgiapp=gluon.main.wsgibase, + logfilename='httpserver.log', + profilerfilename=None) +else: + application = gluon.main.wsgibase + +if SOFTCRON: + from gluon.settings import global_settings + global_settings.web2py_crontype = 'soft' + +# uncomment one of the two rows below depending on the SCGIWSGI server installed +#scgi.serve_application(application, '', 4000).run() +SCGIServer(application, port=4000).run() ADDED scripts/autoroutes.py Index: scripts/autoroutes.py ================================================================== --- scripts/autoroutes.py +++ scripts/autoroutes.py @@ -0,0 +1,141 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +''' +autoroutes writes routes for you based on a simpler routing +configuration file called routes.conf. Example: + +----- BEGIN routes.conf------- +127.0.0.1 /examples/default +domain1.com /app1/default +domain2.com /app2/default +domain3.com /app3/default +----- END ---------- + +It maps a domain (the left-hand side) to an app (one app per domain), +and shortens the URLs for the app by removing the listed path prefix. That means: + +http://domain1.com/index is mapped to /app1/default/index +http://domain2.com/index is mapped to /app2/default/index + +It preserves admin, appadmin, static files, favicon.ico and robots.txt: + +http://domain1.com/favicon.ico /welcome/static/favicon.ico +http://domain1.com/robots.txt /welcome/static/robots.txt +http://domain1.com/admin/... /admin/... +http://domain1.com/appadmin/... /app1/appadmin/... +http://domain1.com/static/... /app1/static/... + +and vice-versa. + +To use, cp scripts/autoroutes.py routes.py + +and either edit the config string below, or set config = "" and edit routes.conf +''' + +config = ''' +127.0.0.1 /examples/default +domain1.com /app1/default +domain2.com /app2/default +domain3.com /app3/defcon3 +''' +if not config.strip(): + try: + config_file = open('routes.conf','r') + try: + config = config_file.read() + finally: + config_file.close() + except: + config='' + +def auto_in(apps): + routes = [ + ('/robots.txt','/welcome/static/robots.txt'), + ('/favicon.ico','/welcome/static/favicon.ico'), + ('/admin$anything','/admin$anything'), + ] + for domain,path in [x.strip().split() for x in apps.split('\n') if x.strip() and not x.strip().startswith('#')]: + if not path.startswith('/'): path = '/'+path + if path.endswith('/'): path = path[:-1] + app = path.split('/')[1] + routes += [ + ('.*:https?://(.*\.)?%s:$method /' % domain,'%s' % path), + ('.*:https?://(.*\.)?%s:$method /static/$anything' % domain,'/%s/static/$anything' % app), + ('.*:https?://(.*\.)?%s:$method /appadmin/$anything' % domain,'/%s/appadmin/$anything' % app), + ('.*:https?://(.*\.)?%s:$method /$anything' % domain,'%s/$anything' % path), + ] + return routes + +def auto_out(apps): + routes = [] + for domain,path in [x.strip().split() for x in apps.split('\n') if x.strip() and not x.strip().startswith('#')]: + if not path.startswith('/'): path = '/'+path + if path.endswith('/'): path = path[:-1] + app = path.split('/')[1] + routes += [ + ('/%s/static/$anything' % app,'/static/$anything'), + ('/%s/appadmin/$anything' % app, '/appadmin/$anything'), + ('%s/$anything' % path, '/$anything'), + ] + return routes + +routes_in = auto_in(config) +routes_out = auto_out(config) + +def __routes_doctest(): + ''' + Dummy function for doctesting autoroutes.py. + + Use filter_url() to test incoming or outgoing routes; + filter_err() for error redirection. + + filter_url() accepts overrides for method and remote host: + filter_url(url, method='get', remote='0.0.0.0', out=False) + + filter_err() accepts overrides for application and ticket: + filter_err(status, application='app', ticket='tkt') + + >>> filter_url('http://domain1.com/favicon.ico') + 'http://domain1.com/welcome/static/favicon.ico' + >>> filter_url('https://domain2.com/robots.txt') + 'https://domain2.com/welcome/static/robots.txt' + >>> filter_url('http://domain3.com/fcn') + 'http://domain3.com/app3/defcon3/fcn' + >>> filter_url('http://127.0.0.1/fcn') + 'http://127.0.0.1/examples/default/fcn' + >>> filter_url('HTTP://DOMAIN.COM/app/ctr/fcn') + 'http://domain.com/app/ctr/fcn' + >>> filter_url('http://domain.com/app/ctr/fcn?query') + 'http://domain.com/app/ctr/fcn?query' + >>> filter_url('http://otherdomain.com/fcn') + 'http://otherdomain.com/fcn' + >>> regex_filter_out('/app/ctr/fcn') + '/app/ctr/fcn' + >>> regex_filter_out('/app1/ctr/fcn') + '/app1/ctr/fcn' + >>> filter_url('https://otherdomain.com/app1/default/fcn', out=True) + '/fcn' + >>> filter_url('http://otherdomain.com/app2/ctr/fcn', out=True) + '/app2/ctr/fcn' + >>> filter_url('http://domain1.com/app1/default/fcn?query', out=True) + '/fcn?query' + >>> filter_url('http://domain2.com/app3/defcon3/fcn#anchor', out=True) + '/fcn#anchor' + ''' + pass + +if __name__ == '__main__': + try: + import gluon.main + except ImportError: + import sys, os + os.chdir(os.path.dirname(os.path.dirname(__file__))) + sys.path.append(os.path.dirname(os.path.dirname(__file__))) + import gluon.main + from gluon.rewrite import regex_select, load, filter_url, regex_filter_out + regex_select() # use base routing parameters + load(routes=__file__) # load this file + + import doctest + doctest.testmod() + ADDED scripts/cleancss.py Index: scripts/cleancss.py ================================================================== --- scripts/cleancss.py +++ scripts/cleancss.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import sys +import re + +filename = sys.argv[1] + +datafile = open(filename, 'r') +try: + data = datafile.read() +finally: + datafile.close() +data = re.compile('\s*{\s*').sub(' { ', data) +data = re.compile('\s*;\s*').sub('; ', data) +data = re.compile('\s*}\s*').sub(' }\n', data) +data = re.compile('[ ]+').sub(' ', data) + +print data + ADDED scripts/cleanhtml.py Index: scripts/cleanhtml.py ================================================================== --- scripts/cleanhtml.py +++ scripts/cleanhtml.py @@ -0,0 +1,61 @@ +import sys +import re + +def cleancss(text): + text=re.compile('\s+').sub(' ', text) + text=re.compile('\s*(?P,|:)\s*').sub('\g ', text) + text=re.compile('\s*;\s*').sub(';\n ', text) + text=re.compile('\s*\{\s*').sub(' {\n ', text) + text=re.compile('\s*\}\s*').sub('\n}\n\n', text) + return text + +def cleanhtml(text): + text=text.lower() + r=re.compile('\', re.DOTALL) + scripts=r.findall(text) + text=r.sub(' +
    +' >/etc/uwsgi-python/apps-available/web2py.xml +ln -s /etc/uwsgi-python/apps-available/web2py.xml /etc/uwsgi-python/apps-enabled/web2py.xml + +# Install Web2py +apt-get -y install unzip +cd /home +mkdir www-data +cd www-data +wget http://web2py.com/examples/static/web2py_src.zip +unzip web2py_src.zip +rm web2py_src.zip +chown -R www-data:www-data web2py +cd /home/www-data/web2py +sudo -u www-data python -c "from gluon.main import save_password; save_password('$PW',443)" +/etc/init.d/uwsgi-python restart +/etc/init.d/nginx restart ADDED scripts/setup-web2py-ubuntu.sh Index: scripts/setup-web2py-ubuntu.sh ================================================================== --- scripts/setup-web2py-ubuntu.sh +++ scripts/setup-web2py-ubuntu.sh @@ -0,0 +1,169 @@ +echo "This script will: +1) install all modules need to run web2py on Ubuntu/Debian +2) install web2py in /home/www-data/ +3) create a self signed sll certificate +4) setup web2py with mod_wsgi +5) overwrite /etc/apache2/sites-available/default +6) restart apache. + +You may want to read this cript before running it. + +Press a key to continue...[ctrl+C to abort]" + +read CONFIRM + +#!/bin/bash +# optional +# dpkg-reconfigure console-setup +# dpkg-reconfigure timezoneconf +# nano /etc/hostname +# nano /etc/network/interfaces +# nano /etc/resolv.conf +# reboot now +# ifconfig eth0 + +echo "installing useful packages" +echo "==========================" +apt-get update +apt-get -y install ssh +apt-get -y install zip unzip +apt-get -y install tar +apt-get -y install openssh-server +apt-get -y install build-essential +apt-get -y install python2.5 +apt-get -y install ipython +apt-get -y install python-dev +apt-get -y install postgresql +apt-get -y install apache2 +apt-get -y install libapache2-mod-wsgi +apt-get -y install python2.5-psycopg2 +apt-get -y install postfix +apt-get -y install wget +apt-get -y install python-matplotlib +apt-get -y install python-reportlab +apt-get -y install mercurial +/etc/init.d/postgresql restart + +# optional, uncomment for emacs +# apt-get -y install emacs + +# optional, uncomment for backups using samba +# apt-get -y install samba +# apt-get -y install smbfs + +echo "downloading, installing and starting web2py" +echo "===========================================" +cd /home +mkdir www-data +cd www-data +rm web2py_src.zip* +wget http://web2py.com/examples/static/web2py_src.zip +unzip web2py_src.zip +chown -R www-data:www-data web2py + +echo "setting up apache modules" +echo "=========================" +a2enmod ssl +a2enmod proxy +a2enmod proxy_http +a2enmod headers +a2enmod expires +mkdir /etc/apache2/ssl + +echo "creating a self signed certificate" +echo "==================================" +openssl genrsa 1024 > /etc/apache2/ssl/self_signed.key +chmod 400 /etc/apache2/ssl/self_signed.key +openssl req -new -x509 -nodes -sha1 -days 365 -key /etc/apache2/ssl/self_signed.key > /etc/apache2/ssl/self_signed.cert +openssl x509 -noout -fingerprint -text < /etc/apache2/ssl/self_signed.cert > /etc/apache2/ssl/self_signed.info + +echo "rewriting your apache config file to use mod_wsgi" +echo "=================================================" +echo ' +NameVirtualHost *:80 +NameVirtualHost *:443 + + + WSGIDaemonProcess web2py user=www-data group=www-data + WSGIProcessGroup web2py + WSGIScriptAlias / /home/www-data/web2py/wsgihandler.py + + + AllowOverride None + Order Allow,Deny + Deny from all + + Allow from all + + + + AliasMatch ^/([^/]+)/static/(.*) \ + /home/www-data/web2py/applications/$1/static/$2 + + Options -Indexes + Order Allow,Deny + Allow from all + + + + Deny from all + + + + Deny from all + + + CustomLog /var/log/apache2/access.log common + ErrorLog /var/log/apache2/error.log + + + + SSLEngine on + SSLCertificateFile /etc/apache2/ssl/self_signed.cert + SSLCertificateKeyFile /etc/apache2/ssl/self_signed.key + + WSGIProcessGroup web2py + + WSGIScriptAlias / /home/www-data/web2py/wsgihandler.py + + + AllowOverride None + Order Allow,Deny + Deny from all + + Allow from all + + + + AliasMatch ^/([^/]+)/static/(.*) \ + /home/www-data/web2py/applications/$1/static/$2 + + + Options -Indexes + ExpiresActive On + ExpiresDefault "access plus 1 hour" + Order Allow,Deny + Allow from all + + + CustomLog /var/log/apache2/access.log common + ErrorLog /var/log/apache2/error.log + +' > /etc/apache2/sites-available/default + +# echo "setting up PAM" +# echo "================" +# sudo apt-get install pwauth +# sudo ln -s /etc/apache2/mods-available/authnz_external.load /etc/apache2/mods-enabled +# ln -s /etc/pam.d/apache2 /etc/pam.d/httpd +# usermod -a -G shadow www-data + +echo "restarting apage" +echo "================" + +/etc/init.d/apache2 restart +cd /home/www-data/web2py +sudo -u www-data python -c "from gluon.widget import console; console();" +sudo -u www-data python -c "from gluon.main import save_password; save_password(raw_input('admin password: '),443)" +echo "done!" + ADDED scripts/sync_languages.py Index: scripts/sync_languages.py ================================================================== --- scripts/sync_languages.py +++ scripts/sync_languages.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# TODO: Comment this code + +import sys +import shutil +import os + +from gluon.languages import findT + +sys.path.insert(0, '.') + +file = sys.argv[1] +apps = sys.argv[2:] + +d = {} +for app in apps: + path = 'applications/%s/' % app + findT(path, file) + langfile = open(os.path.join(path, 'languages', '%s.py' % file)) + try: + data = eval(langfile.read()) + finally: + langfile.close() + d.update(data) + +path = 'applications/%s/' % apps[-1] +file1 = os.path.join(path, 'languages', '%s.py' % file) + +f = open(file1, 'w') +try: + f.write('{\n') + keys = d.keys() + keys.sort() + for key in keys: + f.write('%s:%s,\n' % (repr(key), repr(str(d[key])))) + f.write('}\n') +finally: + f.close() + +oapps = reversed(apps[:-1]) +for app in oapps: + path2 = 'applications/%s/' % app + file2 = os.path.join(path2, 'languages', '%s.py' % file) + shutil.copyfile(file1, file2) + ADDED scripts/tickets2db.py Index: scripts/tickets2db.py ================================================================== --- scripts/tickets2db.py +++ scripts/tickets2db.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import sys +import os +import time +import stat +import datetime + +from gluon.utils import md5_hash +from gluon.restricted import RestrictedError + +SLEEP_MINUTES = 5 +DB_URI = 'sqlite://tickets.db' +ALLOW_DUPLICATES = True + +path = os.path.join(request.folder, 'errors') + +db = SQLDB(DB_URI) +db.define_table('ticket', SQLField('app'), SQLField('name'), + SQLField('date_saved', 'datetime'), SQLField('layer'), + SQLField('traceback', 'text'), SQLField('code', 'text')) + +hashes = {} + +while 1: + for file in os.listdir(path): + filename = os.path.join(path, file) + + if not ALLOW_DUPLICATES: + fileobj = open(filename, 'r') + try: + file_data = fileobj.read() + finally: + fileobj.close() + key = md5_hash(file_data) + + if key in hashes: + continue + + hashes[key] = 1 + + error = RestrictedError() + error.load(request, request.application, filename) + + modified_time = os.stat(filename)[stat.ST_MTIME] + modified_time = datetime.datetime.fromtimestamp(modified_time) + + db.ticket.insert(app=request.application, + date_saved=modified_time, + name=file, + layer=error.layer, + traceback=error.traceback, + code=error.code) + + os.unlink(filename) + + db.commit() + time.sleep(SLEEP_MINUTES * 60) + ADDED scripts/tickets2email.py Index: scripts/tickets2email.py ================================================================== --- scripts/tickets2email.py +++ scripts/tickets2email.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import sys +import os +import time +import stat +import datetime + +from gluon.utils import md5_hash +from gluon.restricted import RestrictedError +from gluon.tools import Mail + + +path = os.path.join(request.folder, 'errors') +hashes = {} +mail = Mail() + +### CONFIGURE HERE +SLEEP_MINUTES = 5 +ALLOW_DUPLICATES = True +mail.settings.server = 'localhost:25' +mail.settings.sender = 'you@localhost' +administrator_email = 'you@localhost' +### END CONFIGURATION + +while 1: + for file in os.listdir(path): + filename = os.path.join(path, file) + + if not ALLOW_DUPLICATES: + fileobj = open(filename, 'r') + try: + file_data = fileobj.read() + finally: + fileobj.close() + key = md5_hash(file_data) + + if key in hashes: + continue + + hashes[key] = 1 + + error = RestrictedError() + error.load(request, request.application, filename) + + mail.send(to=administrator_email, subject='new web2py ticket', message=error.traceback) + + os.unlink(filename) + time.sleep(SLEEP_MINUTES * 60) + ADDED scripts/update-web2py.sh Index: scripts/update-web2py.sh ================================================================== --- scripts/update-web2py.sh +++ scripts/update-web2py.sh @@ -0,0 +1,59 @@ +#!/bin/bash +# +# update-web2py.sh +# 2009-12-16 +# +# install in web2py/.. or web2py/ or web2py/scripts as update-web2py.sh +# make executable: chmod +x web2py.sh +# +# save a snapshot of current web2py/ as web2py/../web2py-version.zip +# download the current stable version of web2py +# unzip downloaded version over web2py/ +# +TARGET=web2py + +if [ ! -d $TARGET ]; then + # in case we're in web2py/ + if [ -f ../$TARGET/VERSION ]; then + cd .. + # in case we're in web2py/scripts + elif [ -f ../../$TARGET/VERSION ]; then + cd ../.. + fi +fi +read a VERSION c < $TARGET/VERSION +SAVE=$TARGET-$VERSION +URL=http://www.web2py.com/examples/static/web2py_src.zip + +ZIP=`basename $URL` +SAVED="" + +# Save a zip archive of the current version, +# but don't overwrite a previous save of the same version. +# +if [ -f $SAVE.zip ]; then + echo "Remove or rename $SAVE.zip first" >&2 + exit 1 +fi +if [ -d $TARGET ]; then + echo -n ">>Save old version: " >&2 + cat $TARGET/VERSION >&2 + zip -q -r $SAVE.zip $TARGET + SAVED=$SAVE.zip +fi +# +# Download the new version. +# +echo ">>Download latest web2py release:" >&2 +curl -O $URL +# +# Unzip into web2py/ +# +unzip -q -o $ZIP +rm $ZIP +echo -n ">>New version: " >&2 +cat $TARGET/VERSION >&2 +if [ "$SAVED" != "" ]; then + echo ">>Old version saved as $SAVED" +fi + ADDED scripts/web2py-lock.sh Index: scripts/web2py-lock.sh ================================================================== --- scripts/web2py-lock.sh +++ scripts/web2py-lock.sh @@ -0,0 +1,11 @@ +chown -R nobody:nobody *.py +chown -R nobody:nobody gluon +chown -R nobody:nobody scripts +chown -R nobody:nobody applications/*/modules/ +chown -R nobody:nobody applications/*/models/ +chown -R nobody:nobody applications/*/controllers/ +chown -R nobody:nobody applications/*/views/ +chown -R nobody:nobody applications/*/static/ +chown -R nobody:nobody applications/*/cron/ + +echo "unlock with chown -R www-data:www-data ./" ADDED scripts/web2py.archlinux.sh Index: scripts/web2py.archlinux.sh ================================================================== --- scripts/web2py.archlinux.sh +++ scripts/web2py.archlinux.sh @@ -0,0 +1,44 @@ +#!/bin/bash +# the script should be run +# from WEB2PY root directory + +prog=`basename $0` + +cd `pwd` +chmod +x $prog + +function web2py_start { + nohup ./$prog -a "" 2>/dev/null & + pid=`pgrep $prog | tail -1` + if [ $pid -ne $$ ] + then + echo "WEB2PY has been started." + fi +} +function web2py_stop { + kill -15 `pgrep $prog | grep -v $$` 2>/dev/null + pid=`pgrep $prog | head -1` + if [ $pid -ne $$ ] + then + echo "WEB2PY has been stopped." + fi +} + +case "$1" in + start) + web2py_start + ;; + stop) + web2py_stop + ;; + restart) + web2py_stop + web2py_start + ;; + *) + echo "Usage: $prog [start|stop|restart]" + ;; +esac + +exit 0 + ADDED scripts/web2py.fedora.sh Index: scripts/web2py.fedora.sh ================================================================== --- scripts/web2py.fedora.sh +++ scripts/web2py.fedora.sh @@ -0,0 +1,89 @@ +#!/bin/bash +# +# /etc/rc.d/init.d/web2pyd +# +# Starts the Web2py Daemon on Fedora (Red Hat Linux) +# +# To execute automatically at startup +# +# sudo chkconfig --add web2pyd +# +# chkconfig: 2345 90 10 +# description: Web2py Daemon +# processname: web2pyd +# pidfile: /var/lock/subsys/web2pyd + +source /etc/rc.d/init.d/functions + +RETVAL=0 +NAME=web2pyd +DESC="Web2py Daemon" +DAEMON_DIR="/usr/lib/web2py" +ADMINPASS="admin" +#ADMINPASS="\" +PIDFILE=/var/run/$NAME.pid +PORT=8001 +PYTHON=python + +cd $DAEMON_DIR + +start() { + echo -n $"Starting $DESC ($NAME): " + daemon --check $NAME $PYTHON $DAEMON_DIR/web2py.py -Q --nogui -a $ADMINPASS -d $PIDFILE -p $PORT & + RETVAL=$? + if [ $RETVAL -eq 0 ]; then + touch /var/lock/subsys/$NAME + fi + echo + return $RETVAL +} + +stop() { + echo -n $"Shutting down $DESC ($NAME): " + if [ -r "$PIDFILE" ]; then + pid=`cat $PIDFILE` + kill -TERM $pid + RETVAL=$? + else + RETVAL=1 + fi + [ $RETVAL -eq 0 ] && success || failure + echo + if [ $RETVAL -eq 0 ]; then + rm -f /var/lock/subsys/$NAME + rm -f $PIDFILE + fi + return $RETVAL +} + +restart() { + stop + start +} + +status() { + if [ -r "$PIDFILE" ]; then + pid=`cat $PIDFILE` + fi + if [ $pid ]; then + echo "$NAME (pid $pid) is running..." + else + echo "$NAME is stopped" + fi +} + +case "$1" in + start) start;; + stop) stop;; + status) status;; + restart) restart;; + condrestart) [ -e /var/lock/subsys/$NAME ] && restart + RETVAL=$? + ;; + *) echo $"Usage: $0 {start|stop|restart|condrestart|status}" + RETVAL=1 + ;; +esac + +exit $RETVAL + ADDED scripts/web2py.ubuntu.sh Index: scripts/web2py.ubuntu.sh ================================================================== --- scripts/web2py.ubuntu.sh +++ scripts/web2py.ubuntu.sh @@ -0,0 +1,223 @@ +#! /bin/sh +### BEGIN INIT INFO +# startup script for Ubuntu and Debian Linux servers +# +# To use this file +# cp ubuntu.sh /etc/init.d/web2py +# +# To automatitcally start at reboot +# sudo update-rc.d web2py defaults +# +# Provides: web2py +# Required-Start: $local_fs $remote_fs +# Required-Stop: $local_fs $remote_fs +# Default-Start: 2 3 4 5 +# Default-Stop: S 0 1 6 +# Short-Description: web2py initscript +# Description: This file starts up the web2py server. +### END INIT INFO + +# Author: Mark Moore + +PATH=/usr/sbin:/usr/bin:/sbin:/bin +DESC="Web Framework" +NAME=web2py +PIDDIR=/var/run/$NAME +PIDFILE=$PIDDIR/$NAME.pid +SCRIPTNAME=/etc/init.d/$NAME +DAEMON=/usr/bin/python +DAEMON_DIR=/usr/lib/$NAME +DAEMON_ARGS="web2py.py --password= --pid_filename=$PIDFILE" +DAEMON_USER=web2py + +# Exit if the package is not installed +[ -x "$DAEMON" ] || exit 0 + +# Read configuration variable file if it is present +[ -r /etc/default/$NAME ] && . /etc/default/$NAME + +# Load the VERBOSE setting and other rcS variables +[ -f /etc/default/rcS ] && . /etc/default/rcS + +# Define LSB log_* functions. +# Depend on lsb-base (>= 3.0-6) to ensure that this file is present. +. /lib/lsb/init-functions + +# +# Function that starts the daemon/service +# +do_start() +{ + # Return + # 0 if daemon has been started + # 1 if daemon was already running + # 2 if daemon could not be started + + # The PIDDIR should normally be created during installation. This + # fixes things just in case. + [ -d $PIDDIR ] || mkdir -p $PIDDIR + [ -n "$DAEMON_USER" ] && chown --recursive $DAEMON_USER $PIDDIR + + # Check to see if the daemon is already running. + start-stop-daemon --stop --test --quiet --pidfile $PIDFILE \ + && return 1 + + start-stop-daemon --start --quiet --pidfile $PIDFILE \ + ${DAEMON_USER:+--chuid $DAEMON_USER} --chdir $DAEMON_DIR \ + --background --exec $DAEMON -- $DAEMON_ARGS \ + || return 2 + + return 0; +} + +# +# Function that stops the daemon/service +# +do_stop() +{ + # Return + # 0 if daemon has been stopped + # 1 if daemon was already stopped + # 2 if daemon could not be stopped + # other if a failure occurred + + start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE + RETVAL=$? + # Many daemons don't delete their pidfiles when they exit. + rm -f $PIDFILE + return "$RETVAL" +} + +# +# Function that restarts the daemon/service +# +do_restart() +{ + # Return + # 0 if daemon was (re-)started + # 1 if daemon was not strated or re-started + + do_stop + case "$?" in + 0|1) + do_start + case "$?" in + 0) RETVAL=0 ;; + 1) RETVAL=1 ;; # Old process is still running + *) RETVAL=1 ;; # Failed to start + esac + ;; + *) RETVAL=1 ;; # Failed to stop + esac + + return "$RETVAL" +} + +# +# Function that sends a SIGHUP to the daemon/service +# +do_reload() { + # + # If the daemon can reload its configuration without + # restarting (for example, when it is sent a SIGHUP), + # then implement that here. + # + start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE + return 0 +} + +# +# Function that queries the status of the daemon/service +# +do_status() +{ + # Return + # 0 if daemon is responding and OK + # 1 if daemon is not responding, but PIDFILE exists + # 2 if daemon is not responding, but LOCKFILE exists + # 3 if deamon is not running + # 4 if daemon status is unknown + + # Check to see if the daemon is already running. + start-stop-daemon --stop --test --quiet --pidfile $PIDFILE \ + && return 0 + [ -f $PIDFILE ] && return 1 + return 3 +} + +case "$1" in + start) + [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" + do_start + RETVAL=$? + [ "$VERBOSE" != no ] && + case "$RETVAL" in + 0|1) log_end_msg 0 ;; + *) log_end_msg 1 ;; + esac + exit "$RETVAL" + ;; + stop) + [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" + do_stop + RETVAL=$? + [ "$VERBOSE" != no ] && + case "$RETVAL" in + 0|1) log_end_msg 0 ;; + *) log_end_msg 1 ;; + esac + exit "$RETVAL" + ;; + #reload|force-reload) + # + # If do_reload() is not implemented then leave this commented out + # and leave 'force-reload' as an alias for 'restart'. + # + #[ "$VERBOSE" != no ] && log_daemon_msg "Reloading $DESC" "$NAME" + #do_reload + #RETVAL=$? + #[ "$VERBOSE" != no ] && log_end_msg $? + #exit "$RETVAL" + #;; + restart|force-reload) + # + # If the "reload" option is implemented then remove the + # 'force-reload' alias + # + [ "$VERBOSE" != no ] && log_daemon_msg "Restarting $DESC" "$NAME" + do_restart + RETVAL=$? + [ "$VERBOSE" != no ] && log_end_msg "$RETVAL" + exit "$RETVAL" + ;; + status) + do_status + RETVAL=$? + [ "$VERBOSE" != no ] && + case "$RETVAL" in + 0) log_success_msg "$NAME is running" ;; + *) log_failure_msg "$NAME is not running" ;; + esac + exit "$RETVAL" + ;; + *) + echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload|status}" >&2 + exit 3 + ;; +esac + +: + +# This was based off /etc/init.d/skeleton from the Ubuntu 8.04 Hardy release. +# (md5sum: da0162012b6a916bdbd4e2580282af78). If we notice that changes, we +# should re-examine things. + +# The configuration at the very top seems to be documented as part of the +# Linux Standard Base (LSB) Specification. See section 20.6 Facility Names +# in particular. This is also where I got the spec for the status parm. + +# References: +# http://refspecs.linux-foundation.org/LSB_3.2.0/LSB-Core-generic/LSB-Core-generic.pdf +# Debian Policy SysV init: http://www.debian.org/doc/debian-policy/ch-opersys.html#s-sysvinit +# Examine files in /usr/share/doc/sysv-rc/ + ADDED setup.py Index: setup.py ================================================================== --- setup.py +++ setup.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python + +from distutils.core import setup +from gluon.fileutils import tar, untar, read_file, write_file +import tarfile +import sys + +def tar(file, filelist, expression='^.+$'): + """ + tars dir/files into file, only tars file that match expression + """ + + tar = tarfile.TarFile(file, 'w') + try: + for element in filelist: + try: + for file in listdir(element, expression, add_dirs=True): + tar.add(os.path.join(element, file), file, False) + except: + tar.add(element) + finally: + tar.close() + +def start(): + if 'sdist' in sys.argv: + tar('gluon/env.tar',['applications','VERSION','splashlogo.gif']) + + setup(name='web2py', + version=read_file("VERSION").split()[1], + description="""full-stack framework for rapid development and prototyping + of secure database-driven web-based applications, written and + programmable in Python.""", + long_description=""" + Everything in one package with no dependencies. Development, deployment, + debugging, testing, database administration and maintenance of applications can + be done via the provided web interface. web2py has no configuration files, + requires no installation, can run off a USB drive. web2py uses Python for the + Model, the Views and the Controllers, has a built-in ticketing system to manage + errors, an internationalization engine, works with SQLite, PostgreSQL, MySQL, + MSSQL, FireBird, Oracle, IBM DB2, Informix, Ingres, sybase and Google App Engine via a + Database Abstraction Layer. web2py includes libraries to handle + HTML/XML, RSS, ATOM, CSV, RTF, JSON, AJAX, XMLRPC, WIKI markup. Production + ready, capable of upload/download streaming of very large files, and always + backward compatible. + """, + author='Massimo Di Pierro', + author_email='mdipierro@cs.depaul.edu', + license = 'http://web2py.com/examples/default/license', + classifiers = ["Development Status :: 5 - Production/Stable"], + url='http://web2py.com', + platforms ='Windows, Linux, Mac, Unix,Windows Mobile', + packages=['gluon', + 'gluon/contrib', + 'gluon/contrib/gateways', + 'gluon/contrib/login_methods', + 'gluon/contrib/markdown', + 'gluon/contrib/markmin', + 'gluon/contrib/memcache', + 'gluon/contrib/pyfpdf', + 'gluon/contrib/pymysql', + 'gluon/contrib/pyrtf', + 'gluon/contrib/pysimplesoap', + 'gluon/contrib/simplejson', + 'gluon/tests', + ], + package_data = {'gluon':['env.tar']}, + scripts = ['mkweb2pyenv','runweb2py'], + ) + +if __name__ == '__main__': + print "web2py does not require installation and" + print "you should just start it with:" + print + print "$ python web2py.py" + print + print "are you sure you want to install it anyway (y/n)?" + s = raw_input('>') + if s.lower()[:1]=='y': + start() + ADDED setup_app.py Index: setup_app.py ================================================================== --- setup_app.py +++ setup_app.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +This is a setup.py script generated by py2applet + +Usage: + python setup.py py2app +""" + +from setuptools import setup +from gluon.import_all import base_modules, contributed_modules +import os +import fnmatch + +class reglob: + def __init__(self, directory, pattern="*"): + self.stack = [directory] + self.pattern = pattern + self.files = [] + self.index = 0 + def __getitem__(self, index): + while 1: + try: + file = self.files[self.index] + self.index = self.index + 1 + except IndexError: + self.index = 0 + self.directory = self.stack.pop() + self.files = os.listdir(self.directory) + else: + fullname = os.path.join(self.directory, file) + if os.path.isdir(fullname) and not os.path.islink(fullname): + self.stack.append(fullname) + if not (file.startswith('.') or file.startswith('#') or file.endswith('~')) \ + and fnmatch.fnmatch(file, self.pattern): + return fullname + +setup(app=['web2py.py'], + data_files=[ + 'NEWINSTALL', + 'ABOUT', + 'LICENSE', + 'VERSION', + ] + \ + [x for x in reglob('applications/examples')] + \ + [x for x in reglob('applications/welcome')] + \ + [x for x in reglob('applications/admin')], + options={'py2app': { + 'argv_emulation': True, + 'includes': base_modules, + 'packages': contributed_modules, + }}, + setup_requires=['py2app']) ADDED setup_exe.py Index: setup_exe.py ================================================================== --- setup_exe.py +++ setup_exe.py @@ -0,0 +1,184 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Usage: + Install py2exe: http://sourceforge.net/projects/py2exe/files/ + Copy script to the web2py directory + c:\bin\python26\python build_windows_exe.py py2exe + +Adapted from http://bazaar.launchpad.net/~flavour/sahana-eden/trunk/view/head:/static/scripts/tools/standalone_exe.py +""" + +from distutils.core import setup +import py2exe +from gluon.import_all import base_modules, contributed_modules +from gluon.fileutils import readlines_file +from glob import glob +import fnmatch +import os +import shutil +import sys +import re +import zipfile + +#read web2py version from VERSION file +web2py_version_line = readlines_file('VERSION')[0] +#use regular expression to get just the version number +v_re = re.compile('[0-9]+\.[0-9]+\.[0-9]+') +web2py_version = v_re.search(web2py_version_line).group(0) + +#pull in preferences from config file +import ConfigParser +Config = ConfigParser.ConfigParser() +Config.read('setup_exe.conf') +remove_msft_dlls = Config.getboolean("Setup", "remove_microsoft_dlls") +copy_apps = Config.getboolean("Setup", "copy_apps") +copy_site_packages = Config.getboolean("Setup", "copy_site_packages") +copy_scripts = Config.getboolean("Setup", "copy_scripts") +make_zip = Config.getboolean("Setup", "make_zip") +zip_filename = Config.get("Setup", "zip_filename") +remove_build_files = Config.getboolean("Setup", "remove_build_files") + + +# Python base version +python_version = sys.version[:3] + +# List of modules deprecated in python2.6 that are in the above set +py26_deprecated = ['mhlib', 'multifile', 'mimify', 'sets', 'MimeWriter'] + +if python_version == '2.6': + base_modules += ['json', 'multiprocessing'] + base_modules = list(set(base_modules).difference(set(py26_deprecated))) + + +#I don't know if this is even necessary +if python_version == '2.6': + # Python26 compatibility: http://www.py2exe.org/index.cgi/Tutorial#Step52 + try: + shutil.copytree('C:\Bin\Microsoft.VC90.CRT', 'dist/') + except: + print "You MUST copy Microsoft.VC90.CRT folder into the dist directory" + + +setup( + console=['web2py.py'], + windows=[{'script':'web2py.py', + 'dest_base':'web2py_no_console' # MUST NOT be just 'web2py' otherwise it overrides the standard web2py.exe + }], + name="web2py", + version=web2py_version, + description="web2py web framework", + author="Massimo DiPierro", + license = "LGPL v3", + data_files=[ + 'ABOUT', + 'LICENSE', + 'VERSION', + 'splashlogo.gif', + 'logging.example.conf', + 'options_std.py', + 'app.example.yaml', + 'queue.example.yaml' + ], + options={'py2exe': { + 'packages': contributed_modules, + 'includes': base_modules, + }}, + ) + +print "web2py binary successfully built" + +def copy_folders(source, destination): + """Copy files & folders from source to destination (within dist/)""" + if os.path.exists(os.path.join('dist',destination)): + shutil.rmtree(os.path.join('dist',destination)) + shutil.copytree(os.path.join(source), os.path.join('dist',destination)) + +#should we remove Windows OS dlls user is unlikely to be able to distribute + +if remove_msft_dlls: + print "Deleted Microsoft files not licensed for open source distribution" + print "You are still responsible for making sure you have the rights to distribute any other included files!" + #delete the API-MS-Win-Core DLLs + for f in glob ('dist/API-MS-Win-*.dll'): + os.unlink (f) + #then delete some other files belonging to Microsoft + other_ms_files = ['KERNELBASE.dll', 'MPR.dll', 'MSWSOCK.dll', 'POWRPROF.dll'] + for f in other_ms_files: + try: + os.unlink(os.path.join('dist',f)) + except: + print "unable to delete dist/"+f + sys.exit(1) + + +#Should we include applications? +if copy_apps: + copy_folders('applications', 'applications') + print "Your application(s) have been added" +else: + #only copy web2py's default applications + copy_folders('applications/admin', 'applications/admin') + copy_folders('applications/welcome', 'applications/welcome') + copy_folders('applications/examples', 'applications/examples') + print "Only web2py's admin, examples & welcome applications have been added" + + +#should we copy project's site-packages into dist/site-packages +if copy_site_packages: + #copy site-packages + copy_folders('site-packages', 'site-packages') +else: + #no worries, web2py will create the (empty) folder first run + print "Skipping site-packages" + pass + +#should we copy project's scripts into dist/scripts +if copy_scripts: + #copy scripts + copy_folders('scripts', 'scripts') +else: + #no worries, web2py will create the (empty) folder first run + print "Skipping scripts" + pass + + + +#borrowed from http://bytes.com/topic/python/answers/851018-how-zip-directory-python-using-zipfile +def recursive_zip(zipf, directory, folder = ""): + for item in os.listdir(directory): + if os.path.isfile(os.path.join(directory, item)): + zipf.write(os.path.join(directory, item), folder + os.sep + item) + elif os.path.isdir(os.path.join(directory, item)): + recursive_zip(zipf, os.path.join(directory, item), folder + os.sep + item) + +#should we create a zip file of the build? + +if make_zip: + #to keep consistent with how official web2py windows zip file is setup, + #create a web2py folder & copy dist's files into it + shutil.copytree('dist','zip_temp/web2py') + #create zip file + #use filename specified via command line + zipf = zipfile.ZipFile(zip_filename+".zip", "w", compression=zipfile.ZIP_DEFLATED ) + path = 'zip_temp' #just temp so the web2py directory is included in our zip file + recursive_zip(zipf, path) #leave the first folder as None, as path is root. + zipf.close() + shutil.rmtree('zip_temp') + print "Your Windows binary version of web2py can be found in "+zip_filename+".zip" + print "You may extract the archive anywhere and then run web2py/web2py.exe" + +#should py2exe build files be removed? +if remove_build_files: + shutil.rmtree('build') + shutil.rmtree('deposit') + shutil.rmtree('dist') + print "py2exe build files removed" + +#final info +if not make_zip and not remove_build_files: + print "Your Windows binary & associated files can also be found in /dist" + +print "Finished!" +print "Enjoy web2py " +web2py_version_line ADDED setup_exe_2.6.py Index: setup_exe_2.6.py ================================================================== --- setup_exe_2.6.py +++ setup_exe_2.6.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Usage: + Install py2exe: http://sourceforge.net/projects/py2exe/files/ + Copy script to the web2py directory + c:\bin\python26\python build_windows_exe.py py2exe + +Adapted from http://bazaar.launchpad.net/~flavour/sahana-eden/trunk/view/head:/static/scripts/tools/standalone_exe.py +""" + +from distutils.core import setup +import py2exe +from gluon.import_all import base_modules, contributed_modules +from glob import glob +import fnmatch +import os +import shutil +import sys +import re +import zipfile + +# Python base version +python_version = sys.version[:3] + +# List of modules deprecated in python2.6 that are in the above set +py26_deprecated = ['mhlib', 'multifile', 'mimify', 'sets', 'MimeWriter'] + +if python_version == '2.6': + base_modules += ['json', 'multiprocessing'] + base_modules = list(set(base_modules).difference(set(py26_deprecated))) + + +#I don't know if this is even necessary +if python_version == '2.6': + # Python26 compatibility: http://www.py2exe.org/index.cgi/Tutorial#Step52 + try: + shutil.copytree('C:\Bin\Microsoft.VC90.CRT', 'dist/') + except: + print "You MUST copy Microsoft.VC90.CRT folder into the dist directory" + +#read web2py version from VERSION file +web2py_version_line = readlines_file('VERSION')[0] +#use regular expression to get just the version number +v_re = re.compile('[0-9]+\.[0-9]+\.[0-9]+') +web2py_version = v_re.search(web2py_version_line).group(0) + +setup( + console=['web2py.py'], + windows=[{'script':'web2py.py', + 'dest_base':'web2py_no_console' # MUST NOT be just 'web2py' otherwise it overrides the standard web2py.exe + }], + name="web2py", + version=web2py_version, + description="web2py web framework", + author="Massimo DiPierro", + license = "LGPL v3", + data_files=[ + 'ABOUT', + 'LICENSE', + 'VERSION', + 'splashlogo.gif', + 'logging.example.conf', + 'options_std.py', + 'app.example.yaml', + 'queue.example.yaml' + ], + options={'py2exe': { + 'packages': contributed_modules, + 'includes': base_modules, + }}, + ) + +print "web2py binary successfully built" + +#offer to remove Windows OS dlls user is unlikely to be able to distribute +print "The process of building a windows executable often includes copying files that belong to Windows." +delete_ms_files = raw_input("Delete API-MS-Win-* files that are probably unsafe for distribution? (Y/n) ") +if delete_ms_files.lower().startswith("y"): + print "Deleted Microsoft files not licensed for open source distribution" + print "You are still responsible for making sure you have the rights to distribute any other included files!" + #delete the API-MS-Win-Core DLLs + for f in glob ('dist/API-MS-Win-*.dll'): + os.unlink (f) + #then delete some other files belonging to Microsoft + other_ms_files = ['KERNELBASE.dll', 'MPR.dll', 'MSWSOCK.dll', 'POWRPROF.dll'] + for f in other_ms_files: + try: + os.unlink(os.path.join('dist',f)) + except: + print "unable to delete dist/"+f + sys.exit(1) + +#Offer to include applications +copy_apps = raw_input("Include your web2py application(s)? (Y/n) ") +if os.path.exists('dist/applications'): + shutil.rmtree('dist/applications') +if copy_apps.lower().startswith("y"): + shutil.copytree('applications', 'dist/applications') + print "Your application(s) have been added" +else: + shutil.copytree('applications/admin', 'dist/applications/admin') + shutil.copytree('applications/welcome', 'dist/applications/welcome') + shutil.copytree('applications/examples', 'dist/applications/examples') + print "Only web2py's admin/welcome/examples applications have been added" +print "" + +#Offer to copy project's site-packages into dist/site-packages +copy_apps = raw_input("Include your web2py site-packages & scripts folders? (Y/n) ") +if copy_apps.lower().startswith("y"): + #copy site-packages + if os.path.exists('dist/site-packages') + shutil.rmtree('dist/site-packages') + shutil.copytree('site-packages', 'dist/site-packages') + #copy scripts + if os.path.exists('dist/scripts'): + shutil.rmtree('dist/scripts') + shutil.copytree('scripts', 'dist/scripts') +else: + #no worries, web2py will create the (empty) folder first run + print "Skipping site-packages & scripts" + pass + + +print "" + +#borrowed from http://bytes.com/topic/python/answers/851018-how-zip-directory-python-using-zipfile +def recursive_zip(zipf, directory, folder = ""): + for item in os.listdir(directory): + if os.path.isfile(os.path.join(directory, item)): + zipf.write(os.path.join(directory, item), folder + os.sep + item) + elif os.path.isdir(os.path.join(directory, item)): + recursive_zip(zipf, os.path.join(directory, item), folder + os.sep + item) + +create_zip = raw_input("Create a zip file of web2py for Windows (Y/n)? ") +if create_zip.lower().startswith("y"): + #to keep consistent with how official web2py windows zip file is setup, + #create a web2py folder & copy dist's files into it + shutil.copytree('dist','zip_temp/web2py') + #create zip file + zipf = zipfile.ZipFile("web2py_win.zip", "w", compression=zipfile.ZIP_DEFLATED ) + path = 'zip_temp' #just temp so the web2py directory is included in our zip file + recursive_zip(zipf, path) #leave the first folder as None, as path is root. + zipf.close() + shutil.rmtree('zip_temp') + print "Your Windows binary version of web2py can be found in web2py_win.zip" + print "You may extract the archive anywhere and then run web2py/web2py.exe" + + # offer to clear up + print "Since you created a zip file you likely do not need the build, deposit and dist folders used while building binary." + clean_up_files = raw_input("Delete these un-necessary folders/files? (Y/n) ") + if clean_up_files.lower().startswith("y"): + shutil.rmtree('build') + shutil.rmtree('deposit') + shutil.rmtree('dist') + else: + print "Your Windows binary & associated files can also be found in /dist" +else: + #Didn't want zip file created + print "" + print "Creation of web2py Windows binary completed." + print "You should copy the /dist directory and its contents." + print "To run use web2py.exe" +print "Finished!" +print "Enjoy web2py " +web2py_version_line ADDED site-packages/__init__.py Index: site-packages/__init__.py ================================================================== --- site-packages/__init__.py +++ site-packages/__init__.py @@ -0,0 +1,1 @@ + ADDED splashlogo.gif Index: splashlogo.gif ================================================================== --- splashlogo.gif +++ splashlogo.gif cannot compute difference between binary files ADDED web2py.py Index: web2py.py ================================================================== --- web2py.py +++ web2py.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import os +import sys + +if '__file__' in globals(): + path = os.path.dirname(os.path.abspath(__file__)) + os.chdir(path) +else: + path = os.getcwd() # Seems necessary for py2exe + +sys.path = [path]+[p for p in sys.path if not p==path] + +# import gluon.import_all ##### This should be uncommented for py2exe.py +import gluon.widget + +# Start Web2py and Web2py cron service! +gluon.widget.start(cron=True) ADDED web2py_src.zip Index: web2py_src.zip ================================================================== --- web2py_src.zip +++ web2py_src.zip cannot compute difference between binary files ADDED wsgihandler.py Index: wsgihandler.py ================================================================== --- wsgihandler.py +++ wsgihandler.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +This file is part of the web2py Web Framework +Copyrighted by Massimo Di Pierro +License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) + + +This is a WSGI handler for Apache +Requires apache+mod_wsgi. + +In httpd.conf put something like: + + LoadModule wsgi_module modules/mod_wsgi.so + WSGIScriptAlias / /path/to/wsgihandler.py + +""" + +# change these parameters as required +LOGGING = False +SOFTCRON = False + +import sys +import os + +path = os.path.dirname(os.path.abspath(__file__)) +os.chdir(path) +sys.path = [path]+[p for p in sys.path if not p==path] + +sys.stdout=sys.stderr + +import gluon.main + +if LOGGING: + application = gluon.main.appfactory(wsgiapp=gluon.main.wsgibase, + logfilename='httpserver.log', + profilerfilename=None) +else: + application = gluon.main.wsgibase + +if SOFTCRON: + from gluon.settings import global_settings + global_settings.web2py_crontype = 'soft'
    <>